diff --git a/.changes/un-register-all-perf-linux.md b/.changes/un-register-all-perf-linux.md new file mode 100644 index 0000000..90512d5 --- /dev/null +++ b/.changes/un-register-all-perf-linux.md @@ -0,0 +1,5 @@ +--- +"global-hotkey": "patch" +--- + +On Linux, improve the performance of `GlobalHotKeyManager::register_all` and `GlobalHotKeyManager::unregister_all` to 2711x faster. diff --git a/src/lib.rs b/src/lib.rs index e406503..dbee080 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -150,16 +150,12 @@ impl GlobalHotKeyManager { } pub fn register_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> { - for hotkey in hotkeys { - self.register(*hotkey)?; - } + self.platform_impl.register_all(hotkeys)?; Ok(()) } pub fn unregister_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> { - for hotkey in hotkeys { - self.unregister(*hotkey)?; - } + self.platform_impl.unregister_all(hotkeys)?; Ok(()) } } diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index c291423..63b79f0 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -131,10 +131,16 @@ impl GlobalHotKeyManager { Ok(()) } - pub fn unregister_all(&self) -> crate::Result<()> { - let hotkeys = self.hotkeys.lock().unwrap().clone(); - for (_, hotkeywrapper) in hotkeys { - self.unregister(hotkeywrapper.hotkey)?; + pub fn register_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> { + for hotkey in hotkeys { + self.register(*hotkey)?; + } + Ok(()) + } + + pub fn unregister_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> { + for hotkey in hotkeys { + self.unregister(*hotkey)?; } Ok(()) } @@ -154,7 +160,10 @@ impl GlobalHotKeyManager { impl Drop for GlobalHotKeyManager { fn drop(&mut self) { - let _ = self.unregister_all(); + let hotkeys = self.hotkeys.lock().unwrap().clone(); + for (_, hotkeywrapper) in hotkeys { + let _ = self.unregister(hotkeywrapper.hotkey); + } unsafe { RemoveEventHandler(self.event_handler_ptr); } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index c4211f1..9f514fb 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -116,6 +116,20 @@ impl GlobalHotKeyManager { } Ok(()) } + + pub fn register_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> { + for hotkey in hotkeys { + self.register(*hotkey)?; + } + Ok(()) + } + + pub fn unregister_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> { + for hotkey in hotkeys { + self.unregister(*hotkey)?; + } + Ok(()) + } } unsafe extern "system" fn global_hotkey_proc( hwnd: HWND, diff --git a/src/platform_impl/x11/mod.rs b/src/platform_impl/x11/mod.rs index 62470fa..b176707 100644 --- a/src/platform_impl/x11/mod.rs +++ b/src/platform_impl/x11/mod.rs @@ -9,13 +9,18 @@ use std::{ use crossbeam_channel::{unbounded, Receiver, Sender}; use keyboard_types::{Code, Modifiers}; -use x11_dl::{keysym, xlib}; +use x11_dl::{ + keysym, + xlib::{self, Xlib, _XDisplay}, +}; use crate::{hotkey::HotKey, GlobalHotKeyEvent}; enum ThreadMessage { RegisterHotKey(HotKey, Sender>), + RegisterHotKeys(Vec, Sender>), UnRegisterHotKey(HotKey, Sender>), + UnRegisterHotKeys(Vec, Sender>), DropThread, } @@ -55,6 +60,32 @@ impl GlobalHotKeyManager { Ok(()) } + + pub fn register_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> { + let (tx, rx) = crossbeam_channel::bounded(1); + let _ = self + .thread_tx + .send(ThreadMessage::RegisterHotKeys(hotkeys.to_vec(), tx)); + + if let Ok(result) = rx.recv() { + result?; + } + + Ok(()) + } + + pub fn unregister_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> { + let (tx, rx) = crossbeam_channel::bounded(1); + let _ = self + .thread_tx + .send(ThreadMessage::UnRegisterHotKeys(hotkeys.to_vec(), tx)); + + if let Ok(result) = rx.recv() { + result?; + } + + Ok(()) + } } impl Drop for GlobalHotKeyManager { @@ -63,6 +94,96 @@ impl Drop for GlobalHotKeyManager { } } +// XGrabKey works only with the exact state (modifiers) +// and since X11 considers NumLock, ScrollLock and CapsLock a modifier when it is ON, +// we also need to register our shortcut combined with these extra modifiers as well +const IGNORED_MODS: [u32; 4] = [ + 0, // modifier only + xlib::Mod2Mask, // NumLock + xlib::LockMask, // CapsLock + xlib::Mod2Mask | xlib::LockMask, +]; + +#[inline] +fn register_hotkey( + xlib: &Xlib, + display: *mut _XDisplay, + root: u64, + hotkeys: &mut HashMap<(u32, u32), (u32, bool)>, + hotkey: HotKey, +) -> crate::Result<()> { + let (modifiers, key) = ( + modifiers_to_x11_mods(hotkey.mods), + keycode_to_x11_scancode(hotkey.key), + ); + + if let Some(key) = key { + let keycode = unsafe { (xlib.XKeysymToKeycode)(display, key as _) }; + + for m in IGNORED_MODS { + let result = unsafe { + (xlib.XGrabKey)( + display, + keycode as _, + modifiers | m, + root, + 0, + xlib::GrabModeAsync, + xlib::GrabModeAsync, + ) + }; + + if result == xlib::BadAccess as _ { + for m in IGNORED_MODS { + unsafe { (xlib.XUngrabKey)(display, keycode as _, modifiers | m, root) }; + } + + return Err(crate::Error::AlreadyRegistered(hotkey)); + } + } + + if let Entry::Vacant(e) = hotkeys.entry((modifiers, keycode as _)) { + e.insert((hotkey.id(), false)); + } else { + return Err(crate::Error::AlreadyRegistered(hotkey)); + } + } else { + return Err(crate::Error::FailedToRegister(format!( + "Unable to register accelerator (unknown scancode for this key: {}).", + hotkey.key + ))); + } + + Ok(()) +} + +#[inline] +fn unregister_hotkey( + xlib: &Xlib, + display: *mut _XDisplay, + root: u64, + hotkeys: &mut HashMap<(u32, u32), (u32, bool)>, + hotkey: HotKey, +) -> crate::Result<()> { + let (modifiers, key) = ( + modifiers_to_x11_mods(hotkey.mods), + keycode_to_x11_scancode(hotkey.key), + ); + + if let Some(key) = key { + let keycode = unsafe { (xlib.XKeysymToKeycode)(display, key as _) }; + + for m in IGNORED_MODS { + unsafe { (xlib.XUngrabKey)(display, keycode as _, modifiers | m, root) }; + } + + hotkeys.remove(&(modifiers, keycode as _)); + Ok(()) + } else { + Err(crate::Error::FailedToUnRegister(hotkey)) + } +} + fn events_processor(thread_rx: Receiver) { // mods, key id, repeating let mut hotkeys = HashMap::<(u32, u32), (u32, bool)>::new(); @@ -115,98 +236,45 @@ fn events_processor(thread_rx: Receiver) { } } - // XGrabKey works only with the exact state (modifiers) - // and since X11 considers NumLock, ScrollLock and CapsLock a modifier when it is ON, - // we also need to register our shortcut combined with these extra modifiers as well - const IGNORED_MODS: [u32; 4] = [ - 0, // modifier only - xlib::Mod2Mask, // NumLock - xlib::LockMask, // CapsLock - xlib::Mod2Mask | xlib::LockMask, - ]; - if let Ok(msg) = thread_rx.try_recv() { match msg { ThreadMessage::RegisterHotKey(hotkey, tx) => { - let (modifiers, key) = ( - modifiers_to_x11_mods(hotkey.mods), - keycode_to_x11_scancode(hotkey.key), - ); - - if let Some(key) = key { - let keycode = (xlib.XKeysymToKeycode)(display, key as _); - - let mut errored = false; - - for m in IGNORED_MODS { - let result = (xlib.XGrabKey)( - display, - keycode as _, - modifiers | m, - root, - 0, - xlib::GrabModeAsync, - xlib::GrabModeAsync, - ); - - if result == xlib::BadAccess as _ { - errored = true; - - let _ = - tx.send(Err(crate::Error::AlreadyRegistered(hotkey))); - - for m in IGNORED_MODS { - (xlib.XUngrabKey)( - display, - keycode as _, - modifiers | m, - root, - ); - } - - break; - } - } - - if !errored { - if let Entry::Vacant(e) = - hotkeys.entry((modifiers, keycode as _)) - { - e.insert((hotkey.id(), false)); - } else { - let _ = - tx.send(Err(crate::Error::AlreadyRegistered(hotkey))); - } - - let _ = tx.send(Ok(())); + let _ = tx.send(register_hotkey( + &xlib, + display, + root, + &mut hotkeys, + hotkey, + )); + } + ThreadMessage::RegisterHotKeys(keys, tx) => { + for hotkey in keys { + if let Err(e) = + register_hotkey(&xlib, display, root, &mut hotkeys, hotkey) + { + let _ = tx.send(Err(e)); } - } else { - let _ = tx - .send(Err(crate::Error::FailedToRegister(format!( - "Unable to register accelerator (unknown scancode for this key: {}).", - hotkey.key - )))); } + let _ = tx.send(Ok(())); } ThreadMessage::UnRegisterHotKey(hotkey, tx) => { - let (modifiers, key) = ( - modifiers_to_x11_mods(hotkey.mods), - keycode_to_x11_scancode(hotkey.key), - ); - - if let Some(key) = key { - let keycode = (xlib.XKeysymToKeycode)(display, key as _); - - for m in IGNORED_MODS { - (xlib.XUngrabKey)(display, keycode as _, modifiers | m, root); + let _ = tx.send(register_hotkey( + &xlib, + display, + root, + &mut hotkeys, + hotkey, + )); + } + ThreadMessage::UnRegisterHotKeys(keys, tx) => { + for hotkey in keys { + if let Err(e) = + unregister_hotkey(&xlib, display, root, &mut hotkeys, hotkey) + { + let _ = tx.send(Err(e)); } - - hotkeys.remove(&(modifiers, keycode as _)); - - let _ = tx.send(Ok(())); - } else { - let _ = tx.send(Err(crate::Error::FailedToUnRegister(hotkey))); } + let _ = tx.send(Ok(())); } ThreadMessage::DropThread => { (xlib.XCloseDisplay)(display);