diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f2fcfba..a76d273 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -109,6 +109,26 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -231,6 +251,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfb" version = "0.7.3" @@ -280,6 +309,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "cmake" version = "0.1.50" @@ -414,6 +454,26 @@ dependencies = [ "libc", ] +[[package]] +name = "coreaudio-rs" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff" +dependencies = [ + "bitflags", + "core-foundation-sys 0.6.2", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f034b2258e6c4ade2f73bf87b21047567fb913ee9550837c2316d139b0262b24" +dependencies = [ + "bindgen", +] + [[package]] name = "cpufeatures" version = "0.2.7" @@ -734,7 +794,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24ce75530893d834dcfe3bb67ce0e7dec489484e7cb4423ca31618af4bab24fe" dependencies = [ - "nom", + "nom 3.2.1", ] [[package]] @@ -1674,10 +1734,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "libc" -version = "0.2.142" +name = "lazycell" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc207893e85c5d6be840e969b496b53d94cec8be2d501b214f50daa97fa8024" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] [[package]] name = "libudev-sys" @@ -1831,7 +1907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eb961d01a3bb07969cfa276be2ab88c31d0fefa77a872696832732d6e9ec094" dependencies = [ "mccs", - "nom", + "nom 3.2.1", ] [[package]] @@ -1841,7 +1917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cdaa8fe19a1a1918becc1b8cbbbdc1058bc71411dff4de0a6ec6b5269f49d38" dependencies = [ "mccs", - "nom", + "nom 3.2.1", "serde", "serde_derive", "serde_yaml", @@ -1884,6 +1960,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1955,6 +2037,16 @@ dependencies = [ "memchr 1.0.2", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr 2.5.0", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2202,6 +2294,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.2.0" @@ -2623,6 +2721,12 @@ dependencies = [ "uninitialized", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2863,6 +2967,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3330,6 +3440,7 @@ dependencies = [ "color_space", "core-foundation", "core-graphics", + "coreaudio-rs", "ddc-hi", "display-info", "env_logger", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9d7ad0d..ae4a880 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -36,6 +36,7 @@ tokio-stream = "0.1.14" mdns-sd = "0.7.2" futures = "0.3.28" ddc-hi = "0.4.1" +coreaudio-rs = "0.11.2" [features] # this feature is used for production builds or when `devPath` points to the filesystem diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c8da01c..ee9eae9 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,8 +5,9 @@ mod ambient_light; mod display; mod led_color; mod rpc; -pub mod screenshot; +mod screenshot; mod screenshot_manager; +mod volume; use ambient_light::{Border, ColorCalibration, LedStripConfig, LedStripConfigGroup}; use display::{DisplayManager, DisplayState}; @@ -18,6 +19,7 @@ use screenshot_manager::ScreenshotManager; use serde::{Deserialize, Serialize}; use serde_json::to_string; use tauri::{http::ResponseBuilder, regex, Manager}; +use volume::VolumeManager; #[derive(Serialize, Deserialize)] #[serde(remote = "DisplayInfo")] @@ -223,6 +225,8 @@ async fn main() { let _mqtt = MqttRpc::global().await; + let _volume = VolumeManager::global().await; + tauri::Builder::default() .invoke_handler(tauri::generate_handler![ greet, diff --git a/src-tauri/src/rpc/board.rs b/src-tauri/src/rpc/board.rs index 03c0b44..14a5bde 100644 --- a/src-tauri/src/rpc/board.rs +++ b/src-tauri/src/rpc/board.rs @@ -45,6 +45,8 @@ impl Board { let display_setting_request_sender = board_message_channels .display_setting_request_sender .clone(); + let volume_setting_request_sender = + board_message_channels.volume_setting_request_sender.clone(); loop { match socket.try_recv(&mut buf) { @@ -60,6 +62,8 @@ impl Board { if let Err(err) = result { error!("send display setting request to channel failed: {:?}", err); } + } else if buf[0] == 4 { + let result = volume_setting_request_sender.send(buf[1] as f32 / 100.0); } } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { diff --git a/src-tauri/src/rpc/channels.rs b/src-tauri/src/rpc/channels.rs index 8e7a20d..d5dc849 100644 --- a/src-tauri/src/rpc/channels.rs +++ b/src-tauri/src/rpc/channels.rs @@ -6,6 +6,7 @@ use super::DisplaySettingRequest; pub struct BoardMessageChannels { pub display_setting_request_sender: Arc>, + pub volume_setting_request_sender: Arc>, } impl BoardMessageChannels { @@ -19,8 +20,12 @@ impl BoardMessageChannels { let (display_setting_request_sender, _) = broadcast::channel(16); let display_setting_request_sender = Arc::new(display_setting_request_sender); + let (volume_setting_request_sender, _) = broadcast::channel(16); + let volume_setting_request_sender = Arc::new(volume_setting_request_sender); + Self { display_setting_request_sender, + volume_setting_request_sender, } } } \ No newline at end of file diff --git a/src-tauri/src/rpc/udp.rs b/src-tauri/src/rpc/udp.rs index 1ef01ae..1acaa14 100644 --- a/src-tauri/src/rpc/udp.rs +++ b/src-tauri/src/rpc/udp.rs @@ -3,9 +3,9 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; use futures::future::join_all; use mdns_sd::{ServiceDaemon, ServiceEvent}; use paris::{error, info, warn}; -use tokio::sync::{watch, OnceCell, RwLock, broadcast}; +use tokio::sync::{watch, OnceCell, RwLock}; -use super::{Board, BoardInfo, DisplaySettingRequest}; +use super::{Board, BoardInfo}; #[derive(Debug, Clone)] pub struct UdpRpc { diff --git a/src-tauri/src/volume/manager.rs b/src-tauri/src/volume/manager.rs new file mode 100644 index 0000000..54b7b60 --- /dev/null +++ b/src-tauri/src/volume/manager.rs @@ -0,0 +1,101 @@ +use std::{ + mem, + sync::{Arc, RwLock}, +}; + +use coreaudio::{ + audio_unit::macos_helpers::get_default_device_id, + sys::{ + kAudioHardwareServiceDeviceProperty_VirtualMasterVolume, kAudioObjectPropertyScopeOutput, + AudioObjectHasProperty, AudioObjectPropertyAddress, AudioObjectSetPropertyData, + }, +}; +use paris::error; +use tokio::sync::OnceCell; + +use crate::rpc::BoardMessageChannels; + +pub struct VolumeManager { + current_volume: Arc>, + handler: Option>, +} + +impl VolumeManager { + pub async fn global() -> &'static Self { + static VOLUME_MANAGER: OnceCell = OnceCell::const_new(); + + VOLUME_MANAGER + .get_or_init(|| async { Self::create() }) + .await + } + + pub fn create() -> Self { + let mut instance = Self { + current_volume: Arc::new(RwLock::new(0.0)), + handler: None, + }; + + instance.subscribe_volume_setting_request(); + + instance + } + + fn subscribe_volume_setting_request(&mut self) { + let handler = tokio::spawn(async { + let channels = BoardMessageChannels::global().await; + let mut request_rx = channels.volume_setting_request_sender.subscribe(); + + while let Ok(volume) = request_rx.recv().await { + if let Err(err) = Self::set_volume(volume) { + error!("failed to set volume: {}", err); + } + } + }); + + self.handler = Some(handler); + } + + fn set_volume(volume: f32) -> anyhow::Result<()> { + log::debug!("set volume: {}", volume); + + let device_id = get_default_device_id(false); + + if device_id.is_none() { + anyhow::bail!("default audio output device is not found."); + } + + let device_id = device_id.unwrap(); + + let address = AudioObjectPropertyAddress { + mSelector: kAudioHardwareServiceDeviceProperty_VirtualMasterVolume, + mScope: kAudioObjectPropertyScopeOutput, + mElement: 0, + }; + + log::debug!("device id: {}", device_id); + log::debug!("address: {:?}", address); + + if 0 == unsafe { AudioObjectHasProperty(device_id, &address) } { + anyhow::bail!("Can not get audio property"); + } + + let size = mem::size_of::() as u32; + + let result = unsafe { + AudioObjectSetPropertyData( + device_id, + &address, + 0, + std::ptr::null(), + size, + &volume as *const f32 as *const std::ffi::c_void, + ) + }; + + if result != 0 { + anyhow::bail!("Can not set audio property"); + } + + Ok(()) + } +} diff --git a/src-tauri/src/volume/mod.rs b/src-tauri/src/volume/mod.rs new file mode 100644 index 0000000..cd499db --- /dev/null +++ b/src-tauri/src/volume/mod.rs @@ -0,0 +1,3 @@ +mod manager; + +pub use manager::*; \ No newline at end of file