diff --git a/src/event.rs b/src/event.rs index e226bd70..c4ef5f85 100644 --- a/src/event.rs +++ b/src/event.rs @@ -133,6 +133,9 @@ pub enum WindowEvent { input: KeyboardInput, }, + /// Keyboard modifiers have changed + ModifiersChanged { modifiers: ModifiersState }, + /// The cursor has moved on the window. CursorMoved { device_id: DeviceId, diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index 727062ad..4a236691 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -83,7 +83,17 @@ pub fn init_keyboard( KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ } KbEvent::Modifiers { modifiers: event_modifiers, - } => *modifiers_tracker.lock().unwrap() = event_modifiers.into(), + } => { + let modifiers = event_modifiers.into(); + + *modifiers_tracker.lock().unwrap() = modifiers; + + if let Some(wid) = *target.lock().unwrap() { + my_sink + .send((WindowEvent::ModifiersChanged { modifiers }, wid)) + .unwrap(); + } + } } }, move |repeat_event: KeyRepeatEvent, _| { diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 4dea6d27..d65a0133 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -8,6 +8,8 @@ use super::{ XExtension, }; +use util::modifiers::{ModifierKeyState, ModifierKeymap}; + use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent}, @@ -21,6 +23,9 @@ pub(super) struct EventProcessor { pub(super) devices: RefCell>, pub(super) xi2ext: XExtension, pub(super) target: Rc>, + pub(super) mod_keymap: ModifierKeymap, + pub(super) device_mod_state: ModifierKeyState, + pub(super) window_mod_state: ModifierKeyState, } impl EventProcessor { @@ -112,12 +117,22 @@ impl EventProcessor { let event_type = xev.get_type(); match event_type { ffi::MappingNotify => { - unsafe { - (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); + let mapping: &ffi::XMappingEvent = xev.as_ref(); + + if mapping.request == ffi::MappingModifier + || mapping.request == ffi::MappingKeyboard + { + unsafe { + (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); + } + wt.xconn + .check_errors() + .expect("Failed to call XRefreshKeyboardMapping"); + + self.mod_keymap.reset_from_x_connection(&wt.xconn); + self.device_mod_state.update(&self.mod_keymap); + self.window_mod_state.update(&self.mod_keymap); } - wt.xconn - .check_errors() - .expect("Failed to call XRefreshKeyboardMapping"); } ffi::ClientMessage => { @@ -514,13 +529,6 @@ impl EventProcessor { // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with // a keycode of 0. if xkev.keycode != 0 { - let modifiers = ModifiersState { - alt: xkev.state & ffi::Mod1Mask != 0, - shift: xkev.state & ffi::ShiftMask != 0, - ctrl: xkev.state & ffi::ControlMask != 0, - logo: xkev.state & ffi::Mod4Mask != 0, - }; - let keysym = unsafe { let mut keysym = 0; (wt.xconn.xlib.XLookupString)( @@ -535,6 +543,8 @@ impl EventProcessor { }; let virtual_keycode = events::keysym_to_element(keysym as c_uint); + let modifiers = self.window_mod_state.modifiers(); + callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { @@ -547,6 +557,27 @@ impl EventProcessor { }, }, }); + + if let Some(modifier) = + self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode) + { + self.window_mod_state.key_event( + state, + xkev.keycode as ffi::KeyCode, + modifier, + ); + + let new_modifiers = self.window_mod_state.modifiers(); + + if modifiers != new_modifiers { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged { + modifiers: new_modifiers, + }, + }); + } + } } if state == Pressed { @@ -859,6 +890,21 @@ impl EventProcessor { event: Focused(true), }); + // When focus is gained, send any existing modifiers + // to the window in a ModifiersChanged event. This is + // done to compensate for modifier keys that may be + // changed while a window is out of focus. + if !self.device_mod_state.is_empty() { + self.window_mod_state = self.device_mod_state.clone(); + + let modifiers = self.window_mod_state.modifiers(); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged { modifiers }, + }); + } + // The deviceid for this event is for a keyboard instead of a pointer, // so we have to do a little extra work. let pointer_id = self @@ -890,6 +936,22 @@ impl EventProcessor { .borrow_mut() .unfocus(xev.event) .expect("Failed to unfocus input context"); + + // When focus is lost, send a ModifiersChanged event + // containing no modifiers set. This is done to compensate + // for modifier keys that may be changed while a window + // is out of focus. + if !self.window_mod_state.is_empty() { + self.window_mod_state.clear(); + + callback(Event::WindowEvent { + window_id: mkwid(xev.event), + event: WindowEvent::ModifiersChanged { + modifiers: ModifiersState::default(), + }, + }); + } + callback(Event::WindowEvent { window_id: mkwid(xev.event), event: Focused(false), @@ -1022,18 +1084,25 @@ impl EventProcessor { let virtual_keycode = events::keysym_to_element(keysym as c_uint); + if let Some(modifier) = + self.mod_keymap.get_modifier(keycode as ffi::KeyCode) + { + self.device_mod_state.key_event( + state, + keycode as ffi::KeyCode, + modifier, + ); + } + + let modifiers = self.device_mod_state.modifiers(); + callback(Event::DeviceEvent { device_id: mkdid(device_id), event: DeviceEvent::Key(KeyboardInput { scancode, virtual_keycode, state, - // So, in an ideal world we can use libxkbcommon to get modifiers. - // However, libxkbcommon-x11 isn't as commonly installed as one - // would hope. We can still use the Xkb extension to get - // comprehensive keyboard state updates, but interpreting that - // info manually is going to be involved. - modifiers: ModifiersState::default(), + modifiers, }), }); } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 420dfb2d..5b283e68 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -34,6 +34,7 @@ use self::{ dnd::{Dnd, DndState}, event_processor::EventProcessor, ime::{Ime, ImeCreationError, ImeReceiver, ImeSender}, + util::modifiers::ModifierKeymap, }; use crate::{ error::OsError as RootOsError, @@ -143,6 +144,9 @@ impl EventLoop { xconn.update_cached_wm_info(root); + let mut mod_keymap = ModifierKeymap::new(); + mod_keymap.reset_from_x_connection(&xconn); + let target = Rc::new(RootELW { p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { ime, @@ -186,6 +190,9 @@ impl EventLoop { randr_event_offset, ime_receiver, xi2ext, + mod_keymap, + device_mod_state: Default::default(), + window_mod_state: Default::default(), }; // Register for device hotplug events diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index dba0f9d2..1410da28 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -10,6 +10,7 @@ mod hint; mod icon; mod input; mod memory; +pub mod modifiers; mod randr; mod window_property; mod wm; diff --git a/src/platform_impl/linux/x11/util/modifiers.rs b/src/platform_impl/linux/x11/util/modifiers.rs new file mode 100644 index 00000000..9b8da4b7 --- /dev/null +++ b/src/platform_impl/linux/x11/util/modifiers.rs @@ -0,0 +1,149 @@ +use std::{collections::HashMap, slice}; + +use super::*; + +use crate::event::{ElementState, ModifiersState}; + +// Offsets within XModifierKeymap to each set of keycodes. +// We are only interested in Shift, Control, Alt, and Logo. +// +// There are 8 sets total. The order of keycode sets is: +// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5 +// +// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html +const SHIFT_OFFSET: usize = 0; +const CONTROL_OFFSET: usize = 2; +const ALT_OFFSET: usize = 3; +const LOGO_OFFSET: usize = 6; +const NUM_MODS: usize = 8; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Modifier { + Alt, + Ctrl, + Shift, + Logo, +} + +#[derive(Debug, Default)] +pub struct ModifierKeymap { + // Maps keycodes to modifiers + keys: HashMap, +} + +#[derive(Clone, Debug, Default)] +pub struct ModifierKeyState { + // Contains currently pressed modifier keys and their corresponding modifiers + keys: HashMap, +} + +impl ModifierKeymap { + pub fn new() -> ModifierKeymap { + ModifierKeymap::default() + } + + pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option { + self.keys.get(&keycode).cloned() + } + + pub fn reset_from_x_connection(&mut self, xconn: &XConnection) { + unsafe { + let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display); + + if keymap.is_null() { + panic!("failed to allocate XModifierKeymap"); + } + + self.reset_from_x_keymap(&*keymap); + + (xconn.xlib.XFreeModifiermap)(keymap); + } + } + + pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) { + let keys_per_mod = keymap.max_keypermod as usize; + + let keys = unsafe { + slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS) + }; + + self.keys.clear(); + + self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift); + self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl); + self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt); + self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo); + } + + fn read_x_keys( + &mut self, + keys: &[ffi::KeyCode], + offset: usize, + keys_per_mod: usize, + modifier: Modifier, + ) { + let start = offset * keys_per_mod; + let end = start + keys_per_mod; + + for &keycode in &keys[start..end] { + if keycode != 0 { + self.keys.insert(keycode, modifier); + } + } + } +} + +impl ModifierKeyState { + pub fn clear(&mut self) { + self.keys.clear(); + } + + pub fn is_empty(&self) -> bool { + self.keys.is_empty() + } + + pub fn update(&mut self, mods: &ModifierKeymap) { + self.keys.retain(|k, v| { + if let Some(m) = mods.get_modifier(*k) { + *v = m; + true + } else { + false + } + }); + } + + pub fn modifiers(&self) -> ModifiersState { + let mut state = ModifiersState::default(); + + for &m in self.keys.values() { + set_modifier(&mut state, m); + } + + state + } + + pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) { + match state { + ElementState::Pressed => self.key_press(keycode, modifier), + ElementState::Released => self.key_release(keycode), + } + } + + pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) { + self.keys.insert(keycode, modifier); + } + + pub fn key_release(&mut self, keycode: ffi::KeyCode) { + self.keys.remove(&keycode); + } +} + +fn set_modifier(state: &mut ModifiersState, modifier: Modifier) { + match modifier { + Modifier::Alt => state.alt = true, + Modifier::Ctrl => state.ctrl = true, + Modifier::Shift => state.shift = true, + Modifier::Logo => state.logo = true, + } +}