diff --git a/Cargo.lock b/Cargo.lock index 3221406..4c5eccd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,21 @@ version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" +[[package]] +name = "arraydeque" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ffd3d69bd89910509a5d31d1f1353f38ccffdd116dd0099bbd6627f7bd8ad8" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "ashpd" version = "0.6.7" @@ -397,7 +412,7 @@ version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.42", @@ -540,7 +555,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.42", @@ -961,7 +976,7 @@ version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72793962ceece3863c2965d7f10c8786323b17c7adea75a515809fa20ab799a5" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-crate 2.0.1", "proc-macro-error", "proc-macro2", @@ -1104,6 +1119,15 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "heck" version = "0.4.1" @@ -1224,6 +1248,30 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "keycode" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07873c3182aec8a0eb1a5a4e7b197d42e9d167ba78497a6ee932a82d94673ed" +dependencies = [ + "arraydeque", + "arrayvec", + "bitflags 1.3.2", + "keycode_macro", +] + +[[package]] +name = "keycode_macro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e521ea802f5b3c7194e169d75cab431b0ff08d022f2b6047b08754b4988b89df" +dependencies = [ + "anyhow", + "heck 0.3.3", + "proc-macro2", + "quote", +] + [[package]] name = "lan-mouse" version = "0.5.1" @@ -1239,6 +1287,7 @@ dependencies = [ "futures-core", "glib-build-tools", "gtk4", + "keycode", "libadwaita", "libc", "log", @@ -1421,6 +1470,12 @@ dependencies = [ "memoffset 0.7.1", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "num_cpus" version = "1.16.0" @@ -1959,7 +2014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" dependencies = [ "cfg-expr", - "heck", + "heck 0.4.1", "pkg-config", "toml", "version-compare", @@ -2218,6 +2273,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "url" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 29b965a..268fae0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ clap = { version="4.4.11", features = ["derive"] } gtk = { package = "gtk4", version = "0.7.2", features = ["v4_2"], optional = true } adw = { package = "libadwaita", version = "0.5.2", features = ["v1_1"], optional = true } async-channel = { version = "2.1.1", optional = true } +keycode = "0.4.0" [target.'cfg(unix)'.dependencies] libc = "0.2.148" diff --git a/README.md b/README.md index 1a357b2..1693116 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,7 @@ input capture (to send events *to* other clients) on different operating systems | Wayland (Gnome) | :heavy_check_mark: | WIP | | X11 | :heavy_check_mark: | WIP | | Windows | :heavy_check_mark: | WIP | -| MacOS | ( :heavy_check_mark: ) | WIP | - -Keycode translation is not yet implemented so on MacOS only mouse emulation works as of right now. +| MacOS | :heavy_check_mark: | WIP | > [!Important] > If you are using [Wayfire](https://github.com/WayfireWM/wayfire), make sure to use a recent version (must be newer than October 23rd) and **add `shortcuts-inhibit` to the list of plugins in your wayfire config!** diff --git a/src/backend/consumer/macos.rs b/src/backend/consumer/macos.rs index a82a3fe..954ec2b 100644 --- a/src/backend/consumer/macos.rs +++ b/src/backend/consumer/macos.rs @@ -5,13 +5,20 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint}; use core_graphics::event::{ - CGEvent, CGEventTapLocation, CGEventType, CGMouseButton, EventField, ScrollEventUnit, + CGEvent, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField, ScrollEventUnit, }; use core_graphics::event_source::{CGEventSource, CGEventSourceStateID}; +use keycode::{KeyMap, KeyMapping}; use std::ops::{Index, IndexMut}; +use std::time::Duration; +use tokio::task::AbortHandle; + +const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500); +const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32); pub struct MacOSConsumer { pub event_source: CGEventSource, + repeat_task: Option, button_state: ButtonState, } @@ -59,6 +66,7 @@ impl MacOSConsumer { Ok(Self { event_source, button_state, + repeat_task: None, }) } @@ -66,6 +74,37 @@ impl MacOSConsumer { let event: CGEvent = CGEvent::new(self.event_source.clone()).ok()?; Some(event.location()) } + + async fn spawn_repeat_task(&mut self, key: u16) { + // there can only be one repeating key and it's + // always the last to be pressed + self.kill_repeat_task(); + let event_source = self.event_source.clone(); + let repeat_task = tokio::task::spawn_local(async move { + tokio::time::sleep(DEFAULT_REPEAT_DELAY).await; + loop { + key_event(event_source.clone(), key, 1); + tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await; + } + }); + self.repeat_task = Some(repeat_task.abort_handle()); + } + fn kill_repeat_task(&mut self) { + if let Some(task) = self.repeat_task.take() { + task.abort(); + } + } +} + +fn key_event(event_source: CGEventSource, key: u16, state: u8) { + let event = match CGEvent::new_keyboard_event(event_source, key, state != 0) { + Ok(e) => e, + Err(_) => { + log::warn!("unable to create key event"); + return; + } + }; + event.post(CGEventTapLocation::HID); } #[async_trait] @@ -209,22 +248,24 @@ impl EventConsumer for MacOSConsumer { PointerEvent::Frame { .. } => {} }, Event::Keyboard(keyboard_event) => match keyboard_event { - KeyboardEvent::Key { .. } => { - /* - let code = CGKeyCode::from_le(key as u16); - let event = match CGEvent::new_keyboard_event( - self.event_source.clone(), - code, - match state { 1 => true, _ => false } - ) { - Ok(e) => e, + KeyboardEvent::Key { + time: _, + key, + state, + } => { + let code = match KeyMap::from_key_mapping(KeyMapping::Evdev(key as u16)) { + Ok(k) => k.mac as CGKeyCode, Err(_) => { - log::warn!("unable to create key event"); - return + log::warn!("unable to map key event"); + return; } }; - event.post(CGEventTapLocation::HID); - */ + match state { + // pressed + 1 => self.spawn_repeat_task(code).await, + _ => self.kill_repeat_task(), + } + key_event(self.event_source.clone(), code, state) } KeyboardEvent::Modifiers { .. } => {} },