From a320702a71fb3b1ec0aaa07f611a6bb233e860b1 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 30 Jun 2023 19:59:24 +0400 Subject: [PATCH] On X11, fix IME not working The change to xinput2 completely disabled IME support, thus we've got a dead keys reporting, because nothing was eating the key events anymore, however that's not what we really need, given that not working IME makes it impossible for some users to type. The proper solution is to not use Xlib at all for that and rely on xcb and its tooling around the XIM and text compose stuff, so we'll have full control over what is getting sent to the XIM/IC or not. Fixes #2888. --- .../linux/x11/event_processor.rs | 102 ++++++++---------- src/platform_impl/linux/x11/mod.rs | 10 ++ src/platform_impl/linux/x11/window.rs | 3 +- src/window.rs | 1 + 4 files changed, 57 insertions(+), 59 deletions(-) diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 67e321b4..d8f076be 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -31,6 +31,8 @@ pub(super) struct EventProcessor { pub(super) kb_state: KbdState, // Number of touch events currently in progress pub(super) num_touch: u32, + // Whether we've got a key release for the key press. + pub(super) got_key_release: bool, pub(super) first_touch: Option, // Currently focused window belonging to this process pub(super) active_window: Option, @@ -535,34 +537,54 @@ impl EventProcessor { } // Note that in compose/pre-edit sequences, we'll always receive KeyRelease events - ffi::KeyPress => { + ty @ ffi::KeyPress | ty @ ffi::KeyRelease => { let xkev: &mut ffi::XKeyEvent = xev.as_mut(); - - let window = xkev.window; - let window_id = mkwid(window); - - let written = if let Some(ic) = wt.ime.borrow().get_context(window) { - wt.xconn.lookup_utf8(ic, xkev) - } else { - return; + let window = match self.active_window { + Some(window) => window, + None => return, }; - // If we're composing right now, send the string we've got from X11 via - // Ime::Commit. - if self.is_composing && xkev.keycode == 0 && !written.is_empty() { - let event = Event::WindowEvent { - window_id, - event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), - }; - callback(event); + let window_id = mkwid(window); + let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); - let event = Event::WindowEvent { - window_id, - event: WindowEvent::Ime(Ime::Commit(written)), - }; + let keycode = xkev.keycode as _; + let repeat = ty == ffi::KeyPress && !self.got_key_release; + // Update state after the repeat setting. + let state = if ty == ffi::KeyPress { + self.got_key_release = false; + ElementState::Pressed + } else { + self.got_key_release = true; + ElementState::Released + }; - self.is_composing = false; - callback(event); + if keycode != 0 && !self.is_composing { + let event = self.kb_state.process_key_event(keycode, state, repeat); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id, + event, + is_synthetic: false, + }, + }); + } else if let Some(ic) = wt.ime.borrow().get_context(window) { + let written = wt.xconn.lookup_utf8(ic, xkev); + if !written.is_empty() { + let event = Event::WindowEvent { + window_id, + event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), + }; + callback(event); + + let event = Event::WindowEvent { + window_id, + event: WindowEvent::Ime(Ime::Commit(written)), + }; + + self.is_composing = false; + callback(event); + } } } @@ -1029,40 +1051,6 @@ impl EventProcessor { }); } } - - // The regular KeyPress event has a problem where if you press a dead key, a KeyPress - // event won't be emitted. XInput 2 does not have this problem. - ffi::XI_KeyPress | ffi::XI_KeyRelease if !self.is_composing => { - if let Some(active_window) = self.active_window { - let state = if xev.evtype == ffi::XI_KeyPress { - Pressed - } else { - Released - }; - - let xkev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - - // We use `self.active_window` here as `xkev.event` has a completely different - // value for some reason. - let window_id = mkwid(active_window); - - let device_id = mkdid(xkev.deviceid); - let keycode = xkev.detail as u32; - - let repeat = xkev.flags & ffi::XIKeyRepeat == ffi::XIKeyRepeat; - let event = self.kb_state.process_key_event(keycode, state, repeat); - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - device_id, - event, - is_synthetic: false, - }, - }); - } - } - ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index e0e166cb..4400b36b 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -188,6 +188,15 @@ impl EventLoop { panic!("X server missing XKB extension"); } + // Enable detectable auto repeat. + let mut supported = 0; + unsafe { + (xconn.xlib.XkbSetDetectableAutoRepeat)(xconn.display, 1, &mut supported); + } + if supported == 0 { + warn!("Detectable auto repeart is not supported"); + } + ext }; @@ -283,6 +292,7 @@ impl EventLoop { first_touch: None, active_window: None, is_composing: false, + got_key_release: true, }; // Register for device hotplug events diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 550b612c..e64d4178 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -245,6 +245,7 @@ impl UnownedWindow { | ffi::StructureNotifyMask | ffi::VisibilityChangeMask | ffi::KeyPressMask + | ffi::KeyReleaseMask | ffi::KeymapStateMask | ffi::ButtonPressMask | ffi::ButtonReleaseMask @@ -446,8 +447,6 @@ impl UnownedWindow { let mask = ffi::XI_MotionMask | ffi::XI_ButtonPressMask | ffi::XI_ButtonReleaseMask - | ffi::XI_KeyPressMask - | ffi::XI_KeyReleaseMask | ffi::XI_EnterMask | ffi::XI_LeaveMask | ffi::XI_FocusInMask diff --git a/src/window.rs b/src/window.rs index 1b370cd1..c61b5d99 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1074,6 +1074,7 @@ impl Window { /// /// - **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined. /// - **iOS / Android / Web / Orbital:** Unsupported. + /// - **X11**: Enabling IME will disable dead keys reporting during compose. /// /// [`Ime`]: crate::event::WindowEvent::Ime /// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput