diff --git a/Cargo.lock b/Cargo.lock index 6dfa215..45fca0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,24 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote 1.0.37", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.77", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -181,6 +199,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + [[package]] name = "blocking" version = "1.3.1" @@ -214,6 +241,15 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -235,6 +271,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "concurrent-queue" version = "2.2.0" @@ -260,6 +307,26 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "coreaudio-rs" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ca07354f6d0640333ef95f48d460a4bcf34812a7e7967f9b44c728a8f37c28" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b" +dependencies = [ + "bindgen", +] + [[package]] name = "crossbeam-utils" version = "0.8.16" @@ -288,7 +355,7 @@ dependencies = [ "fnv", "ident_case", "proc-macro2", - "quote 1.0.32", + "quote 1.0.37", "strsim", "syn 1.0.109", ] @@ -300,7 +367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", - "quote 1.0.32", + "quote 1.0.37", "syn 1.0.109", ] @@ -326,6 +393,12 @@ dependencies = [ "syn 0.11.11", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "enum-kinds" version = "0.5.1" @@ -333,29 +406,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e40a16955681d469ab3da85aaa6b42ff656b3c67b52e1d8d3dd36afe97fd462" dependencies = [ "proc-macro2", - "quote 1.0.32", + "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "errno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -396,7 +458,7 @@ checksum = "55a5e644a80e6d96b2b4910fa7993301d7b7926c045b475b62202b20a36ce69e" dependencies = [ "darling", "proc-macro2", - "quote 1.0.32", + "quote 1.0.37", "syn 1.0.109", ] @@ -442,6 +504,12 @@ version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "gloo-timers" version = "0.2.6" @@ -471,14 +539,15 @@ checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hidapi" -version = "2.4.1" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723777263b0dcc5730aec947496bd8c3940ba63c15f5633b288cc615f4f6af79" +checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" dependencies = [ "cc", + "cfg-if", "libc", "pkg-config", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -527,7 +596,16 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", ] [[package]] @@ -575,6 +653,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + [[package]] name = "libpulse-binding" version = "2.28.1" @@ -642,6 +730,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[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" @@ -659,7 +753,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -675,6 +769,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -692,7 +796,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", - "quote 1.0.32", + "quote 1.0.37", "syn 1.0.109", ] @@ -715,6 +819,40 @@ dependencies = [ "libc", ] +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", +] + [[package]] name = "object" version = "0.31.1" @@ -796,14 +934,14 @@ dependencies = [ "libc", "log", "pin-project-lite", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -822,13 +960,18 @@ name = "qmk-hid-host" version = "0.0.0" dependencies = [ "async-std", + "block2", "chrono", "core-foundation", "core-foundation-sys", + "coreaudio-rs", + "coreaudio-sys", "hidapi", "libc", "libpulse-binding", "mpris", + "objc2", + "objc2-foundation", "pulsectl-rs", "serde", "serde_json", @@ -848,9 +991,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -914,6 +1057,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.37.23" @@ -925,7 +1074,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -956,8 +1105,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28482318d6641454cb273da158647922d1be6b5a2fcc6165cd89ebdd7ed576b" dependencies = [ "proc-macro2", - "quote 1.0.32", - "syn 2.0.27", + "quote 1.0.37", + "syn 2.0.77", ] [[package]] @@ -980,6 +1129,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1038,18 +1193,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote 1.0.32", + "quote 1.0.37", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.27" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", - "quote 1.0.32", + "quote 1.0.37", "unicode-ident", ] @@ -1078,8 +1233,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", - "quote 1.0.32", - "syn 2.0.27", + "quote 1.0.37", + "syn 2.0.77", ] [[package]] @@ -1120,7 +1275,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1130,8 +1285,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", - "quote 1.0.32", - "syn 2.0.27", + "quote 1.0.37", + "syn 2.0.77", ] [[package]] @@ -1153,8 +1308,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", - "quote 1.0.32", - "syn 2.0.27", + "quote 1.0.37", + "syn 2.0.77", ] [[package]] @@ -1264,8 +1419,8 @@ dependencies = [ "log", "once_cell", "proc-macro2", - "quote 1.0.32", - "syn 2.0.27", + "quote 1.0.37", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -1287,7 +1442,7 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ - "quote 1.0.32", + "quote 1.0.37", "wasm-bindgen-macro-support", ] @@ -1298,8 +1453,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", - "quote 1.0.32", - "syn 2.0.27", + "quote 1.0.37", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1380,8 +1535,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", - "quote 1.0.32", - "syn 2.0.27", + "quote 1.0.37", + "syn 2.0.77", ] [[package]] @@ -1391,8 +1546,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", - "quote 1.0.32", - "syn 2.0.27", + "quote 1.0.37", + "syn 2.0.77", ] [[package]] @@ -1413,6 +1568,15 @@ dependencies = [ "windows-targets 0.48.1", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-targets" version = "0.48.1" diff --git a/Cargo.toml b/Cargo.toml index cab2a88..6aeb43e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,11 @@ mpris = "2.0.1" core-foundation = "0.10" core-foundation-sys = "0.8.7" libc = "0.2" +objc2 = {version = "0.5.2", features = ["default"]} +objc2-foundation = { version = "0.2.2", features = ["all"] } +block2 = "0.5.1" +coreaudio-sys = "0.2.16" +coreaudio-rs = "0.12.1" [target.'cfg(target_os = "windows")'.dependencies] [dependencies.windows] diff --git a/README.md b/README.md index ca9a593..6edaa52 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Application is written in Rust which gives easy access to HID libraries, low-lev | | Windows | Linux | macos | | ------------ | ------------------ | ------------------------------- |--------------------| | Time | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Volume | :heavy_check_mark: | :heavy_check_mark: (PulseAudio) | | +| Volume | :heavy_check_mark: | :heavy_check_mark: (PulseAudio) | :heavy_check_mark: | | Input layout | :heavy_check_mark: | :heavy_check_mark: (X11) | :heavy_check_mark: | | Media info | :heavy_check_mark: | :heavy_check_mark: (D-Bus) | | @@ -58,7 +58,24 @@ When you verified that the application works with your keyboard, you can use `qm 3. Start `qmk-hid-host`, add it to autorun if needed ### MacOS -1. Start `qmk-hid-host`, add it to autorun if needed +1. Download `qmk-hid-host` +2. Modify `qmk-hid-host.json` +3. Add your layouts, for example: +``` +"layouts": [ + "ABC", "Russian" +], +``` +if you don't know what layout are installed in you system, run qmk-hid-host with the layouts listed above, change lang and look at terminal output: +``` +INFO qmk_hid_host::providers::layout::macos: new layout: 'ABC', layout list: ["ABC", "Russian"] +INFO qmk_hid_host::providers::layout::macos: new layout: 'Russian', layout list: ["ABC", "Russian"] +``` +"new layout:" is what you need +4. start `qmk-hid-host` from directory where your `qmk-hid-host.json` is located +5. If you `qmk-hid-host` stuck at `Waiting for keyboard...` there are two common mistakes: + 1. You're wrong with productId in your config + 2. Close Vial app and try again ## Development diff --git a/src/data_type.rs b/src/data_type.rs index acc6b21..fc659bc 100644 --- a/src/data_type.rs +++ b/src/data_type.rs @@ -10,5 +10,6 @@ pub enum DataType { #[cfg(target_os = "macos")] pub enum DataType { Time = 0xAA, // random value that does not conflict with VIA/VIAL, must match firmware - Layout = 0xAC, + Volume, + Layout, } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5b65879..36460a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,37 +8,51 @@ mod data_type; mod keyboard; mod providers; +use std::thread; +use tokio::sync::{broadcast, mpsc}; use config::get_config; use keyboard::Keyboard; #[cfg(not(target_os = "macos"))] -use providers::{_base::Provider, layout::LayoutProvider, time::TimeProvider, media::MediaProvider, volume::VolumeProvider}; +use providers::{_base::Provider, layout::LayoutProvider, time::TimeProvider, media::MediaProvider, volume::VolumeProvider}; + #[cfg(target_os = "macos")] -use providers::{_base::Provider, layout::LayoutProvider, time::TimeProvider}; +use { + providers::{_base::Provider, layout::LayoutProvider, time::TimeProvider, volume::VolumeProvider}, + core_foundation_sys::runloop::CFRunLoopRun, +}; -fn main() { - let env_filter = tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::level_filters::LevelFilter::INFO.into()) - .from_env_lossy(); - let tracing_subscriber = tracing_subscriber::fmt().with_env_filter(env_filter).finish(); - let _ = tracing::subscriber::set_global_default(tracing_subscriber); +#[cfg(target_os = "macos")] +fn run(layouts: Vec, data_sender: mpsc::Sender>, connected_sender: broadcast::Sender) { + let mut is_connected = false; + let mut connected_receiver = connected_sender.subscribe(); - let config = get_config(); + thread::spawn(move || { + let providers: Vec> = vec![ + TimeProvider::new(data_sender.clone(), connected_sender.clone()), + LayoutProvider::new(data_sender.clone(), connected_sender.clone(), layouts), + VolumeProvider::new(data_sender.clone(), connected_sender.clone()), + ]; - let keyboard = Keyboard::new(config.device, config.reconnect_delay); - let (connected_sender, data_sender) = keyboard.connect(); + loop { + if let Ok(connected) = connected_receiver.blocking_recv() { + if !is_connected && connected { + providers.iter().for_each(|p| p.start()); + } - #[cfg(target_os = "macos")] - let providers: Vec> = vec![ - TimeProvider::new(data_sender.clone(), connected_sender.clone()), - LayoutProvider::new(data_sender.clone(), connected_sender.clone(), config.layouts), - ]; + is_connected = connected; + } + } + }); + unsafe { CFRunLoopRun(); } +} - #[cfg(not(target_os = "macos"))] +#[cfg(not(target_os = "macos"))] +fn run(layouts: Vec, data_sender: mpsc::Sender>, connected_sender: broadcast::Sender) { let providers: Vec> = vec![ TimeProvider::new(data_sender.clone(), connected_sender.clone()), VolumeProvider::new(data_sender.clone(), connected_sender.clone()), - LayoutProvider::new(data_sender.clone(), connected_sender.clone(), config.layouts), + LayoutProvider::new(data_sender.clone(), connected_sender.clone(), layouts), MediaProvider::new(data_sender.clone(), connected_sender.clone()), ]; @@ -55,3 +69,17 @@ fn main() { } } } + +fn main() { + let env_filter = tracing_subscriber::EnvFilter::builder() + .with_default_directive(tracing::level_filters::LevelFilter::INFO.into()) + .from_env_lossy(); + let tracing_subscriber = tracing_subscriber::fmt().with_env_filter(env_filter).finish(); + let _ = tracing::subscriber::set_global_default(tracing_subscriber); + let config = get_config(); + + let keyboard = Keyboard::new(config.device, config.reconnect_delay); + let (connected_sender, data_sender) = keyboard.connect(); + + run(config.layouts, data_sender, connected_sender); +} diff --git a/src/providers/layout/macos.rs b/src/providers/layout/macos.rs index 9578f34..67b0909 100644 --- a/src/providers/layout/macos.rs +++ b/src/providers/layout/macos.rs @@ -1,13 +1,8 @@ use crate::data_type::DataType; use core_foundation::base::{CFRelease, TCFType}; use core_foundation::string::{CFString, CFStringRef}; -use core_foundation_sys::runloop::{kCFRunLoopDefaultMode, CFRunLoopRunInMode}; use libc::c_void; -use std::sync::{Arc, Mutex}; -use core_foundation_sys::base::Boolean; -use core_foundation_sys::date::CFTimeInterval; use tokio::sync::{broadcast, mpsc}; - use super::super::_base::Provider; #[link(name = "Carbon", kind = "framework")] @@ -15,6 +10,7 @@ extern "C" { fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut c_void; fn TISGetInputSourceProperty(input_source: *mut c_void, key: CFStringRef) -> *mut CFStringRef; } + fn get_keyboard_layout() -> Option { unsafe { @@ -77,33 +73,23 @@ impl Provider for LayoutProvider { let connected_sender = self.connected_sender.clone(); let mut synced_layout = "".to_string(); - let is_connected = Arc::new(Mutex::new(true)); - let is_connected_ref = is_connected.clone(); std::thread::spawn(move || { let mut connected_receiver = connected_sender.subscribe(); loop { if !connected_receiver.try_recv().unwrap_or(true) { - let mut is_connected = is_connected_ref.lock().unwrap(); - *is_connected = false; break; } - + if let Some(layout) = get_keyboard_layout() { + let lang = layout.split('.').last().unwrap().to_string(); + if synced_layout != lang { + synced_layout = lang; + send_data(&synced_layout, &layouts, &data_sender); + } + } std::thread::sleep(std::time::Duration::from_millis(100)); }} ); - loop { - if !*(is_connected.lock().unwrap()) { - break; - } - if let Some(layout) = get_keyboard_layout() { - let lang = layout.split('.').last().unwrap().to_string(); - if synced_layout != lang { - synced_layout = lang; - send_data(&synced_layout, &layouts, &data_sender); - } - } - unsafe {CFRunLoopRunInMode(kCFRunLoopDefaultMode, CFTimeInterval::from(1), Boolean::from(true));} - } + tracing::info!("Layout Provider stopped"); } } diff --git a/src/providers/volume.rs b/src/providers/volume.rs index 5acf7fa..3535a8c 100644 --- a/src/providers/volume.rs +++ b/src/providers/volume.rs @@ -9,3 +9,9 @@ mod windows; #[cfg(target_os = "windows")] pub use self::windows::VolumeProvider; + +#[cfg(target_os = "macos")] +mod macos; + +#[cfg(target_os = "macos")] +pub use self::macos::VolumeProvider; \ No newline at end of file diff --git a/src/providers/volume/macos.rs b/src/providers/volume/macos.rs new file mode 100644 index 0000000..d87026a --- /dev/null +++ b/src/providers/volume/macos.rs @@ -0,0 +1,222 @@ +use crate::providers::_base::Provider; +use block2::{Block, RcBlock}; +use coreaudio::audio_unit::macos_helpers::get_default_device_id; +use coreaudio_sys::{dispatch_queue_t, kAudioDevicePropertyScopeOutput, kAudioDevicePropertyVolumeScalar, kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeOutput, kAudioObjectSystemObject, AudioObjectGetPropertyData, AudioObjectID, AudioObjectIsPropertySettable, AudioObjectPropertyAddress, OSStatus}; +use std::option::Option; +use std::ptr; +use tokio::sync::{broadcast, mpsc}; + +use crate::data_type::DataType; + + +extern "C" { + pub fn AudioObjectAddPropertyListenerBlock( + in_object_id: AudioObjectID, + in_address: *const AudioObjectPropertyAddress, + in_dispatch_queue: dispatch_queue_t, + in_listener: &Block, + ) -> OSStatus; + + pub fn AudioObjectRemovePropertyListenerBlock( + in_object_id: AudioObjectID, + in_address: *const AudioObjectPropertyAddress, + in_dispatch_queue: dispatch_queue_t, + in_listener: &Block, + ) -> OSStatus; +} + +fn get_current_volume() -> Option { + let device_id = get_default_device_id(false); + if device_id.is_none() { + return None; + } + let active_channel = get_channel(device_id?); + let mut volume: f32 = 0.0; + let mut property_size = size_of_val(&volume) as u32; + let element = active_channel.unwrap_or(kAudioObjectPropertyElementMain as u32); + + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioDevicePropertyVolumeScalar, + mScope: kAudioObjectPropertyScopeOutput, + mElement: element, + }; + + let status = unsafe { + AudioObjectGetPropertyData( + device_id?, + &property_address, + 0, + ptr::null(), + &mut property_size, + &mut volume as *mut _ as *mut _, + ) + }; + + if status == 0 { + Some(volume) + } else { + tracing::info!("Error getting volume for device {}", device_id?); + None + } +} + +fn is_volume_control_supported(device_id: AudioObjectID, channel: u32) -> bool { + let mut is_writable = 0; + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioDevicePropertyVolumeScalar, + mScope: kAudioDevicePropertyScopeOutput, + mElement: channel, + }; + + let status = unsafe { + AudioObjectIsPropertySettable( + device_id, + &property_address, + &mut is_writable, + ) + }; + + status == 0 && is_writable != 0 +} + +fn get_channel(device_id: AudioObjectID) -> Option { + for i in 0..=1 { + if is_volume_control_supported(device_id, i) { + return Some(i); + } + } + None +} + +fn register_volume_listener(listener: &RcBlock) { + let device_id = get_default_device_id(false); + if device_id.is_none() { + return; + } + let channel = get_channel(device_id.unwrap()); + if channel.is_none() { + return; + } + + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioDevicePropertyVolumeScalar, + mScope: kAudioObjectPropertyScopeOutput, + mElement: channel.unwrap(), + }; + + let listener_status = unsafe { AudioObjectRemovePropertyListenerBlock(device_id.unwrap(), &property_address, ptr::null_mut(), &listener)}; + if listener_status == 0 { + tracing::info!( + "Volume listener successfully removed for channel {} of device {}", + channel.unwrap(), device_id.unwrap() + ); + } else { + tracing::info!("Failed to remove volume listener for channel {} of device {}", channel.unwrap(), device_id.unwrap()) + } + + let listener_status = unsafe { + AudioObjectAddPropertyListenerBlock(device_id.unwrap(), &property_address, ptr::null_mut(), &listener) + }; + + if listener_status == 0 { + tracing::info!( + "Volume listener successfully registered for channel {} of device {}", + channel.unwrap(), device_id.unwrap() + ); + } else { + tracing::info!("Failed to register volume listener for channel {} of device {}", channel.unwrap(), device_id.unwrap()) + } +} + + +fn register_device_change_listener(listener: &RcBlock) { + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioHardwarePropertyDefaultOutputDevice, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMain, + }; + + let listener_status = unsafe { + AudioObjectRemovePropertyListenerBlock(kAudioObjectSystemObject, &property_address, ptr::null_mut(), &listener) + }; + if listener_status == 0 { + tracing::info!("Default device change listener successfully removed"); + } else { + tracing::info!("Failed to remove default device change listener"); + } + + let listener_status = unsafe { + AudioObjectAddPropertyListenerBlock(kAudioObjectSystemObject, &property_address, ptr::null_mut(), &listener) + }; + + if listener_status == 0 { + tracing::info!("Default device change listener registered successfully"); + } else { + tracing::info!("Failed to register default device change listener"); + } +} + +fn send_data(value: &f32, push_sender: &mpsc::Sender>) { + let volume = (value * 100.0).round() as u8; + let data = vec![DataType::Volume as u8, volume]; + push_sender.try_send(data).unwrap_or_else(|e| tracing::error!("{}", e)); +} + +pub struct VolumeProvider { + connected_sender: broadcast::Sender, + device_changed_block: RcBlock, + volume_changed_block: RcBlock, +} + +impl VolumeProvider { + pub fn new(data_sender: mpsc::Sender>, connected_sender: broadcast::Sender) -> Box { + let sender = data_sender.clone(); + let volume_changed_block = RcBlock::new(move |_: u32, _: u64|{ + if let Some(volume) = get_current_volume(){ + send_data(&volume, &sender.clone()); + } + }); + + let sender = data_sender.clone(); + let volume_changed_block_clone = volume_changed_block.clone(); + let device_changed_block: RcBlock = RcBlock::new(move |_: u32, _: u64|{ + register_volume_listener(&volume_changed_block_clone); + if let Some(volume) = get_current_volume(){ + send_data(&volume, &sender.clone()); + } + }); + + let provider = VolumeProvider { + connected_sender, + device_changed_block, + volume_changed_block, + }; + Box::new(provider) + } + +} + +impl Provider for VolumeProvider { + + fn start(&self) { + tracing::info!("Volume Provider started"); + let connected_sender = self.connected_sender.clone(); + + register_volume_listener(&self.volume_changed_block); + register_device_change_listener(&self.device_changed_block); + + std::thread::spawn(move || { + let mut connected_receiver = connected_sender.subscribe(); + + loop { + if !connected_receiver.try_recv().unwrap_or(true) { + break; + } + + std::thread::sleep(std::time::Duration::from_millis(100)); + } + + tracing::info!("Volume Provider stopped"); + }); + } +} \ No newline at end of file