diff --git a/examples/global_shortcut.rs b/examples/global_shortcut.rs index c3d7129ab..0cd66a914 100644 --- a/examples/global_shortcut.rs +++ b/examples/global_shortcut.rs @@ -23,7 +23,7 @@ fn main() { let shortcut_1 = Accelerator::new(SysMods::Shift, KeyCode::ArrowUp); let shortcut_2 = Accelerator::new(RawMods::AltCtrlMeta, KeyCode::KeyB); // use string parser to generate accelerator (require `std::str::FromStr`) - let shortcut_3 = Accelerator::from_str("COMMANDORCONTROL+3").unwrap(); + let shortcut_3 = Accelerator::from_str("COMMANDORCONTROL+SHIFT+3").unwrap(); let shortcut_4 = Accelerator::from_str("COMMANDORCONTROL+shIfT+DOWN").unwrap(); // save a reference to unregister it later @@ -64,7 +64,7 @@ fn main() { // by example `shortcut_1` will NOT match AcceleratorId::new("SHIFT+UP") as it's // been created with a struct and the ID is generated automatically Event::GlobalShortcutEvent(hotkey_id) - if hotkey_id == AcceleratorId::new("COMMANDORCONTROL+3") => + if hotkey_id == AcceleratorId::new("COMMANDORCONTROL+SHIFT+3") => { println!("Pressed on `shortcut_3`"); } diff --git a/src/global_shortcut.rs b/src/global_shortcut.rs index a97ddace1..7a7c6c6e5 100644 --- a/src/global_shortcut.rs +++ b/src/global_shortcut.rs @@ -10,6 +10,10 @@ //! **UNSTABLE** -- The `GlobalShortcut` struct and associated types. //! +//! ## Platform-specific +//! +//! - **Linux**: Only works on x11. See [#331](https://github.com/tauri-apps/tao/issues/331) for more information. +//! //! ```rust,ignore //! let mut hotkey_manager = ShortcutManager::new(&event_loop); //! let accelerator = Accelerator::new(SysMods::Shift, KeyCode::ArrowUp); diff --git a/src/platform_impl/linux/event_loop.rs b/src/platform_impl/linux/event_loop.rs index 559e07cd8..6891a9757 100644 --- a/src/platform_impl/linux/event_loop.rs +++ b/src/platform_impl/linux/event_loop.rs @@ -17,6 +17,7 @@ use glib::{source::Priority, Continue, MainContext}; use gtk::{prelude::*, AboutDialog, Inhibit}; use crate::{ + accelerator::AcceleratorId, dpi::{LogicalPosition, LogicalSize}, event::{ElementState, Event, MouseButton, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, @@ -28,7 +29,6 @@ use crate::{ }; use super::{ - global_shortcut::GlobalShortcutEvent, keyboard, monitor::MonitorHandle, window::{WindowId, WindowRequest}, @@ -44,8 +44,6 @@ pub struct EventLoopWindowTarget { pub(crate) windows: Rc>>, /// Window requests sender pub(crate) window_requests_tx: glib::Sender<(WindowId, WindowRequest)>, - /// Global shortcut requests sender - pub(crate) shortcut_requests_tx: glib::Sender, _marker: std::marker::PhantomData, } @@ -118,8 +116,6 @@ impl EventLoop { // Create event loop window target. let (window_requests_tx, window_requests_rx) = glib::MainContext::channel(Priority::default()); let window_requests_tx_ = window_requests_tx.clone(); - let (shortcut_requests_tx, shortcut_requests_rx) = - glib::MainContext::channel(Priority::default()); let display = gdk::Display::default() .expect("GdkDisplay not found. This usually means `gkt_init` hasn't called yet."); let window_target = EventLoopWindowTarget { @@ -127,7 +123,6 @@ impl EventLoop { app, windows: Rc::new(RefCell::new(HashSet::new())), window_requests_tx, - shortcut_requests_tx, _marker: std::marker::PhantomData, }; @@ -141,39 +136,6 @@ impl EventLoop { Continue(true) }); - // Global Shortcut Event Request - let app = app_.clone(); - let event_tx_ = event_tx.clone(); - shortcut_requests_rx.attach(Some(&context), move |event| { - match event { - GlobalShortcutEvent::Register((id, key)) => { - let name = format!("{}", id.0); - let action = gio::SimpleAction::new(&name, None); - let event_tx = event_tx_.clone(); - action.connect_activate(move |_, _| { - if let Err(e) = event_tx.send(Event::GlobalShortcutEvent(id)) { - log::warn!( - "Failed to send global shortcut event to event channel: {}", - e - ); - } - }); - app.add_action(&action); - app.set_accels_for_action(&format!("app.{}", name), &[&key]); - } - GlobalShortcutEvent::UnRegister(id) => { - app.remove_action(&format!("{}", id.0)); - } - GlobalShortcutEvent::UnRegisterAll => { - for action in app.list_actions() { - app.remove_action(&action); - } - } - } - - Continue(true) - }); - // Window Request window_requests_rx.attach(Some(&context), move |(id, request)| { if let Some(window) = app_.window_by_id(id.0) { @@ -605,9 +567,9 @@ impl EventLoop { let tx_clone = event_tx.clone(); let keyboard_handler = Rc::new(move |event_key: EventKey, element_state| { - let mut mods = keyboard::get_modifiers(event_key.clone()); // if we have a modifier lets send it - if event_key.is_modifier() { + let mut mods = keyboard::get_modifiers(event_key.clone()); + if !mods.is_empty() { // if we release the modifier tell the world if ElementState::Released == element_state { mods = ModifiersState::empty(); @@ -765,16 +727,25 @@ impl EventLoop { menubar.show_all(); } } + WindowRequest::GlobalHotKey(_hotkey_id) => {} } } else if id == WindowId::dummy() { - if let WindowRequest::Menu((None, Some(menu_id))) = request { - if let Err(e) = event_tx.send(Event::MenuEvent { - window_id: None, - menu_id, - origin: MenuType::ContextMenu, - }) { - log::warn!("Failed to send status bar event to event channel: {}", e); + match request { + WindowRequest::GlobalHotKey(hotkey_id) => { + if let Err(e) = event_tx.send(Event::GlobalShortcutEvent(AcceleratorId(hotkey_id))) { + log::warn!("Failed to send global hotkey event to event channel: {}", e); + } + } + WindowRequest::Menu((None, Some(menu_id))) => { + if let Err(e) = event_tx.send(Event::MenuEvent { + window_id: None, + menu_id, + origin: MenuType::ContextMenu, + }) { + log::warn!("Failed to send status bar event to event channel: {}", e); + } } + _ => {} } } Continue(true) diff --git a/src/platform_impl/linux/global_shortcut.rs b/src/platform_impl/linux/global_shortcut.rs index 289e1761d..e6b4f6e0e 100644 --- a/src/platform_impl/linux/global_shortcut.rs +++ b/src/platform_impl/linux/global_shortcut.rs @@ -1,18 +1,152 @@ +use super::window::{WindowId, WindowRequest}; use crate::{ accelerator::{Accelerator, AcceleratorId}, event_loop::EventLoopWindowTarget, global_shortcut::{GlobalShortcut as RootGlobalShortcut, ShortcutManagerError}, + keyboard::KeyCode, }; +use crossbeam_channel::{self as channel, Receiver, Sender, TryRecvError}; +use std::{ + collections::HashMap, + ptr, + sync::{Arc, Mutex}, +}; +use x11_dl::{keysym, xlib}; + +#[derive(Debug)] +enum HotkeyMessage { + RegisterHotkey(ListenerId, u32, u32), + RegisterHotkeyResult(Result), + UnregisterHotkey(ListenerId), + UnregisterHotkeyResult(Result<(), ShortcutManagerError>), + DropThread, +} #[derive(Debug)] pub struct ShortcutManager { - tx: glib::Sender, + shortcuts: ListenerMap, + method_sender: Sender, + method_receiver: Receiver, } impl ShortcutManager { - pub(crate) fn new(window_target: &EventLoopWindowTarget) -> Self { - Self { - tx: window_target.p.shortcut_requests_tx.clone(), + pub(crate) fn new(_window_target: &EventLoopWindowTarget) -> Self { + let window_id = WindowId::dummy(); + let hotkeys = ListenerMap::default(); + let hotkey_map = hotkeys.clone(); + + let event_loop_channel = _window_target.p.window_requests_tx.clone(); + + let (method_sender, thread_receiver) = channel::unbounded(); + let (thread_sender, method_receiver) = channel::unbounded(); + + std::thread::spawn(move || { + let event_loop_channel = event_loop_channel.clone(); + 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 + #[allow(clippy::uninit_assumed_init)] + let mut supported_rtrn: i32 = std::mem::MaybeUninit::uninit().assume_init(); + (xlib.XkbSetDetectableAutoRepeat)(display, 1, &mut supported_rtrn); + + (xlib.XSelectInput)(display, root, xlib::KeyReleaseMask); + #[allow(clippy::uninit_assumed_init)] + let mut event: xlib::XEvent = std::mem::MaybeUninit::uninit().assume_init(); + + loop { + let event_loop_channel = event_loop_channel.clone(); + if (xlib.XPending)(display) > 0 { + (xlib.XNextEvent)(display, &mut event); + if let xlib::KeyRelease = event.get_type() { + let keycode = event.key.keycode; + let modifiers = event.key.state; + if let Some(hotkey_id) = hotkey_map.lock().unwrap().get(&(keycode as i32, modifiers)) + { + event_loop_channel + .send((window_id, WindowRequest::GlobalHotKey(*hotkey_id as u16))) + .unwrap(); + } + } + } + + match thread_receiver.try_recv() { + Ok(HotkeyMessage::RegisterHotkey(_, modifiers, key)) => { + let keycode = (xlib.XKeysymToKeycode)(display, key.into()) as i32; + + let result = (xlib.XGrabKey)( + display, + keycode, + modifiers, + root, + 0, + xlib::GrabModeAsync, + xlib::GrabModeAsync, + ); + if result == 0 { + if let Err(err) = thread_sender + .clone() + .send(HotkeyMessage::RegisterHotkeyResult(Err( + ShortcutManagerError::InvalidAccelerator( + "Unable to register accelerator".into(), + ), + ))) + { + #[cfg(debug_assertions)] + eprintln!("hotkey: thread_sender.send error {}", err); + } + } else if let Err(err) = thread_sender.send(HotkeyMessage::RegisterHotkeyResult(Ok( + (keycode, modifiers), + ))) { + #[cfg(debug_assertions)] + eprintln!("hotkey: thread_sender.send error {}", err); + } + } + Ok(HotkeyMessage::UnregisterHotkey(id)) => { + let result = (xlib.XUngrabKey)(display, id.0, id.1, root); + if result == 0 { + if let Err(err) = thread_sender + .clone() + .send(HotkeyMessage::UnregisterHotkeyResult(Err( + ShortcutManagerError::InvalidAccelerator( + "Unable to unregister accelerator".into(), + ), + ))) + { + #[cfg(debug_assertions)] + eprintln!("hotkey: thread_sender.send error {}", err); + } + } else if let Err(err) = + thread_sender.send(HotkeyMessage::UnregisterHotkeyResult(Ok(()))) + { + #[cfg(debug_assertions)] + eprintln!("hotkey: thread_sender.send error {}", err); + } + } + Ok(HotkeyMessage::DropThread) => { + (xlib.XCloseDisplay)(display); + return; + } + Err(err) => { + if let TryRecvError::Disconnected = err { + #[cfg(debug_assertions)] + eprintln!("hotkey: try_recv error {}", err); + } + } + _ => unreachable!("other message should not arrive"), + }; + + std::thread::sleep(std::time::Duration::from_millis(50)); + } + } + }); + + ShortcutManager { + shortcuts: hotkeys, + method_sender, + method_receiver, } } @@ -20,29 +154,65 @@ impl ShortcutManager { &mut self, accelerator: Accelerator, ) -> Result { - if let Some(key) = get_gtk_key(&accelerator) { - let id = accelerator.clone().id(); - if let Err(e) = self.tx.send(GlobalShortcutEvent::Register((id, key))) { - log::warn!( - "Failed to send global shortcut event to event loop channel: {}", - e - ); + let keycode = get_x11_scancode_from_hotkey(accelerator.key); + + if let Some(keycode) = keycode { + let mut converted_modifiers: u32 = 0; + if accelerator.mods.shift_key() { + converted_modifiers |= xlib::ShiftMask; + } + if accelerator.mods.super_key() { + converted_modifiers |= xlib::Mod4Mask; + } + if accelerator.mods.alt_key() { + converted_modifiers |= xlib::Mod1Mask; } - Ok(RootGlobalShortcut(GlobalShortcut { accelerator })) - } else { - Err(ShortcutManagerError::InvalidAccelerator( - "Failed to convert KeyCode to gdk::Key".into(), - )) + if accelerator.mods.control_key() { + converted_modifiers |= xlib::ControlMask; + } + + self + .method_sender + .send(HotkeyMessage::RegisterHotkey( + (0, 0), + converted_modifiers, + keycode, + )) + .map_err(|_| { + ShortcutManagerError::InvalidAccelerator("Unable to register global shortcut".into()) + })?; + + return match self.method_receiver.recv() { + Ok(HotkeyMessage::RegisterHotkeyResult(Ok(id))) => { + self + .shortcuts + .lock() + .unwrap() + .insert(id, accelerator.clone().id().0 as u32); + let shortcut = GlobalShortcut { accelerator }; + return Ok(RootGlobalShortcut(shortcut)); + } + Ok(HotkeyMessage::RegisterHotkeyResult(Err(err))) => Err(err), + Err(err) => Err(ShortcutManagerError::InvalidAccelerator(err.to_string())), + _ => Err(ShortcutManagerError::InvalidAccelerator( + "Unknown error".into(), + )), + }; } + + Err(ShortcutManagerError::InvalidAccelerator( + "Invalid accelerators".into(), + )) } pub(crate) fn unregister_all(&mut self) -> Result<(), ShortcutManagerError> { - if let Err(e) = self.tx.send(GlobalShortcutEvent::UnRegisterAll) { - log::warn!( - "Failed to send global shortcut event to event loop channel: {}", - e - ); + for (found_id, _) in self.shortcuts.lock().unwrap().iter() { + self + .method_sender + .send(HotkeyMessage::UnregisterHotkey(*found_id)) + .map_err(|_| ShortcutManagerError::InvalidAccelerator("Channel error".into()))?; } + self.shortcuts = ListenerMap::default(); Ok(()) } @@ -50,16 +220,43 @@ impl ShortcutManager { &self, shortcut: RootGlobalShortcut, ) -> Result<(), ShortcutManagerError> { - if let Err(e) = self - .tx - .send(GlobalShortcutEvent::UnRegister(shortcut.0.accelerator.id())) - { - log::warn!( - "Failed to send global shortcut event to event loop channel: {}", - e - ); + let mut found_id = (-1, 0); + for (id, shortcut_id) in self.shortcuts.lock().unwrap().iter() { + if *shortcut_id == shortcut.0.id().0 as u32 { + found_id = *id; + break; + } + } + if found_id == (-1, 0) { + return Err(ShortcutManagerError::AcceleratorNotRegistered( + shortcut.0.accelerator, + )); + } + + self + .method_sender + .send(HotkeyMessage::UnregisterHotkey(found_id)) + .map_err(|_| ShortcutManagerError::InvalidAccelerator("Channel error".into()))?; + if self.shortcuts.lock().unwrap().remove(&found_id).is_none() { + panic!("hotkey should never be none") + }; + match self.method_receiver.recv() { + Ok(HotkeyMessage::UnregisterHotkeyResult(Ok(_))) => Ok(()), + Ok(HotkeyMessage::UnregisterHotkeyResult(Err(err))) => Err(err), + Err(err) => Err(ShortcutManagerError::InvalidAccelerator(err.to_string())), + _ => Err(ShortcutManagerError::InvalidAccelerator( + "Unknown error".into(), + )), + } + } +} + +impl Drop for ShortcutManager { + fn drop(&mut self) { + if let Err(err) = self.method_sender.send(HotkeyMessage::DropThread) { + #[cfg(debug_assertions)] + eprintln!("cant send close thread message {}", err); } - Ok(()) } } @@ -67,6 +264,8 @@ impl ShortcutManager { pub struct GlobalShortcut { pub(crate) accelerator: Accelerator, } +type ListenerId = (i32, u32); +type ListenerMap = Arc>>; impl GlobalShortcut { pub fn id(&self) -> AcceleratorId { @@ -74,37 +273,117 @@ impl GlobalShortcut { } } -pub enum GlobalShortcutEvent { - Register((AcceleratorId, String)), - UnRegister(AcceleratorId), - UnRegisterAll, -} +// required for event but we use dummy window Id +// so it shouldn't be a problem +unsafe impl Send for WindowId {} +unsafe impl Sync for WindowId {} +// simple enum, no pointer, shouldn't be a problem +// to use send + sync +unsafe impl Send for WindowRequest {} +unsafe impl Sync for WindowRequest {} -fn get_gtk_key(key: &Accelerator) -> Option { - let mut result = String::new(); +fn get_x11_scancode_from_hotkey(key: KeyCode) -> Option { + Some(match key { + KeyCode::KeyA => 'A' as u32, + KeyCode::KeyB => 'B' as u32, + KeyCode::KeyC => 'C' as u32, + KeyCode::KeyD => 'D' as u32, + KeyCode::KeyE => 'E' as u32, + KeyCode::KeyF => 'F' as u32, + KeyCode::KeyG => 'G' as u32, + KeyCode::KeyH => 'H' as u32, + KeyCode::KeyI => 'I' as u32, + KeyCode::KeyJ => 'J' as u32, + KeyCode::KeyK => 'K' as u32, + KeyCode::KeyL => 'L' as u32, + KeyCode::KeyM => 'M' as u32, + KeyCode::KeyN => 'N' as u32, + KeyCode::KeyO => 'O' as u32, + KeyCode::KeyP => 'P' as u32, + KeyCode::KeyQ => 'Q' as u32, + KeyCode::KeyR => 'R' as u32, + KeyCode::KeyS => 'S' as u32, + KeyCode::KeyT => 'T' as u32, + KeyCode::KeyU => 'U' as u32, + KeyCode::KeyV => 'V' as u32, + KeyCode::KeyW => 'W' as u32, + KeyCode::KeyX => 'X' as u32, + KeyCode::KeyY => 'Y' as u32, + KeyCode::KeyZ => 'Z' as u32, + KeyCode::Backslash => keysym::XK_backslash, + KeyCode::BracketLeft => keysym::XK_bracketleft, + KeyCode::BracketRight => keysym::XK_bracketright, + KeyCode::Comma => keysym::XK_comma, + KeyCode::Digit0 => '0' as u32, + KeyCode::Digit1 => '1' as u32, + KeyCode::Digit2 => '2' as u32, + KeyCode::Digit3 => '3' as u32, + KeyCode::Digit4 => '4' as u32, + KeyCode::Digit5 => '5' as u32, + KeyCode::Digit6 => '6' as u32, + KeyCode::Digit7 => '7' as u32, + KeyCode::Digit8 => '8' as u32, + KeyCode::Digit9 => '9' as u32, + KeyCode::Equal => keysym::XK_equal, + KeyCode::IntlBackslash => keysym::XK_backslash, + KeyCode::Minus => keysym::XK_minus, + KeyCode::Period => keysym::XK_period, + KeyCode::Quote => keysym::XK_leftsinglequotemark, + KeyCode::Semicolon => keysym::XK_semicolon, + KeyCode::Slash => keysym::XK_slash, + KeyCode::Backspace => keysym::XK_BackSpace, + KeyCode::CapsLock => keysym::XK_Caps_Lock, + KeyCode::Enter => keysym::XK_Return, + KeyCode::Space => keysym::XK_space, + KeyCode::Tab => keysym::XK_Tab, + KeyCode::Delete => keysym::XK_Delete, + KeyCode::End => keysym::XK_End, + KeyCode::Home => keysym::XK_Home, + KeyCode::Insert => keysym::XK_Insert, + KeyCode::PageDown => keysym::XK_Page_Down, + KeyCode::PageUp => keysym::XK_Page_Up, + KeyCode::ArrowDown => keysym::XK_Down, + KeyCode::ArrowLeft => keysym::XK_Left, + KeyCode::ArrowRight => keysym::XK_Right, + KeyCode::ArrowUp => keysym::XK_Up, + KeyCode::Numpad0 => keysym::XK_KP_0, + KeyCode::Numpad1 => keysym::XK_KP_1, + KeyCode::Numpad2 => keysym::XK_KP_2, + KeyCode::Numpad3 => keysym::XK_KP_3, + KeyCode::Numpad4 => keysym::XK_KP_4, + KeyCode::Numpad5 => keysym::XK_KP_5, + KeyCode::Numpad6 => keysym::XK_KP_6, + KeyCode::Numpad7 => keysym::XK_KP_7, + KeyCode::Numpad8 => keysym::XK_KP_8, + KeyCode::Numpad9 => keysym::XK_KP_9, + KeyCode::NumpadAdd => keysym::XK_KP_Add, + KeyCode::NumpadDecimal => keysym::XK_KP_Decimal, + KeyCode::NumpadDivide => keysym::XK_KP_Divide, + KeyCode::NumpadMultiply => keysym::XK_KP_Multiply, + KeyCode::NumpadSubtract => keysym::XK_KP_Subtract, + KeyCode::Escape => keysym::XK_Escape, + KeyCode::PrintScreen => keysym::XK_Print, + KeyCode::ScrollLock => keysym::XK_Scroll_Lock, + KeyCode::Pause => keysym::XF86XK_AudioPlay, + KeyCode::MediaStop => keysym::XF86XK_AudioStop, + KeyCode::MediaTrackNext => keysym::XF86XK_AudioNext, + KeyCode::MediaTrackPrevious => keysym::XF86XK_AudioPrev, + KeyCode::AudioVolumeDown => keysym::XF86XK_AudioLowerVolume, + KeyCode::AudioVolumeMute => keysym::XF86XK_AudioMute, + KeyCode::AudioVolumeUp => keysym::XF86XK_AudioRaiseVolume, + KeyCode::F1 => keysym::XK_F1, + KeyCode::F2 => keysym::XK_F2, + KeyCode::F3 => keysym::XK_F3, + KeyCode::F4 => keysym::XK_F4, + KeyCode::F5 => keysym::XK_F5, + KeyCode::F6 => keysym::XK_F6, + KeyCode::F7 => keysym::XK_F7, + KeyCode::F8 => keysym::XK_F8, + KeyCode::F9 => keysym::XK_F9, + KeyCode::F10 => keysym::XK_F10, + KeyCode::F11 => keysym::XK_F11, + KeyCode::F12 => keysym::XK_F12, - let mods = key.mods; - if mods.shift_key() { - result += ""; - } - if mods.control_key() { - result += ""; - } - if mods.alt_key() { - result += ""; - } - if mods.super_key() { - result += ""; - } - - if let Some(k) = super::keyboard::key_to_raw_key(&key.key) { - if let Some(name) = k.name() { - result += &name; - Some(result) - } else { - None - } - } else { - None - } + _ => return None, + }) } diff --git a/src/platform_impl/linux/keyboard.rs b/src/platform_impl/linux/keyboard.rs index aab17ed46..fbe01aa03 100644 --- a/src/platform_impl/linux/keyboard.rs +++ b/src/platform_impl/linux/keyboard.rs @@ -3,7 +3,7 @@ use crate::{ event::{ElementState, KeyEvent}, keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKeyCode}, }; -use gdk::{keys::constants::*, EventKey, ModifierType}; +use gdk::{keys::constants::*, EventKey}; use std::{ collections::HashSet, ffi::c_void, @@ -117,11 +117,11 @@ pub(crate) fn raw_key_to_location(raw: RawKey) -> KeyLocation { } } -const MODIFIER_MAP: &[(ModifierType, ModifiersState)] = &[ - (ModifierType::SHIFT_MASK, ModifiersState::SHIFT), - (ModifierType::MOD1_MASK, ModifiersState::ALT), - (ModifierType::CONTROL_MASK, ModifiersState::CONTROL), - (ModifierType::SUPER_MASK, ModifiersState::SUPER), +const MODIFIER_MAP: &[(Key<'static>, ModifiersState)] = &[ + (Key::Shift, ModifiersState::SHIFT), + (Key::Alt, ModifiersState::ALT), + (Key::Control, ModifiersState::CONTROL), + (Key::Super, ModifiersState::SUPER), ]; // we use the EventKey to extract the modifier mainly because @@ -129,12 +129,29 @@ const MODIFIER_MAP: &[(ModifierType, ModifiersState)] = &[ // other os' logic -- this way we can emit the new `ModifiersState` before // we receive the next key, if needed the developer can update his local state. pub(crate) fn get_modifiers(key: EventKey) -> ModifiersState { - let state = key.state(); + // a keycode (scancode in Windows) is a code that refers to a physical keyboard key. + let scancode = key.hardware_keycode(); + // a keyval (keysym in X) is a "logical" key name, such as GDK_Enter, GDK_a, GDK_space, etc. + let keyval = key.keyval(); + // unicode value + let unicode = keyval.to_unicode(); + // translate to tao::keyboard::Key + let key_from_code = raw_key_to_key(keyval).unwrap_or_else(|| { + if let Some(key) = unicode { + if key >= ' ' && key != '\x7f' { + Key::Character(insert_or_get_key_str(key.to_string())) + } else { + Key::Unidentified(NativeKeyCode::Gtk(scancode)) + } + } else { + Key::Unidentified(NativeKeyCode::Gtk(scancode)) + } + }); // start with empty state let mut result = ModifiersState::empty(); // loop trough our modifier map for (gdk_mod, modifier) in MODIFIER_MAP { - if state.contains(*gdk_mod) { + if key_from_code == *gdk_mod { result |= *modifier; } } @@ -317,66 +334,6 @@ pub fn key_to_raw_key(src: &KeyCode) -> Option { KeyCode::ContextMenu => Menu, KeyCode::WakeUp => WakeUp, - - KeyCode::Backslash => backslash, - KeyCode::BracketLeft => braceleft, - KeyCode::BracketRight => braceright, - KeyCode::Comma => comma, - KeyCode::Digit0 => _0, - KeyCode::Digit1 => _1, - KeyCode::Digit2 => _2, - KeyCode::Digit3 => _3, - KeyCode::Digit4 => _4, - KeyCode::Digit5 => _5, - KeyCode::Digit6 => _6, - KeyCode::Digit7 => _7, - KeyCode::Digit8 => _8, - KeyCode::Digit9 => _9, - KeyCode::Equal => equal, - KeyCode::KeyA => A, - KeyCode::KeyB => B, - KeyCode::KeyC => C, - KeyCode::KeyD => D, - KeyCode::KeyE => E, - KeyCode::KeyF => F, - KeyCode::KeyG => G, - KeyCode::KeyH => H, - KeyCode::KeyI => I, - KeyCode::KeyJ => J, - KeyCode::KeyK => K, - KeyCode::KeyL => L, - KeyCode::KeyM => M, - KeyCode::KeyN => N, - KeyCode::KeyO => O, - KeyCode::KeyP => P, - KeyCode::KeyQ => Q, - KeyCode::KeyR => R, - KeyCode::KeyS => S, - KeyCode::KeyT => T, - KeyCode::KeyU => U, - KeyCode::KeyV => V, - KeyCode::KeyW => W, - KeyCode::KeyX => X, - KeyCode::KeyY => Y, - KeyCode::KeyZ => Z, - KeyCode::Minus => minus, - KeyCode::Period => period, - KeyCode::Quote => leftsinglequotemark, - KeyCode::Semicolon => semicolon, - KeyCode::Slash => slash, - KeyCode::Space => space, - KeyCode::Numpad0 => KP_0, - KeyCode::Numpad1 => KP_1, - KeyCode::Numpad2 => KP_2, - KeyCode::Numpad3 => KP_3, - KeyCode::Numpad4 => KP_4, - KeyCode::Numpad5 => KP_5, - KeyCode::Numpad6 => KP_6, - KeyCode::Numpad7 => KP_7, - KeyCode::Numpad8 => KP_8, - KeyCode::Numpad9 => KP_9, - KeyCode::NumpadAdd => KP_Add, - // TODO add more RawKey(gdk::key) _ => return None, }) } diff --git a/src/platform_impl/linux/window.rs b/src/platform_impl/linux/window.rs index 50582d85f..0bc319368 100644 --- a/src/platform_impl/linux/window.rs +++ b/src/platform_impl/linux/window.rs @@ -664,6 +664,7 @@ pub enum WindowRequest { Redraw, Menu((Option, Option)), SetMenu((Option, AccelGroup, gtk::MenuBar)), + GlobalHotKey(u16), } pub fn hit_test(window: &gdk::Window, cx: f64, cy: f64) -> WindowEdge {