Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: improve un/register_all performance on Linux #45

Merged
merged 2 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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