diff --git a/src/platform_impl/linux/common/xkb_state.rs b/src/platform_impl/linux/common/xkb_state.rs index 980e6f3f801..5e4a10054ee 100644 --- a/src/platform_impl/linux/common/xkb_state.rs +++ b/src/platform_impl/linux/common/xkb_state.rs @@ -58,6 +58,8 @@ pub(crate) struct KbState { mods_state: ModifiersState, #[cfg(feature = "wayland")] locked: bool, + #[cfg(feature = "x11")] + pub(crate) core_keyboard_id: i32, scratch_buffer: Vec, } @@ -294,6 +296,8 @@ impl KbState { mods_state: ModifiersState::new(), #[cfg(feature = "wayland")] locked: false, + #[cfg(feature = "x11")] + core_keyboard_id: 0, scratch_buffer: Vec::new(), }; @@ -428,17 +432,20 @@ impl KbState { } // TODO: Support keyboards other than the "virtual core keyboard device". - let core_keyboard_id = (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection); + self.core_keyboard_id = (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection); let keymap = (XKBXH.xkb_x11_keymap_new_from_device)( self.xkb_context, self.xcb_connection, - core_keyboard_id, + self.core_keyboard_id, xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, ); assert_ne!(keymap, ptr::null_mut()); - let state = - (XKBXH.xkb_x11_state_new_from_device)(keymap, self.xcb_connection, core_keyboard_id); + let state = (XKBXH.xkb_x11_state_new_from_device)( + keymap, + self.xcb_connection, + self.core_keyboard_id, + ); self.post_init(state, keymap); } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 2995768372f..5b93f0e4d54 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc}; +use std::{cell::RefCell, collections::HashMap, convert::identity, rc::Rc, slice, sync::Arc}; use libc::{c_char, c_int, c_long, c_ulong}; @@ -35,6 +35,7 @@ pub(super) struct EventProcessor { pub(super) randr_event_offset: c_int, pub(super) devices: RefCell>, pub(super) xi2ext: XExtension, + pub(super) xkbext: XExtension, pub(super) target: Rc>, pub(super) kb_state: KbState, pub(super) mod_keymap: ModifierKeymap, @@ -1095,44 +1096,6 @@ impl EventProcessor { } ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { - // This is horrible, but I couldn't manage to respect keyboard layout changes - // in any other way. In fact, getting this to work at all proved so frustrating - // that I (@maroider) lost motivation to work on the keyboard event rework for - // some months. Thankfully, @ArturKovacs offered to help debug the problem - // over discord, and the following is the result of that debugging session. - // - // Without the XKB extension, the X.Org server sends us the `MappingNotify` - // event when there's been a change in the keyboard layout. This stops - // being the case when we select ourselves some XKB events with `XkbSelectEvents` - // and the "core keyboard device (0x100)" (we haven't tried with any other - // devices). We managed to reproduce this on both our machines. - // - // With the XKB extension active, it would seem like we're supposed to use the - // `XkbStateNotify` event to detect keyboard layout changes, but the `group` - // never changes value (it is always `0`). This worked for @ArturKovacs, but - // not for me. We also tried to use the `group` given to us in keypress events, - // but it remained constant there, too. - // - // We also tried to see if there was some other event that got fired when the - // keyboard layout changed, and we found a mysterious event with the value - // `85` (`0x55`). We couldn't find any reference to it in the X11 headers or - // in the X.Org server source. - // - // `KeymapNotify` did briefly look interesting based purely on the name, but - // it is only useful for checking what keys are pressed when we receive the - // event. - // - // So instead of any vaguely reasonable approach, we get this: reloading the - // keymap on *every* keypress. That's peak efficiency right there! - // - // FIXME: Someone please save our souls! Or at least our wasted CPU cycles. - // - // If you do manage to find a solution, remember to re-enable (and handle) the - // `XkbStateNotify` event with `XkbSelectEventDetails` with a mask of - // `XkbAllStateComponentsMask & !XkbPointerButtonMask` like in - // . - unsafe { self.kb_state.init_with_x11_keymap() }; - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; let state = match xev.evtype { @@ -1213,6 +1176,34 @@ impl EventProcessor { } } _ => { + if event_type == self.xkbext.first_event_id { + let xev = unsafe { &*(identity(xev) as *const _ as *const ffi::XkbAnyEvent) }; + match xev.xkb_type { + ffi::XkbNewKeyboardNotify => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::XkbNewKeyboardNotifyEvent) + }; + let keycodes_changed_flag = 0x1; + let geometry_changed_flag = 0x1 << 1; + + // TODO: What do we do about the "geometry changed" case? + let keycodes_changed = + util::has_flag(xev.changed, keycodes_changed_flag); + let geometry_changed = + util::has_flag(xev.changed, geometry_changed_flag); + + if xev.device == self.kb_state.core_keyboard_id + && (keycodes_changed || geometry_changed) + { + unsafe { self.kb_state.init_with_x11_keymap() }; + } + } + ffi::XkbMapNotify => { + todo!("Handle this"); + } + _ => {} + } + } if event_type == self.randr_event_offset { // In the future, it would be quite easy to emit monitor hotplug events. let prev_list = monitor::invalidate_cached_monitor_list(); diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 92e30f73c9a..44b215b7838 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -202,6 +202,27 @@ impl EventLoop { ext }; + let xkbext = { + let mut ext = XExtension::default(); + + let res = unsafe { + (xconn.xlib.XkbQueryExtension)( + xconn.display, + &mut ext.opcode, + &mut ext.first_event_id, + &mut ext.first_error_id, + &mut 1, + &mut 0, + ) + }; + + if res == ffi::False { + panic!("X server missing XKB extension"); + } + + ext + }; + unsafe { let mut xinput_major_ver = ffi::XI_2_Major; let mut xinput_minor_ver = ffi::XI_2_Minor; @@ -269,6 +290,7 @@ impl EventLoop { ime_receiver, ime_event_receiver, xi2ext, + xkbext, kb_state, mod_keymap, device_mod_state: Default::default(), @@ -285,6 +307,15 @@ impl EventLoop { .select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask) .queue(); + get_xtarget(&target) + .xconn + .select_xkb_events( + 0x100, // Use the "core keyboard device" + ffi::XkbNewKeyboardNotifyMask | ffi::XkbMapNotifyMask, + ) + .unwrap() + .queue(); + event_processor.init_device(ffi::XIAllDevices); EventLoop {