mirror of
https://github.com/tauri-apps/global-hotkey.git
synced 2026-01-31 00:45:22 +01:00
Add x11 implementation
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -712,6 +712,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"windows-sys 0.42.0",
|
||||
"winit",
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -27,6 +27,8 @@ features = [
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
]
|
||||
|
||||
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||
x11-dl = "2.20"
|
||||
|
||||
[dev-dependencies]
|
||||
winit = "0.27"
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::hotkey::HotKey;
|
||||
|
||||
/// Errors returned by tray-icon.
|
||||
#[non_exhaustive]
|
||||
#[derive(Error, Debug)]
|
||||
@@ -16,6 +18,8 @@ pub enum Error {
|
||||
FailedToRegister(String),
|
||||
#[error("Failed to unregister this hotkey")]
|
||||
FailedToUnRegister,
|
||||
#[error("HotKey already registerd: {0:?}")]
|
||||
AlreadyRegistered(HotKey),
|
||||
}
|
||||
|
||||
/// Convenient type alias of Result type for tray-icon.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#[path = "windows/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[path = "gtk/mod.rs"]
|
||||
#[path = "x11/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path = "macos/mod.rs"]
|
||||
|
||||
@@ -119,8 +119,8 @@ impl GlobalHotKeyManager {
|
||||
Some(vk_code) => {
|
||||
let result =
|
||||
unsafe { RegisterHotKey(self.hwnd, hotkey.id() as _, mods, vk_code as _) };
|
||||
if result == 0 {
|
||||
return Err(crate::Error::OsError(std::io::Error::last_os_error()));
|
||||
if result == 1 {
|
||||
return Err(crate::Error::AlreadyRegistered);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
||||
339
src/platform_impl/x11/mod.rs
Normal file
339
src/platform_impl/x11/mod.rs
Normal file
@@ -0,0 +1,339 @@
|
||||
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{collections::HashMap, ptr};
|
||||
|
||||
use crossbeam_channel::{unbounded, Sender};
|
||||
use keyboard_types::{Code, Modifiers};
|
||||
use x11_dl::{keysym, xlib};
|
||||
|
||||
use crate::{hotkey::HotKey, GLOBAL_HOTKEY_CHANNEL};
|
||||
|
||||
enum ThreadMessage {
|
||||
RegisterHotKey(HotKey, Sender<crate::Result<()>>),
|
||||
UnRegisterHotKey(HotKey, Sender<crate::Result<()>>),
|
||||
DropThread,
|
||||
}
|
||||
|
||||
pub struct GlobalHotKeyManager {
|
||||
thread_tx: Sender<ThreadMessage>,
|
||||
}
|
||||
|
||||
impl GlobalHotKeyManager {
|
||||
pub fn new() -> crate::Result<Self> {
|
||||
let (thread_tx, thread_rx) = unbounded();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// mods, key id, repeating
|
||||
let mut hotkeys = HashMap::<(u32, u32), (u32, bool)>::new();
|
||||
let xlib = xlib::Xlib::open().unwrap();
|
||||
unsafe {
|
||||
let display = (xlib.XOpenDisplay)(ptr::null());
|
||||
let root = (xlib.XDefaultRootWindow)(display);
|
||||
|
||||
// Only trigger key release at end of repeated keys
|
||||
let mut supported_rtrn: i32 = 0;
|
||||
(xlib.XkbSetDetectableAutoRepeat)(display, 1, &mut supported_rtrn);
|
||||
|
||||
(xlib.XSelectInput)(display, root, xlib::KeyPressMask);
|
||||
let mut event: xlib::XEvent = std::mem::zeroed();
|
||||
|
||||
loop {
|
||||
if (xlib.XPending)(display) > 0 {
|
||||
(xlib.XNextEvent)(display, &mut event);
|
||||
match event.get_type() {
|
||||
e if matches!(e, xlib::KeyPress | xlib::KeyRelease) => {
|
||||
let keycode = event.key.keycode;
|
||||
// X11 sends masks for Lock keys also and we only care about the 4 below
|
||||
let modifiers = event.key.state
|
||||
& (xlib::ControlMask
|
||||
| xlib::ShiftMask
|
||||
| xlib::Mod4Mask
|
||||
| xlib::Mod1Mask);
|
||||
|
||||
if let Some((id, repeating)) =
|
||||
hotkeys.get_mut(&(modifiers, keycode))
|
||||
{
|
||||
match (e, *repeating) {
|
||||
(xlib::KeyPress, false) => {
|
||||
let _ = &GLOBAL_HOTKEY_CHANNEL
|
||||
.0
|
||||
.send(crate::GlobalHotKeyEvent(*id));
|
||||
*repeating = true;
|
||||
}
|
||||
(xlib::KeyRelease, true) => {
|
||||
*repeating = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
hotkeys.insert(
|
||||
(modifiers, keycode as _),
|
||||
(hotkey.id(), false),
|
||||
);
|
||||
|
||||
let _ = tx.send(Ok(()));
|
||||
}
|
||||
} else {
|
||||
let _ = tx
|
||||
.send(Err(crate::Error::FailedToRegister(format!(
|
||||
"Unable to register accelerator (unknown VKCode for this char: {}).",
|
||||
hotkey.key
|
||||
))));
|
||||
}
|
||||
}
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
hotkeys.remove(&(modifiers, keycode as _));
|
||||
|
||||
let _ = tx.send(Ok(()));
|
||||
} else {
|
||||
// send back error
|
||||
}
|
||||
}
|
||||
ThreadMessage::DropThread => {
|
||||
(xlib.XCloseDisplay)(display);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Ok(Self { thread_tx })
|
||||
}
|
||||
|
||||
pub fn register(&self, hotkey: HotKey) -> crate::Result<()> {
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
let _ = self
|
||||
.thread_tx
|
||||
.send(ThreadMessage::RegisterHotKey(hotkey, tx));
|
||||
|
||||
if let Ok(result) = rx.recv() {
|
||||
result?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unregister(&self, hotkey: HotKey) -> crate::Result<()> {
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
let _ = self
|
||||
.thread_tx
|
||||
.send(ThreadMessage::UnRegisterHotKey(hotkey, tx));
|
||||
|
||||
if let Ok(result) = rx.recv() {
|
||||
result?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GlobalHotKeyManager {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.thread_tx.send(ThreadMessage::DropThread);
|
||||
}
|
||||
}
|
||||
|
||||
fn keycode_to_x11_scancode(key: Code) -> Option<u32> {
|
||||
Some(match key {
|
||||
Code::KeyA => 'A' as u32,
|
||||
Code::KeyB => 'B' as u32,
|
||||
Code::KeyC => 'C' as u32,
|
||||
Code::KeyD => 'D' as u32,
|
||||
Code::KeyE => 'E' as u32,
|
||||
Code::KeyF => 'F' as u32,
|
||||
Code::KeyG => 'G' as u32,
|
||||
Code::KeyH => 'H' as u32,
|
||||
Code::KeyI => 'I' as u32,
|
||||
Code::KeyJ => 'J' as u32,
|
||||
Code::KeyK => 'K' as u32,
|
||||
Code::KeyL => 'L' as u32,
|
||||
Code::KeyM => 'M' as u32,
|
||||
Code::KeyN => 'N' as u32,
|
||||
Code::KeyO => 'O' as u32,
|
||||
Code::KeyP => 'P' as u32,
|
||||
Code::KeyQ => 'Q' as u32,
|
||||
Code::KeyR => 'R' as u32,
|
||||
Code::KeyS => 'S' as u32,
|
||||
Code::KeyT => 'T' as u32,
|
||||
Code::KeyU => 'U' as u32,
|
||||
Code::KeyV => 'V' as u32,
|
||||
Code::KeyW => 'W' as u32,
|
||||
Code::KeyX => 'X' as u32,
|
||||
Code::KeyY => 'Y' as u32,
|
||||
Code::KeyZ => 'Z' as u32,
|
||||
Code::Backslash => keysym::XK_backslash,
|
||||
Code::BracketLeft => keysym::XK_bracketleft,
|
||||
Code::BracketRight => keysym::XK_bracketright,
|
||||
Code::Comma => keysym::XK_comma,
|
||||
Code::Digit0 => '0' as u32,
|
||||
Code::Digit1 => '1' as u32,
|
||||
Code::Digit2 => '2' as u32,
|
||||
Code::Digit3 => '3' as u32,
|
||||
Code::Digit4 => '4' as u32,
|
||||
Code::Digit5 => '5' as u32,
|
||||
Code::Digit6 => '6' as u32,
|
||||
Code::Digit7 => '7' as u32,
|
||||
Code::Digit8 => '8' as u32,
|
||||
Code::Digit9 => '9' as u32,
|
||||
Code::Equal => keysym::XK_equal,
|
||||
Code::IntlBackslash => keysym::XK_backslash,
|
||||
Code::Minus => keysym::XK_minus,
|
||||
Code::Period => keysym::XK_period,
|
||||
Code::Quote => keysym::XK_leftsinglequotemark,
|
||||
Code::Semicolon => keysym::XK_semicolon,
|
||||
Code::Slash => keysym::XK_slash,
|
||||
Code::Backspace => keysym::XK_BackSpace,
|
||||
Code::CapsLock => keysym::XK_Caps_Lock,
|
||||
Code::Enter => keysym::XK_Return,
|
||||
Code::Space => keysym::XK_space,
|
||||
Code::Tab => keysym::XK_Tab,
|
||||
Code::Delete => keysym::XK_Delete,
|
||||
Code::End => keysym::XK_End,
|
||||
Code::Home => keysym::XK_Home,
|
||||
Code::Insert => keysym::XK_Insert,
|
||||
Code::PageDown => keysym::XK_Page_Down,
|
||||
Code::PageUp => keysym::XK_Page_Up,
|
||||
Code::ArrowDown => keysym::XK_Down,
|
||||
Code::ArrowLeft => keysym::XK_Left,
|
||||
Code::ArrowRight => keysym::XK_Right,
|
||||
Code::ArrowUp => keysym::XK_Up,
|
||||
Code::Numpad0 => keysym::XK_KP_0,
|
||||
Code::Numpad1 => keysym::XK_KP_1,
|
||||
Code::Numpad2 => keysym::XK_KP_2,
|
||||
Code::Numpad3 => keysym::XK_KP_3,
|
||||
Code::Numpad4 => keysym::XK_KP_4,
|
||||
Code::Numpad5 => keysym::XK_KP_5,
|
||||
Code::Numpad6 => keysym::XK_KP_6,
|
||||
Code::Numpad7 => keysym::XK_KP_7,
|
||||
Code::Numpad8 => keysym::XK_KP_8,
|
||||
Code::Numpad9 => keysym::XK_KP_9,
|
||||
Code::NumpadAdd => keysym::XK_KP_Add,
|
||||
Code::NumpadDecimal => keysym::XK_KP_Decimal,
|
||||
Code::NumpadDivide => keysym::XK_KP_Divide,
|
||||
Code::NumpadMultiply => keysym::XK_KP_Multiply,
|
||||
Code::NumpadSubtract => keysym::XK_KP_Subtract,
|
||||
Code::Escape => keysym::XK_Escape,
|
||||
Code::PrintScreen => keysym::XK_Print,
|
||||
Code::ScrollLock => keysym::XK_Scroll_Lock,
|
||||
Code::Pause => keysym::XF86XK_AudioPlay,
|
||||
Code::MediaStop => keysym::XF86XK_AudioStop,
|
||||
Code::MediaTrackNext => keysym::XF86XK_AudioNext,
|
||||
Code::MediaTrackPrevious => keysym::XF86XK_AudioPrev,
|
||||
Code::AudioVolumeDown => keysym::XF86XK_AudioLowerVolume,
|
||||
Code::AudioVolumeMute => keysym::XF86XK_AudioMute,
|
||||
Code::AudioVolumeUp => keysym::XF86XK_AudioRaiseVolume,
|
||||
Code::F1 => keysym::XK_F1,
|
||||
Code::F2 => keysym::XK_F2,
|
||||
Code::F3 => keysym::XK_F3,
|
||||
Code::F4 => keysym::XK_F4,
|
||||
Code::F5 => keysym::XK_F5,
|
||||
Code::F6 => keysym::XK_F6,
|
||||
Code::F7 => keysym::XK_F7,
|
||||
Code::F8 => keysym::XK_F8,
|
||||
Code::F9 => keysym::XK_F9,
|
||||
Code::F10 => keysym::XK_F10,
|
||||
Code::F11 => keysym::XK_F11,
|
||||
Code::F12 => keysym::XK_F12,
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn modifiers_to_x11_mods(modifiers: Modifiers) -> u32 {
|
||||
let mut x11mods = 0;
|
||||
if modifiers.contains(Modifiers::SHIFT) {
|
||||
x11mods |= xlib::ShiftMask;
|
||||
}
|
||||
if modifiers.intersects(Modifiers::SUPER | Modifiers::META) {
|
||||
x11mods |= xlib::Mod4Mask;
|
||||
}
|
||||
if modifiers.contains(Modifiers::ALT) {
|
||||
x11mods |= xlib::Mod1Mask;
|
||||
}
|
||||
if modifiers.contains(Modifiers::CONTROL) {
|
||||
x11mods |= xlib::ControlMask;
|
||||
}
|
||||
x11mods
|
||||
}
|
||||
Reference in New Issue
Block a user