From 552cd50266fb9007c7ec1e6c2e549d950d4fbd79 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Sat, 9 Oct 2021 01:39:10 -0400 Subject: [PATCH] macOS support --- Cargo.lock | 82 ++++++- Cargo.toml | 6 + src/keymapper.rs | 108 +++++---- src/lib.rs | 5 + src/macos/codes.rs | 568 +++++++++++++++++++++++++++++++++++++++++++++ src/macos/mod.rs | 414 +++++++++++++++++++++++++++++++++ src/main.rs | 4 +- 7 files changed, 1118 insertions(+), 69 deletions(-) create mode 100644 src/macos/codes.rs create mode 100644 src/macos/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 4c11dab..539ac84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,9 +16,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "cc" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" [[package]] name = "cfg-if" @@ -26,6 +26,47 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[package]] +name = "core-graphics" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types", + "libc", +] + [[package]] name = "epoll" version = "4.3.1" @@ -36,6 +77,21 @@ dependencies = [ "libc", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "getopts" version = "0.2.21" @@ -47,9 +103,9 @@ dependencies = [ [[package]] name = "inotify" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88ed757e516714cd8736e65b84ed901f72458512111871f20c1d377abdfbf5e" +checksum = "9e5fc8f41dbaa9c8492a96c8afffda4f76896ee041d6a57606e70581b80c901f" dependencies = [ "bitflags", "inotify-sys", @@ -73,9 +129,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.102" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "memoffset" @@ -88,9 +144,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7555d6c7164cc913be1ce7f95cbecdabda61eb2ccd89008524af306fb7f5031" +checksum = "d3bb9a13fa32bc5aeb64150cd3f32d6cf4c748f8f8a417cce5d2eb976a8370ba" dependencies = [ "bitflags", "cc", @@ -110,9 +166,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] @@ -121,6 +177,8 @@ dependencies = [ name = "rusty-keys" version = "0.0.3" dependencies = [ + "core-foundation-sys", + "core-graphics", "epoll", "getopts", "inotify", @@ -154,9 +212,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.77" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5519a4f..1469c11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,12 @@ getopts = "0.2.21" toml = { version = "0.5.8", optional = true } serde = { version = "1.0.130", features = ["derive"], optional = true } +[target.'cfg(target_os="macos")'.dependencies] +core-graphics = "0.22" +core-foundation-sys = "0.8" +#rustkit = "0.0.1" +libc = "0.2" + [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.9", features = ["winuser", "wincon"] } lazy_static = "1.4.0" diff --git a/src/keymapper.rs b/src/keymapper.rs index 327857c..bc99f08 100644 --- a/src/keymapper.rs +++ b/src/keymapper.rs @@ -32,7 +32,7 @@ pub trait KeyEvent pub trait Keyboard where - T: Into, + T: Into + Copy, E: KeyEvent, { fn send(&self, event: &mut E) -> Result; @@ -43,11 +43,52 @@ pub trait Keyboard fn right_shift_code(&self) -> T; fn caps_lock_code(&self) -> T; fn block_key(&self) -> Result; + + fn send_half_inverted_key(&self, half_inverted_key: &HalfInvertedKey, event: &mut E, left_shift: bool, right_shift: bool, caps_lock: bool) -> Result { + let value = event.value(); + let mut invert_shift = half_inverted_key.invert_shift; + if value == KeyState::DOWN { + if caps_lock && half_inverted_key.capslock_nomodify { + invert_shift = !invert_shift; + } + if invert_shift { + let (shift_code, up_not_down) = if left_shift { + (self.left_shift_code(), true) + } else if right_shift { + (self.right_shift_code(), true) + } else { + (self.left_shift_code(), false) + }; + self.send_mod_code_value(shift_code, up_not_down, event)?; + // SYN_REPORT after, then key, then key's SYN_REPORT + self.synchronize()?; + } + } + let ret = self.send_mod_code(half_inverted_key.code, event)?; + if value == KeyState::UP { + if caps_lock && half_inverted_key.capslock_nomodify { + invert_shift = !invert_shift; + } + if invert_shift { + let (shift_code, up_not_down) = if left_shift { + (self.left_shift_code(), false) + } else if right_shift { + (self.right_shift_code(), false) + } else { + (self.left_shift_code(), true) + }; + // SYN_REPORT first after key, then shift, then key's SYN_REPORT which will be used for shift's + self.synchronize()?; + self.send_mod_code_value(shift_code, up_not_down, event)?; + } + } + Ok(ret) + } } pub trait KeyMapper where - T: Into, + T: Into + Copy, E: KeyEvent, K: Keyboard, { @@ -254,6 +295,7 @@ pub fn send_event(&mut self, mut event: &mut E, device: &K) -> Result { }, KeyState::UP => { self.current_keymap_index = self.chosen_keymap_index; + #[cfg(not(target_os = "macos"))] { // need to release all currently held down keys, except this one, otherwise ctrl+c will get c stuck because code c value 1 will be sent, but then we'll let go of ctrl, and code j value 0 is sent, so c is never released let orig_code = event.code(); for (idx, key_down) in self.key_state.iter_mut().enumerate() { @@ -262,7 +304,9 @@ pub fn send_event(&mut self, mut event: &mut E, device: &K) -> Result { *key_down = false; } } + // todo: seems like we should not send this here, and instead just set the original code back, and pass it through the keymaps? return device.send_mod_code_value(orig_code, true, event) + } }, _ => () // do nothing for 2 } @@ -337,61 +381,15 @@ impl KeyMapper for CodeKeyMap // todo:capslock_nomodify is like a whole-key thing, not a half-key thing, split code/invert_shift to own struct, send into send_key from *InvertedKey, maybe anyway, consider it, maybe 1 char for whole key and another for half? #[derive(Clone, Copy)] -struct HalfInvertedKey { - code: T, +pub struct HalfInvertedKey { + pub code: T, // code this is describing - invert_shift: bool, + pub invert_shift: bool, // true to invert shift for this code - capslock_nomodify: bool, + pub capslock_nomodify: bool, // true means capslock does not normally modify this, but you would like it to } -fn send_half_inverted_key(half_inverted_key: &HalfInvertedKey, event: &mut E, device: &K, left_shift: bool, right_shift: bool, caps_lock: bool) -> Result - where - T: Into + Clone + Copy, - E: KeyEvent, - K: Keyboard, -{ - let value = event.value(); - let mut invert_shift = half_inverted_key.invert_shift; - if value == KeyState::DOWN { - if caps_lock && half_inverted_key.capslock_nomodify { - invert_shift = !invert_shift; - } - if invert_shift { - let (shift_code, up_not_down) = if left_shift { - (device.left_shift_code(), true) - } else if right_shift { - (device.right_shift_code(), true) - } else { - (device.left_shift_code(), false) - }; - device.send_mod_code_value(shift_code, up_not_down, event)?; - // SYN_REPORT after, then key, then key's SYN_REPORT - device.synchronize()?; - } - } - let ret = device.send_mod_code(half_inverted_key.code, event)?; - if value == KeyState::UP { - if caps_lock && half_inverted_key.capslock_nomodify { - invert_shift = !invert_shift; - } - if invert_shift { - let (shift_code, up_not_down) = if left_shift { - (device.left_shift_code(), false) - } else if right_shift { - (device.right_shift_code(), false) - } else { - (device.left_shift_code(), true) - }; - // SYN_REPORT first after key, then shift, then key's SYN_REPORT which will be used for shift's - device.synchronize()?; - device.send_mod_code_value(shift_code, up_not_down, event)?; - } - } - Ok(ret) -} - impl KeyMapper for HalfInvertedKey where T: Into + Clone + Copy, @@ -402,7 +400,7 @@ impl KeyMapper for HalfInvertedKey let left_shift = key_state[device.left_shift_code().into()]; let right_shift = key_state[device.right_shift_code().into()]; let caps_lock = key_state[device.caps_lock_code().into()]; - send_half_inverted_key(self, event, device, left_shift, right_shift, caps_lock) + device.send_half_inverted_key(self, event, left_shift, right_shift, caps_lock) } } @@ -439,9 +437,9 @@ impl KeyMapper for Key let right_shift = key_state[device.right_shift_code().into()]; let caps_lock = key_state[device.caps_lock_code().into()]; if caps_lock != (left_shift || right_shift) { - send_half_inverted_key(shift_half, event, device, left_shift, right_shift, caps_lock) + device.send_half_inverted_key(shift_half, event, left_shift, right_shift, caps_lock) } else { - send_half_inverted_key(noshift_half, event, device, left_shift, right_shift, caps_lock) + device.send_half_inverted_key(noshift_half, event, left_shift, right_shift, caps_lock) } }, } diff --git a/src/lib.rs b/src/lib.rs index 850fd90..52fdeaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,3 +20,8 @@ pub use windows::*; mod linux; #[cfg(target_os = "linux")] pub use linux::*; + +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_os = "macos")] +pub use macos::*; diff --git a/src/macos/codes.rs b/src/macos/codes.rs new file mode 100644 index 0000000..2ca700d --- /dev/null +++ b/src/macos/codes.rs @@ -0,0 +1,568 @@ + +use std::collections::HashMap; +use core_graphics::event::CGKeyCode; + +pub const KEY_RESERVED: CGKeyCode = 0x31; +pub const KEY_ESC: CGKeyCode = 0x00; +pub const KEY_1: CGKeyCode = 0x0012; +pub const KEY_2: CGKeyCode = 0x0013; +pub const KEY_3: CGKeyCode = 0x0014; +pub const KEY_4: CGKeyCode = 0x0015; +pub const KEY_5: CGKeyCode = 0x0017; +pub const KEY_6: CGKeyCode = 0x0016; +pub const KEY_7: CGKeyCode = 0x001A; +pub const KEY_8: CGKeyCode = 0x001C; +pub const KEY_9: CGKeyCode = 0x0019; +pub const KEY_10: CGKeyCode = 0x001D; +pub const KEY_MINUS: CGKeyCode = 0x001B; +pub const KEY_EQUAL: CGKeyCode = 0x0018; +pub const KEY_BACKSPACE: CGKeyCode = 0x0033; +pub const KEY_TAB: CGKeyCode = 0x0030; +pub const KEY_Q: CGKeyCode = 0x000C; +pub const KEY_W: CGKeyCode = 0x000D; +pub const KEY_E: CGKeyCode = 0x000E; +pub const KEY_R: CGKeyCode = 0x000F; +pub const KEY_T: CGKeyCode = 0x0011; +pub const KEY_Y: CGKeyCode = 0x0010; +pub const KEY_U: CGKeyCode = 0x0020; +pub const KEY_I: CGKeyCode = 0x0022; +pub const KEY_O: CGKeyCode = 0x001F; +pub const KEY_P: CGKeyCode = 0x0023; +pub const KEY_LEFTBRACE: CGKeyCode = 0x0021; +pub const KEY_RIGHTBRACE: CGKeyCode = 0x001E; +pub const KEY_ENTER: CGKeyCode = 0x0024; +pub const KEY_LEFTCTRL: CGKeyCode = 0x003B; +pub const KEY_A: CGKeyCode = 0x0000; +pub const KEY_S: CGKeyCode = 0x0001; +pub const KEY_D: CGKeyCode = 0x0002; +pub const KEY_F: CGKeyCode = 0x0003; +pub const KEY_G: CGKeyCode = 0x0005; +pub const KEY_H: CGKeyCode = 0x0004; +pub const KEY_J: CGKeyCode = 0x0026; +pub const KEY_K: CGKeyCode = 0x0028; +pub const KEY_L: CGKeyCode = 0x0025; +pub const KEY_SEMICOLON: CGKeyCode = 0x0029; +pub const KEY_APOSTROPHE: CGKeyCode = 0x0027; +pub const KEY_GRAVE: CGKeyCode = 0x0032; +pub const KEY_LEFTSHIFT: CGKeyCode = 0x0038; +pub const KEY_BACKSLASH: CGKeyCode = 0x002A; +pub const KEY_Z: CGKeyCode = 0x0006; +pub const KEY_X: CGKeyCode = 0x0007; +pub const KEY_C: CGKeyCode = 0x0008; +pub const KEY_V: CGKeyCode = 0x0009; +pub const KEY_B: CGKeyCode = 0x000B; +pub const KEY_N: CGKeyCode = 0x002D; +pub const KEY_M: CGKeyCode = 0x002E; +pub const KEY_COMMA: CGKeyCode = 0x002B; +pub const KEY_DOT: CGKeyCode = 0x002F; +pub const KEY_SLASH: CGKeyCode = 0x002C; +pub const KEY_RIGHTSHIFT: CGKeyCode = 0x003C; +pub const KEY_KPASTERISK: CGKeyCode = 0x0043; +pub const KEY_LEFTALT: CGKeyCode = 0x003A; +pub const KEY_SPACE: CGKeyCode = 0x0031; +pub const KEY_CAPSLOCK: CGKeyCode = 0x0039; +pub const KEY_F1: CGKeyCode = 0x007A; +pub const KEY_F2: CGKeyCode = 0x0078; +pub const KEY_F3: CGKeyCode = 0x0063; +pub const KEY_F4: CGKeyCode = 0x0076; +pub const KEY_F5: CGKeyCode = 0x0060; +pub const KEY_F6: CGKeyCode = 0x0061; +pub const KEY_F7: CGKeyCode = 0x0062; +pub const KEY_F8: CGKeyCode = 0x0064; +pub const KEY_F9: CGKeyCode = 0x0065; +pub const KEY_F10: CGKeyCode = 0x006D; +pub const KEY_NUMLOCK: CGKeyCode = 0x0047; +pub const KEY_SCROLLLOCK: CGKeyCode = 0x006B; +pub const KEY_KP7: CGKeyCode = 0x0059; +pub const KEY_KP8: CGKeyCode = 0x005B; +pub const KEY_KP9: CGKeyCode = 0x005C; +pub const KEY_KPMINUS: CGKeyCode = 0x004E; +pub const KEY_KP4: CGKeyCode = 0x0056; +pub const KEY_KP5: CGKeyCode = 0x0057; +pub const KEY_KP6: CGKeyCode = 0x0058; +pub const KEY_KPPLUS: CGKeyCode = 0x0045; +pub const KEY_KP1: CGKeyCode = 0x0053; +pub const KEY_KP2: CGKeyCode = 0x0054; +pub const KEY_KP3: CGKeyCode = 0x0055; +pub const KEY_KP0: CGKeyCode = 0x0052; // https://code-with-me.jetbrains.com/UCsz0dzSd1QAbmOi8g0V3w#p=IC&fp=6298EDAF97FA62E9897E2556D1A6631FB66974568C7252E696472EE85078E8A0 +pub const KEY_KPDOT: CGKeyCode = 0x0041; +// pub const KEY_ZENKAKUHANKAKU: CGKeyCode = NOT_FOUND; +pub const KEY_102ND: CGKeyCode = 0x000A; +pub const KEY_F11: CGKeyCode = 0x0067; +pub const KEY_F12: CGKeyCode = 0x006F; +// pub const KEY_RO: CGKeyCode = NOT_FOUND; +// pub const KEY_KATAKANA: CGKeyCode = NOT_FOUND; +// pub const KEY_HIRAGANA: CGKeyCode = NOT_FOUND; +// pub const KEY_HENKAN: CGKeyCode = NOT_FOUND; +// pub const KEY_KATAKANAHIRAGANA: CGKeyCode = NOT_FOUND; +// pub const KEY_MUHENKAN: CGKeyCode = NOT_FOUND; +// pub const KEY_KPJPCOMMA: CGKeyCode = NOT_FOUND; // https://code-with-me.jetbrains.com/Mf59EFUeJZQ2mpGCCCjNWw#p=IC&fp=6298EDAF97FA62E9897E2556D1A6631FB66974568C7252E696472EE85078E8A0 +pub const KEY_KPENTER: CGKeyCode = 0x004C; +pub const KEY_RIGHTCTRL: CGKeyCode = 0x003E; +pub const KEY_KPSLASH: CGKeyCode = 0x004B; +pub const KEY_SYSRQ: CGKeyCode = 0x0069; +pub const KEY_RIGHTALT: CGKeyCode = 0x003D; +pub const KEY_LINEFEED: CGKeyCode = 0x0071; +pub const KEY_HOME: CGKeyCode = 0x0073; +pub const KEY_UP: CGKeyCode = 0x007E; +pub const KEY_PAGEUP: CGKeyCode = 0x0074; +pub const KEY_LEFT: CGKeyCode = 0x007B; +pub const KEY_RIGHT: CGKeyCode = 0x007C; +pub const KEY_END: CGKeyCode = 0x0077; +pub const KEY_DOWN: CGKeyCode = 0x007D; +pub const KEY_PAGEDOWN: CGKeyCode = 0x0079; +pub const KEY_INSERT: CGKeyCode = 0x0072; +pub const KEY_DELETE: CGKeyCode = 0x0075; +// pub const KEY_MACRO: CGKeyCode = NOT_FOUND; +// pub const KEY_MUTE: CGKeyCode = NOT_FOUND; +// pub const KEY_VOLUMEDOWN: CGKeyCode = NOT_FOUND; +// pub const KEY_VOLUMEUP: CGKeyCode = NOT_FOUND; +// pub const KEY_POWER: CGKeyCode = NOT_FOUND; +pub const KEY_KPEQUAL: CGKeyCode = 0x0069; +// pub const KEY_KPPLUSMINUS: CGKeyCode = NOT_FOUND; +pub const KEY_PAUSE: CGKeyCode = KEY_LINEFEED; +pub const KEY_SCALE: CGKeyCode = 0x0047; +pub const KEY_KPCOMMA: CGKeyCode = 0x0036; +// pub const KEY_HANGEUL: CGKeyCode = NOT_FOUND; +// pub const KEY_HANGUEL: CGKeyCode = NOT_FOUND; +// pub const KEY_HANGEUL: CGKeyCode = NOT_FOUND; +// pub const KEY_HANJA: CGKeyCode = NOT_FOUND; +//pub const KEY_YEN: CGKeyCode = NOT_FOUND; +pub const KEY_LEFTMETA: CGKeyCode = 0x0037; +pub const KEY_RIGHTMETA: CGKeyCode = 0x0036; +pub const KEY_COMPOSE: CGKeyCode = 0x006E; +// pub const KEY_STOP: CGKeyCode = NOT_FOUND; +// pub const KEY_AGAIN: CGKeyCode = NOT_FOUND; +// pub const KEY_PROPS: CGKeyCode = NOT_FOUND; +// pub const KEY_UNDO: CGKeyCode = NOT_FOUND; +// pub const KEY_FRONT: CGKeyCode = NOT_FOUND; +// pub const KEY_COPY: CGKeyCode = NOT_FOUND; +// pub const KEY_OPEN: CGKeyCode = NOT_FOUND; +// pub const KEY_PASTE: CGKeyCode = NOT_FOUND; +// pub const KEY_FIND: CGKeyCode = NOT_FOUND; +// pub const KEY_CUT: CGKeyCode = NOT_FOUND; +// pub const KEY_HELP: CGKeyCode = NOT_FOUND; +// pub const KEY_MENU: CGKeyCode = NOT_FOUND; +// pub const KEY_CALC: CGKeyCode = NOT_FOUND; +// pub const KEY_SETUP: CGKeyCode = NOT_FOUND; +// pub const KEY_SLEEP: CGKeyCode = NOT_FOUND; +// pub const KEY_WAKEUP: CGKeyCode = NOT_FOUND; +// pub const KEY_FILE: CGKeyCode = NOT_FOUND; +// pub const KEY_SENDFILE: CGKeyCode = NOT_FOUND; +// pub const KEY_DELETEFILE: CGKeyCode = NOT_FOUND; +// pub const KEY_XFER: CGKeyCode = NOT_FOUND; +// pub const KEY_PROG1: CGKeyCode = NOT_FOUND; +// pub const KEY_PROG2: CGKeyCode = NOT_FOUND; +// pub const KEY_WWW: CGKeyCode = NOT_FOUND; +// pub const KEY_MSDOS: CGKeyCode = NOT_FOUND; +// pub const KEY_COFFEE: CGKeyCode = NOT_FOUND; +// pub const KEY_SCREENLOCK: CGKeyCode = NOT_FOUND; +// pub const KEY_COFFEE: CGKeyCode = NOT_FOUND; +// pub const KEY_ROTATE_DISPLAY: CGKeyCode = NOT_FOUND; +// pub const KEY_DIRECTION: CGKeyCode = NOT_FOUND; +// pub const KEY_ROTATE_DISPLAY: CGKeyCode = NOT_FOUND; +// pub const KEY_CYCLEWINDOWS: CGKeyCode = NOT_FOUND; +// pub const KEY_MAIL: CGKeyCode = NOT_FOUND; +// pub const KEY_BOOKMARKS: CGKeyCode = NOT_FOUND; +// pub const KEY_COMPUTER: CGKeyCode = NOT_FOUND; +// pub const KEY_BACK: CGKeyCode = NOT_FOUND; +// pub const KEY_FORWARD: CGKeyCode = NOT_FOUND; +// pub const KEY_CLOSECD: CGKeyCode = NOT_FOUND; +// pub const KEY_EJECTCD: CGKeyCode = NOT_FOUND; +// pub const KEY_EJECTCLOSECD: CGKeyCode = NOT_FOUND; +// pub const KEY_NEXTSONG: CGKeyCode = NOT_FOUND; +// pub const KEY_PLAYPAUSE: CGKeyCode = NOT_FOUND; +// pub const KEY_PREVIOUSSONG: CGKeyCode = NOT_FOUND; +// pub const KEY_STOPCD: CGKeyCode = NOT_FOUND; +// pub const KEY_RECORD: CGKeyCode = NOT_FOUND; +// pub const KEY_REWIND: CGKeyCode = NOT_FOUND; +// pub const KEY_PHONE: CGKeyCode = NOT_FOUND; +// pub const KEY_ISO: CGKeyCode = NOT_FOUND; +// pub const KEY_CONFIG: CGKeyCode = NOT_FOUND; +// pub const KEY_HOMEPAGE: CGKeyCode = NOT_FOUND; +// pub const KEY_REFRESH: CGKeyCode = NOT_FOUND; +// pub const KEY_EXIT: CGKeyCode = NOT_FOUND; +// pub const KEY_MOVE: CGKeyCode = NOT_FOUND; +// pub const KEY_EDIT: CGKeyCode = NOT_FOUND; +// pub const KEY_SCROLLUP: CGKeyCode = NOT_FOUND; +// pub const KEY_SCROLLDOWN: CGKeyCode = NOT_FOUND; +// pub const KEY_KPLEFTPAREN: CGKeyCode = NOT_FOUND; +// pub const KEY_KPRIGHTPAREN: CGKeyCode = NOT_FOUND; +// pub const KEY_NEW: CGKeyCode = NOT_FOUND; +// pub const KEY_REDO: CGKeyCode = NOT_FOUND; +// pub const KEY_F13: CGKeyCode = NOT_FOUND; +// pub const KEY_F14: CGKeyCode = NOT_FOUND; +// pub const KEY_F15: CGKeyCode = NOT_FOUND; +// pub const KEY_F16: CGKeyCode = NOT_FOUND; +// pub const KEY_F17: CGKeyCode = NOT_FOUND; +// pub const KEY_F18: CGKeyCode = NOT_FOUND; +// pub const KEY_F19: CGKeyCode = NOT_FOUND; +// pub const KEY_F20: CGKeyCode = NOT_FOUND; +// pub const KEY_F21: CGKeyCode = NOT_FOUND; +// pub const KEY_F22: CGKeyCode = NOT_FOUND; +// pub const KEY_F23: CGKeyCode = NOT_FOUND; +// pub const KEY_F24: CGKeyCode = NOT_FOUND; +// pub const KEY_PLAYCD: CGKeyCode = NOT_FOUND; +// pub const KEY_PAUSECD: CGKeyCode = NOT_FOUND; +// pub const KEY_PROG3: CGKeyCode = NOT_FOUND; +// pub const KEY_PROG4: CGKeyCode = NOT_FOUND; +// pub const KEY_DASHBOARD: CGKeyCode = NOT_FOUND; +// pub const KEY_SUSPEND: CGKeyCode = NOT_FOUND; +// pub const KEY_CLOSE: CGKeyCode = NOT_FOUND; +// pub const KEY_PLAY: CGKeyCode = NOT_FOUND; +// pub const KEY_FASTFORWARD: CGKeyCode = NOT_FOUND; +// pub const KEY_BASSBOOST: CGKeyCode = NOT_FOUND; +// pub const KEY_PRINT: CGKeyCode = NOT_FOUND; +// pub const KEY_HP: CGKeyCode = NOT_FOUND; +// pub const KEY_CAMERA: CGKeyCode = NOT_FOUND; +// pub const KEY_SOUND: CGKeyCode = NOT_FOUND; +// pub const KEY_QUESTION: CGKeyCode = NOT_FOUND; +// pub const KEY_EMAIL: CGKeyCode = NOT_FOUND; +// pub const KEY_CHAT: CGKeyCode = NOT_FOUND; +// pub const KEY_SEARCH: CGKeyCode = NOT_FOUND; +// pub const KEY_CONNECT: CGKeyCode = NOT_FOUND; +// pub const KEY_FINANCE: CGKeyCode = NOT_FOUND; +// pub const KEY_SPORT: CGKeyCode = NOT_FOUND; +//pub const KEY_SHOP: CGKeyCode = NOT_FOUND; +pub const KEY_ALTERASE: CGKeyCode = 0x0047; +// pub const KEY_CANCEL: CGKeyCode = NOT_FOUND; +// pub const KEY_BRIGHTNESSDOWN: CGKeyCode = NOT_FOUND; +// pub const KEY_BRIGHTNESSUP: CGKeyCode = NOT_FOUND; +// pub const KEY_MEDIA: CGKeyCode = NOT_FOUND; +// pub const KEY_SWITCHVIDEOMODE: CGKeyCode = NOT_FOUND; +// pub const KEY_KBDILLUMTOGGLE: CGKeyCode = NOT_FOUND; +// pub const KEY_KBDILLUMDOWN: CGKeyCode = NOT_FOUND; +// pub const KEY_KBDILLUMUP: CGKeyCode = NOT_FOUND; +// pub const KEY_SEND: CGKeyCode = NOT_FOUND; +// pub const KEY_REPLY: CGKeyCode = NOT_FOUND; +// pub const KEY_FORWARDMAIL: CGKeyCode = NOT_FOUND; +// pub const KEY_SAVE: CGKeyCode = NOT_FOUND; +// pub const KEY_DOCUMENTS: CGKeyCode = NOT_FOUND; +// pub const KEY_BATTERY: CGKeyCode = NOT_FOUND; +// pub const KEY_BLUETOOTH: CGKeyCode = NOT_FOUND; +// pub const KEY_UWB: CGKeyCode = NOT_FOUND; +// pub const KEY_UNKNOWN: CGKeyCode = NOT_FOUND; +// pub const KEY_VIDEO_NEXT: CGKeyCode = NOT_FOUND; +// pub const KEY_VIDEO_PREV: CGKeyCode = NOT_FOUND; +// pub const KEY_BRIGHTNESS_CYCLE: CGKeyCode = NOT_FOUND; +// pub const KEY_BRIGHTNESS_AUTO: CGKeyCode = NOT_FOUND; +// pub const KEY_BRIGHTNESS_ZERO: CGKeyCode = NOT_FOUND; +// pub const KEY_BRIGHTNESS_AUTO: CGKeyCode = NOT_FOUND; +// pub const KEY_DISPLAY_OFF: CGKeyCode = NOT_FOUND; + +pub fn key_map() -> HashMap<&'static str, CGKeyCode> { + [ + // grep 'Key => 0x' ../rusty-keys-win/src/windows/inputs.rs | tr '[a-z]' '[A-Z]' | sed -r -e 's/KEY => 0X/: CGKeyCode = 0x/' -e 's/^[ ]+/pub const KEY_/' | tr ',' ';' + ("ESC", KEY_ESC), + ("1", KEY_1), + ("2", KEY_2), + ("3", KEY_3), + ("4", KEY_4), + ("5", KEY_5), + ("6", KEY_6), + ("7", KEY_7), + ("8", KEY_8), + ("9", KEY_9), + ("10", KEY_10), + ("MINUS", KEY_MINUS), + ("EQUAL", KEY_EQUAL), + ("BACKSPACE", KEY_BACKSPACE), + ("TAB", KEY_TAB), + ("Q", KEY_Q), + ("W", KEY_W), + ("E", KEY_E), + ("R", KEY_R), + ("T", KEY_T), + ("Y", KEY_Y), + ("U", KEY_U), + ("I", KEY_I), + ("O", KEY_O), + ("P", KEY_P), + ("LEFTBRACE", KEY_LEFTBRACE), + ("RIGHTBRACE", KEY_RIGHTBRACE), + ("ENTER", KEY_ENTER), + ("LEFTCTRL", KEY_LEFTCTRL), + ("A", KEY_A), + ("S", KEY_S), + ("D", KEY_D), + ("F", KEY_F), + ("G", KEY_G), + ("H", KEY_H), + ("J", KEY_J), + ("K", KEY_K), + ("L", KEY_L), + ("SEMICOLON", KEY_SEMICOLON), + ("APOSTROPHE", KEY_APOSTROPHE), + ("GRAVE", KEY_GRAVE), + ("LEFTSHIFT", KEY_LEFTSHIFT), + ("BACKSLASH", KEY_BACKSLASH), + ("Z", KEY_Z), + ("X", KEY_X), + ("C", KEY_C), + ("V", KEY_V), + ("B", KEY_B), + ("N", KEY_N), + ("M", KEY_M), + ("COMMA", KEY_COMMA), + ("DOT", KEY_DOT), + ("SLASH", KEY_SLASH), + ("RIGHTSHIFT", KEY_RIGHTSHIFT), + ("KPASTERISK", KEY_KPASTERISK), + ("LEFTALT", KEY_LEFTALT), + ("SPACE", KEY_SPACE), + ("CAPSLOCK", KEY_CAPSLOCK), + ("F1", KEY_F1), + ("F2", KEY_F2), + ("F3", KEY_F3), + ("F4", KEY_F4), + ("F5", KEY_F5), + ("F6", KEY_F6), + ("F7", KEY_F7), + ("F8", KEY_F8), + ("F9", KEY_F9), + ("F10", KEY_F10), + ("NUMLOCK", KEY_NUMLOCK), + ("SCROLLLOCK", KEY_SCROLLLOCK), + ("KP7", KEY_KP7), + ("KP8", KEY_KP8), + ("KP9", KEY_KP9), + ("KPMINUS", KEY_KPMINUS), + ("KP4", KEY_KP4), + ("KP5", KEY_KP5), + ("KP6", KEY_KP6), + ("KPPLUS", KEY_KPPLUS), + ("KP1", KEY_KP1), + ("KP2", KEY_KP2), + ("KP3", KEY_KP3), + ("KP0", KEY_KP0), + ("KPDOT", KEY_KPDOT), + /* + ("ZENKAKUHANKAKU", KEY_ZENKAKUHANKAKU), + ("102ND", KEY_102ND), + */ + ("F11", KEY_F11), + ("F12", KEY_F12), + /* + ("RO", KEY_RO), + ("KATAKANA", KEY_KATAKANA), + ("HIRAGANA", KEY_HIRAGANA), + ("HENKAN", KEY_HENKAN), + ("KATAKANAHIRAGANA", KEY_KATAKANAHIRAGANA), + ("MUHENKAN", KEY_MUHENKAN), + ("KPJPCOMMA", KEY_KPJPCOMMA), + */ + ("KPENTER", KEY_KPENTER), + ("RIGHTCTRL", KEY_RIGHTCTRL), + ("KPSLASH", KEY_KPSLASH), + ("SYSRQ", KEY_SYSRQ), + ("RIGHTALT", KEY_RIGHTALT), + /* + ("LINEFEED", KEY_LINEFEED), + */ + ("HOME", KEY_HOME), + ("UP", KEY_UP), + ("PAGEUP", KEY_PAGEUP), + ("LEFT", KEY_LEFT), + ("RIGHT", KEY_RIGHT), + ("END", KEY_END), + ("DOWN", KEY_DOWN), + ("PAGEDOWN", KEY_PAGEDOWN), + ("INSERT", KEY_INSERT), + ("DELETE", KEY_DELETE), + /* + ("MACRO", KEY_MACRO), + ("MUTE", KEY_MUTE), + ("VOLUMEDOWN", KEY_VOLUMEDOWN), + ("VOLUMEUP", KEY_VOLUMEUP), + ("POWER", KEY_POWER), + ("KPEQUAL", KEY_KPEQUAL), + ("KPPLUSMINUS", KEY_KPPLUSMINUS), + ("PAUSE", KEY_PAUSE), + ("SCALE", KEY_SCALE), + ("KPCOMMA", KEY_KPCOMMA), + ("HANGEUL", KEY_HANGEUL), + ("HANGUEL", KEY_HANGUEL), + ("HANGEUL", KEY_HANGEUL), + ("HANJA", KEY_HANJA), + ("YEN", KEY_YEN), + ("LEFTMETA", KEY_LEFTMETA), + ("RIGHTMETA", KEY_RIGHTMETA), + ("COMPOSE", KEY_COMPOSE), + ("STOP", KEY_STOP), + ("AGAIN", KEY_AGAIN), + ("PROPS", KEY_PROPS), + ("UNDO", KEY_UNDO), + ("FRONT", KEY_FRONT), + ("COPY", KEY_COPY), + ("OPEN", KEY_OPEN), + ("PASTE", KEY_PASTE), + ("FIND", KEY_FIND), + ("CUT", KEY_CUT), + ("HELP", KEY_HELP), + ("MENU", KEY_MENU), + ("CALC", KEY_CALC), + ("SETUP", KEY_SETUP), + ("SLEEP", KEY_SLEEP), + ("WAKEUP", KEY_WAKEUP), + ("FILE", KEY_FILE), + ("SENDFILE", KEY_SENDFILE), + ("DELETEFILE", KEY_DELETEFILE), + ("XFER", KEY_XFER), + ("PROG1", KEY_PROG1), + ("PROG2", KEY_PROG2), + ("WWW", KEY_WWW), + ("MSDOS", KEY_MSDOS), + ("COFFEE", KEY_COFFEE), + ("SCREENLOCK", KEY_SCREENLOCK), + ("COFFEE", KEY_COFFEE), + ("ROTATE_DISPLAY", KEY_ROTATE_DISPLAY), + ("DIRECTION", KEY_DIRECTION), + ("ROTATE_DISPLAY", KEY_ROTATE_DISPLAY), + ("CYCLEWINDOWS", KEY_CYCLEWINDOWS), + ("MAIL", KEY_MAIL), + ("BOOKMARKS", KEY_BOOKMARKS), + ("COMPUTER", KEY_COMPUTER), + ("BACK", KEY_BACK), + ("FORWARD", KEY_FORWARD), + ("CLOSECD", KEY_CLOSECD), + ("EJECTCD", KEY_EJECTCD), + ("EJECTCLOSECD", KEY_EJECTCLOSECD), + ("NEXTSONG", KEY_NEXTSONG), + ("PLAYPAUSE", KEY_PLAYPAUSE), + ("PREVIOUSSONG", KEY_PREVIOUSSONG), + ("STOPCD", KEY_STOPCD), + ("RECORD", KEY_RECORD), + ("REWIND", KEY_REWIND), + ("PHONE", KEY_PHONE), + ("ISO", KEY_ISO), + ("CONFIG", KEY_CONFIG), + ("HOMEPAGE", KEY_HOMEPAGE), + ("REFRESH", KEY_REFRESH), + ("EXIT", KEY_EXIT), + ("MOVE", KEY_MOVE), + ("EDIT", KEY_EDIT), + ("SCROLLUP", KEY_SCROLLUP), + ("SCROLLDOWN", KEY_SCROLLDOWN), + ("KPLEFTPAREN", KEY_KPLEFTPAREN), + ("KPRIGHTPAREN", KEY_KPRIGHTPAREN), + ("NEW", KEY_NEW), + ("REDO", KEY_REDO), + ("F13", KEY_F13), + ("F14", KEY_F14), + ("F15", KEY_F15), + ("F16", KEY_F16), + ("F17", KEY_F17), + ("F18", KEY_F18), + ("F19", KEY_F19), + ("F20", KEY_F20), + ("F21", KEY_F21), + ("F22", KEY_F22), + ("F23", KEY_F23), + ("F24", KEY_F24), + ("PLAYCD", KEY_PLAYCD), + ("PAUSECD", KEY_PAUSECD), + ("PROG3", KEY_PROG3), + ("PROG4", KEY_PROG4), + ("DASHBOARD", KEY_DASHBOARD), + ("SUSPEND", KEY_SUSPEND), + ("CLOSE", KEY_CLOSE), + ("PLAY", KEY_PLAY), + ("FASTFORWARD", KEY_FASTFORWARD), + ("BASSBOOST", KEY_BASSBOOST), + ("PRINT", KEY_PRINT), + ("HP", KEY_HP), + ("CAMERA", KEY_CAMERA), + ("SOUND", KEY_SOUND), + ("QUESTION", KEY_QUESTION), + ("EMAIL", KEY_EMAIL), + ("CHAT", KEY_CHAT), + ("SEARCH", KEY_SEARCH), + ("CONNECT", KEY_CONNECT), + ("FINANCE", KEY_FINANCE), + ("SPORT", KEY_SPORT), + ("SHOP", KEY_SHOP), + ("ALTERASE", KEY_ALTERASE), + ("CANCEL", KEY_CANCEL), + ("BRIGHTNESSDOWN", KEY_BRIGHTNESSDOWN), + ("BRIGHTNESSUP", KEY_BRIGHTNESSUP), + ("MEDIA", KEY_MEDIA), + ("SWITCHVIDEOMODE", KEY_SWITCHVIDEOMODE), + ("KBDILLUMTOGGLE", KEY_KBDILLUMTOGGLE), + ("KBDILLUMDOWN", KEY_KBDILLUMDOWN), + ("KBDILLUMUP", KEY_KBDILLUMUP), + ("SEND", KEY_SEND), + ("REPLY", KEY_REPLY), + ("FORWARDMAIL", KEY_FORWARDMAIL), + ("SAVE", KEY_SAVE), + ("DOCUMENTS", KEY_DOCUMENTS), + ("BATTERY", KEY_BATTERY), + ("BLUETOOTH", KEY_BLUETOOTH), + ("WLAN", KEY_WLAN), + ("UWB", KEY_UWB), + ("UNKNOWN", KEY_UNKNOWN), + ("VIDEO_NEXT", KEY_VIDEO_NEXT), + ("VIDEO_PREV", KEY_VIDEO_PREV), + ("BRIGHTNESS_CYCLE", KEY_BRIGHTNESS_CYCLE), + ("BRIGHTNESS_AUTO", KEY_BRIGHTNESS_AUTO), + ("BRIGHTNESS_ZERO", KEY_BRIGHTNESS_ZERO), + ("BRIGHTNESS_AUTO", KEY_BRIGHTNESS_AUTO), + ("DISPLAY_OFF", KEY_DISPLAY_OFF), + ("WWAN", KEY_WWAN), + ("WIMAX", KEY_WIMAX), + ("WWAN", KEY_WWAN), + ("RFKILL", KEY_RFKILL), + ("MICMUTE", KEY_MICMUTE), + */ + // below manual shortcuts + ("PSCR", KEY_SYSRQ), + ("SLCK", KEY_SCROLLLOCK), + ("BRK", KEY_PAUSE), + ("GRV", KEY_GRAVE), + ("0", KEY_10), // dumb or named wrong? + ("MINS", KEY_MINUS), + ("EQL", KEY_EQUAL), + ("BSPC", KEY_BACKSPACE), + ("LBRC", KEY_LEFTBRACE), + ("RBRC", KEY_RIGHTBRACE), + ("BSLS", KEY_BACKSLASH), + ("SCLN", KEY_SEMICOLON), + ("QUOT", KEY_APOSTROPHE), + ("ENT", KEY_ENTER), + ("COMM", KEY_COMMA), + ("DOT", KEY_DOT), + ("SLSH", KEY_SLASH), + ("CAPS", KEY_CAPSLOCK), + ("LSFT", KEY_LEFTSHIFT), + ("RSFT", KEY_RIGHTSHIFT), + ("SPC", KEY_SPACE), + ("APP", KEY_COMPOSE), + ("LCTL", KEY_LEFTCTRL), + ("RCTL", KEY_RIGHTCTRL), + ("LALT", KEY_LEFTALT), + ("RALT", KEY_RIGHTALT), + ("LGUI", KEY_LEFTMETA), + ("RGUI", KEY_RIGHTMETA), + ("INS", KEY_INSERT), + ("PGUP", KEY_PAGEUP), + ("PGDN", KEY_PAGEDOWN), + ("DEL", KEY_DELETE), + ("RGHT", KEY_RIGHT), + ("NLCK", KEY_NUMLOCK), + ("PSLS", KEY_KPSLASH), + ("PAST", KEY_KPASTERISK), + ("PMNS", KEY_KPMINUS), + ("P7", KEY_KP7), + ("P8", KEY_KP8), + ("P9", KEY_KP9), + ("P4", KEY_KP4), + ("P5", KEY_KP5), + ("P6", KEY_KP6), + ("PPLS", KEY_KPPLUS), + ("P1", KEY_KP1), + ("P2", KEY_KP2), + ("P3", KEY_KP3), + ("P0", KEY_KP0), + ("PDOT", KEY_KPDOT), + ("PENT", KEY_KPENTER), + ].iter().cloned().map(|(m, v)| (m, v)).collect() +} diff --git a/src/macos/mod.rs b/src/macos/mod.rs new file mode 100644 index 0000000..00fe1bd --- /dev/null +++ b/src/macos/mod.rs @@ -0,0 +1,414 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(improper_ctypes)] + +use crate::*; +use std::env; +use std::process::exit; + +use getopts::Options; +use std::fs::File; + +pub mod codes; +use codes::*; + +use core_graphics::event::CGKeyCode; +use core_graphics::event::*; +use core_graphics::event_source::*; + +use core_graphics::event::{CGEventTapLocation, CGEventType}; + +type MacOSKeyMaps = KeyMaps>; +type CallbackPointer = (MacOSKeyMaps, CGEventSource); + +/* +// possible types for event_source +Private = -1, +CombinedSessionState = 0, +HIDSystemState = 1, + +// possible types for tapLocation +HID, +Session, +AnnotatedSession, + */ + +// macOS seems to require this, or it ignores shift, WHY? +const delay: std::time::Duration = std::time::Duration::from_millis(20); +const tapLocation: CGEventTapLocation = CGEventTapLocation::Session; +// this is only used if tapLocation is HID, to prevent us from mapping our own key inputs +const uniqueHIDUserData: i64 = 45; + +impl KeyEvent for CGEvent { + fn code(&self) -> CGKeyCode { + self.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE) as CGKeyCode + } + + fn value(&self) -> KeyState { + let event_type = self.get_type(); + match event_type { + CGEventType::FlagsChanged => { + let flags = self.get_flags().bits(); // todo: fix cast? + let mask = match self.code() { + KEY_LEFTCTRL => NX_DEVICELCTLKEYMASK, + KEY_RIGHTCTRL => NX_DEVICERCTLKEYMASK, + KEY_LEFTSHIFT => NX_DEVICELSHIFTKEYMASK, + KEY_RIGHTSHIFT => NX_DEVICERSHIFTKEYMASK, + KEY_LEFTMETA => NX_DEVICELCMDKEYMASK, + KEY_RIGHTMETA => NX_DEVICERCMDKEYMASK, + KEY_LEFTALT => NX_DEVICELALTKEYMASK, + KEY_RIGHTALT => NX_DEVICERALTKEYMASK, + KEY_CAPSLOCK => NX_DEVICECAPSLOCKMASK, + _ => panic!("unhandled key: {}", self.code()), + }; + if (flags & mask) != 0 { KeyState::DOWN } else { KeyState::UP } + }, + CGEventType::KeyDown => KeyState::DOWN, + CGEventType::KeyUp => KeyState::UP, + CGEventType::TapDisabledByTimeout => { + println!("Quartz event tap disabled because of timeout; attempting to reregister."); + todo!("implement register listener"); + //register_listener(channel); + //KeyState::OTHER + }, + _ => { + println!("Received unknown EventType: {:?}", event_type); + KeyState::OTHER + }, + } + } +} + +impl Keyboard> for CGEventSource { + fn send(&self, event: &mut CGEvent) -> Result> { + //println!("send orig: {}", event.code()); + + //Ok(Some(event.event)) + + self.send_mod_code_value(event.code(), event.value() == KeyState::UP, event) + } + + fn send_mod_code(&self, code: CGKeyCode, event: &mut CGEvent) -> Result> { + // event.value should only ever be UP/DOWN when this method is called + //println!("send_mod_code orig: {} code: {}", event.code(), code); + + //unsafe { CGEventSetIntegerValueField(event.event, kCGKeyboardEventKeycode, code as i64) }; + //Ok(Some(event.event)) + + self.send_mod_code_value(code, event.value() == KeyState::UP, event) + } + + fn send_mod_code_value(&self, code: CGKeyCode, up_not_down: bool, _event: &mut CGEvent) -> Result> { + //println!("send_mod_code_value orig: {} code: {}, up_not_down: {}", event.code(), code, up_not_down); + //return Ok(None); + + let event = + CGEvent::new_keyboard_event(self.clone(), code, !up_not_down) + .expect("Failed creating event"); + match tapLocation { + CGEventTapLocation::HID => event.set_integer_value_field(EventField::EVENT_SOURCE_USER_DATA, uniqueHIDUserData), + _ => {} + }; + event.post(tapLocation); + + Ok(None) + } + + fn synchronize(&self) -> Result> { + std::thread::sleep(delay); + Ok(None) + } + + fn left_shift_code(&self) -> CGKeyCode { + KEY_LEFTSHIFT + } + + fn right_shift_code(&self) -> CGKeyCode { + KEY_RIGHTSHIFT + } + + fn caps_lock_code(&self) -> CGKeyCode { + KEY_CAPSLOCK + } + + fn block_key(&self) -> Result> { + Ok(None) + } +} + +pub fn main_res() -> Result<()> { + let config = parse_args(); + //println!("Config: {:?}", config); + + let key_map = key_map(); + //println!("key_map: {:?}", key_map); + + let key_maps = MacOSKeyMaps::from_cfg(&key_map, &config.config_file); + //println!("key_maps: {}", key_maps); + let callback_pointer: CallbackPointer = (key_maps, CGEventSource::new(CGEventSourceStateID::Private).expect("Failed creating event source")); + + let mask = CGEventMaskBit(CGEventType::KeyDown) + | CGEventMaskBit(CGEventType::KeyUp) + | CGEventMaskBit(CGEventType::FlagsChanged) + ; + + unsafe { + let options = 0; + + // Create the event tap + let event_tap = CGEventTapCreate( + kCGSessionEventTap, + kCGHeadInsertEventTap, + options, + mask, + callback, + &callback_pointer, + ); + if event_tap.is_null() { + panic!("Unable to create event tap. Please make sure you have the correct permissions"); + } + println!("Created event tap..."); + + let allocator = kCFAllocatorDefault; + let current_event_loop = CFRunLoopGetCurrent(); + let mode = kCFRunLoopCommonModes; + + // Create Run Loop Source + let run_loop_source = CFMachPortCreateRunLoopSource(allocator, event_tap, 0); + + // Add Run Loop Source to the current event loop + CFRunLoopAddSource(current_event_loop, run_loop_source, mode); + + // Enable the tap + CGEventTapEnable(event_tap, true); + + CFRunLoopRun(); + } + + Ok(()) +} + +#[derive(Debug)] +struct Config { + config_file: String +} + +impl Config { + fn new(config_file: String) -> Self { + Config { config_file: config_file } + } +} + +fn get_env_push(key: &str, to_push: &str, vec: &mut Vec) { + if let Some(var) = env::var_os(key) { + if let Ok(str) = var.into_string() { + let mut str = str.to_owned(); + str.push_str(to_push); + vec.push(str); + } + } +} + +fn parse_args() -> Config { + fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {} [options] [keymap.toml]", program); + println!("{}", opts.usage(&brief)); + } + + let args: Vec<_> = env::args().collect(); + + let mut default_configs = Vec::new(); + get_env_push("USERPROFILE", "\\keymap.toml", &mut default_configs); + get_env_push("APPDATA", "\\keymap.toml", &mut default_configs); + + default_configs.push("keymap.toml".to_string()); + + let c_msg = format!("specify the keymap config file to use (default in order: {:?})", default_configs); + + let mut opts = Options::new(); + opts.optflag("h", "help", "prints this help message"); + opts.optflag("v", "version", "prints the version"); + opts.optopt("c", "config", &c_msg, "FILE"); + + let matches = opts.parse(&args[1..]); + if matches.is_err() { + print_usage(&args[0], opts); + exit(1); + } + let matches = matches.unwrap(); + if matches.opt_present("h") { + print_usage(&args[0], opts); + exit(0); + } + + if matches.opt_present("v") { + println!("rusty-keys {}", VERSION); + exit(0); + } + + let config_file = matches.opt_str("c").unwrap_or_else(|| { + let remaining_args = matches.free; + if remaining_args.len() > 0 { + remaining_args[0].clone() + } else { + for keymap in default_configs.drain(..) { + if File::open(&keymap).is_ok() { + return keymap; + } + } + println!("Error: no keymap.toml found..."); + print_usage(&args[0], opts); + exit(1); + } + }); + + Config::new(config_file) +} + +use libc; + +// Opaque Pointer Types +type Pointer = *mut libc::c_void; +type CFMachPortRef = Pointer; + +// Integer Types +type CGEventMask = u64; +type CGEventTapOptions = u32; +type CGEventTapPlacement = u32; + +// Callback Type +type CGEventTapCallBack = extern "C" fn(Pointer, CGEventType, CGEvent, &mut CallbackPointer) -> CGEvent; + +// Constants +const kCGSessionEventTap: CGEventTapLocation = CGEventTapLocation::HID; +const kCGHeadInsertEventTap: CGEventTapPlacement = 0; + + // Link to ApplicationServices/ApplicationServices.h and Carbon/Carbon.h + #[link(name = "ApplicationServices", kind = "framework")] + #[link(name = "Carbon", kind = "framework")] + extern { + + /// Pass through to the default loop modes + pub static kCFRunLoopCommonModes: Pointer; + + /// Pass through to the default allocator + pub static kCFAllocatorDefault: Pointer; + + /// Run the current threads loop in default mode + pub fn CFRunLoopRun(); + + /// Obtain the current threads loop + pub fn CFRunLoopGetCurrent() -> Pointer; + + /// Create an event tap + /// + /// # Arguments + /// + /// * `place` - The location of the new event tap. Pass one of + /// the constants listed in Event Tap Locations. Only + /// processes running as the root user may locate an + /// event tap at the point where HID events enter the + /// window server; for other users, this function + /// returns NULL. + /// + /// * `options` - The placement of the new event tap in the + /// list of active event taps. Pass one of the + /// constants listed in Event Tap Placement. + /// + /// * `eventsOfInterest` - A constant that specifies whether + /// the new event tap is a passive listener or an + /// active filter. + /// + /// * `callback` - A bit mask that specifies the set of events + /// to be observed. For a list of possible events, + /// see Event Types. For information on how to + /// specify the mask, see CGEventMask. If the event + /// tap is not permitted to monitor one or more of + /// the events specified in the eventsOfInterest + /// parameter, then the appropriate bits in the mask + /// are cleared. If that action results in an empty + /// mask, this function returns NULL. callback + /// + /// * `refcon` - An event tap callback function that you + /// provide. Your callback function is invoked from + /// the run loop to which the event tap is added as a + /// source. The thread safety of the callback is + /// defined by the run loop’s environment. To learn + /// more about event tap callbacks, see + /// CGEventTapCallBack. refcon + /// + /// * `channel` - A pointer to user-defined data. This pointer + /// is passed into the callback function specified in + /// the callback parameter. Here we use it as a mpsc + /// channel. + pub fn CGEventTapCreate( + tap: CGEventTapLocation, + place: CGEventTapPlacement, + options: CGEventTapOptions, + eventsOfInterest: CGEventMask, + callback: CGEventTapCallBack, + channel: &CallbackPointer, + ) -> CFMachPortRef; + + /// Creates a CFRunLoopSource object for a CFMachPort + /// object. + /// + /// The run loop source is not automatically added to + /// a run loop. To add the source to a run loop, use + /// CFRunLoopAddSource + pub fn CFMachPortCreateRunLoopSource( + allocator: Pointer, + port: CFMachPortRef, + order: libc::c_int, + ) -> Pointer; + + /// Adds a CFRunLoopSource object to a run loop mode. + pub fn CFRunLoopAddSource( + run_loop: Pointer, + run_loop_source: Pointer, + mode: Pointer, + ); + + pub fn CGEventTapEnable(port: CFMachPortRef, enable: bool); + } + +const NX_DEVICELCTLKEYMASK: u64 = 0x00000001; +const NX_DEVICERCTLKEYMASK: u64 = 0x00002000; +const NX_DEVICELSHIFTKEYMASK: u64 = 0x00000002; +const NX_DEVICERSHIFTKEYMASK: u64 = 0x00000004; +const NX_DEVICELCMDKEYMASK: u64 = 0x00000008; +const NX_DEVICERCMDKEYMASK: u64 = 0x00000010; +const NX_DEVICELALTKEYMASK: u64 = 0x00000020; +const NX_DEVICERALTKEYMASK: u64 = 0x00000040; + +const NX_DEVICECAPSLOCKMASK: u64 = 1 << 16; + +/// This callback will be registered to be invoked from the run loop +/// to which the event tap is added as a source. +#[no_mangle] +#[allow(unused_variables, improper_ctypes_definitions)] +pub extern fn callback(proxy: Pointer, event_type: CGEventType, mut event: CGEvent, callback_pointer: &mut CallbackPointer) -> CGEvent { + let (key_maps, event_source) = callback_pointer; + match tapLocation { + CGEventTapLocation::HID => { + let user_data = event.get_integer_value_field(EventField::EVENT_SOURCE_USER_DATA); + if user_data == uniqueHIDUserData { + return event; + } + } + _ => {} + }; + + key_maps.send_event(&mut event, &event_source).expect("macos shouldn't error...") + .unwrap_or_else(|| { + event.set_type(CGEventType::Null); + event + }) // None means return NULL + } + + /// Redefine macro for bitshifting from header as function here + pub fn CGEventMaskBit(eventType: CGEventType) -> CGEventMask { + 1 << (eventType as u32) + } + diff --git a/src/main.rs b/src/main.rs index 4c97f0b..e073df4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -#[cfg(any(target_os = "windows", target_os = "linux"))] +#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] fn main() { let ret = rusty_keys::main_res(); if let Err(e) = ret { @@ -7,7 +7,7 @@ fn main() { } } -#[cfg(not(any(target_os = "windows", target_os = "linux")))] +#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] fn main() { panic!("sorry no main impl for this platform"); }