Skip to content

Commit

Permalink
perf: improve un/register_all performance on Linux (#45)
Browse files Browse the repository at this point in the history
* perf: improve `un/register_all` performance on Linux

closes #33

pr: 464.645µs
dev branch: 1.259750603s

by passing the hotkeys in one go and registering all of them at once, we get 2711x faster

* change file
  • Loading branch information
amrbashir authored Nov 24, 2023
1 parent a8345a9 commit 860a449
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 95 deletions.
5 changes: 5 additions & 0 deletions .changes/un-register-all-perf-linux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"global-hotkey": "patch"
---

On Linux, improve the performance of `GlobalHotKeyManager::register_all` and `GlobalHotKeyManager::unregister_all` to 2711x faster.
8 changes: 2 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
Expand Down
19 changes: 14 additions & 5 deletions src/platform_impl/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand All @@ -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);
}
Expand Down
14 changes: 14 additions & 0 deletions src/platform_impl/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
236 changes: 152 additions & 84 deletions src/platform_impl/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<crate::Result<()>>),
RegisterHotKeys(Vec<HotKey>, Sender<crate::Result<()>>),
UnRegisterHotKey(HotKey, Sender<crate::Result<()>>),
UnRegisterHotKeys(Vec<HotKey>, Sender<crate::Result<()>>),
DropThread,
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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<ThreadMessage>) {
// mods, key id, repeating
let mut hotkeys = HashMap::<(u32, u32), (u32, bool)>::new();
Expand Down Expand Up @@ -115,98 +236,45 @@ fn events_processor(thread_rx: Receiver<ThreadMessage>) {
}
}

// 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);
Expand Down

0 comments on commit 860a449

Please sign in to comment.