-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix X11 KeyboardInput events sending with ibus input method. #2182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ use super::{ | |
| XExtension, | ||
| }; | ||
|
|
||
| use util::modifiers::{ModifierKeyState, ModifierKeymap}; | ||
| use util::modifiers::{Modifier, ModifierKeyState, ModifierKeymap}; | ||
|
|
||
| use crate::{ | ||
| dpi::{PhysicalPosition, PhysicalSize}, | ||
|
|
@@ -37,6 +37,7 @@ pub(super) struct EventProcessor<T: 'static> { | |
| pub(super) first_touch: Option<u64>, | ||
| // Currently focused window belonging to this process | ||
| pub(super) active_window: Option<ffi::Window>, | ||
| pub(super) key_event_times: [ffi::Time; 256], | ||
| } | ||
|
|
||
| impl<T: 'static> EventProcessor<T> { | ||
|
|
@@ -129,30 +130,21 @@ impl<T: 'static> EventProcessor<T> { | |
| }) | ||
| } | ||
| { | ||
| if xev.get_type() == ffi::KeyPress || xev.get_type() == ffi::KeyRelease { | ||
| Self::handle_key_event( | ||
| xev, | ||
| true, | ||
| wt, | ||
| &self.mod_keymap, | ||
| &mut self.key_event_times, | ||
| &mut self.device_mod_state, | ||
| self.active_window, | ||
| &mut callback, | ||
| ); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| // We can't call a `&mut self` method because of the above borrow, | ||
| // so we use this macro for repeated modifier state updates. | ||
| macro_rules! update_modifiers { | ||
| ( $state:expr , $modifier:expr ) => {{ | ||
| match ($state, $modifier) { | ||
| (state, modifier) => { | ||
| if let Some(modifiers) = | ||
| self.device_mod_state.update_state(&state, modifier) | ||
| { | ||
| if let Some(window_id) = self.active_window { | ||
| callback(Event::WindowEvent { | ||
| window_id: mkwid(window_id), | ||
| event: WindowEvent::ModifiersChanged(modifiers), | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }}; | ||
| } | ||
|
|
||
| let event_type = xev.get_type(); | ||
| match event_type { | ||
| ffi::MappingNotify => { | ||
|
|
@@ -545,71 +537,16 @@ impl<T: 'static> EventProcessor<T> { | |
| } | ||
|
|
||
| ffi::KeyPress | ffi::KeyRelease => { | ||
| use crate::event::ElementState::{Pressed, Released}; | ||
|
|
||
| // Note that in compose/pre-edit sequences, this will always be Released. | ||
| let state = if xev.get_type() == ffi::KeyPress { | ||
| Pressed | ||
| } else { | ||
| Released | ||
| }; | ||
|
|
||
| let xkev: &mut ffi::XKeyEvent = xev.as_mut(); | ||
|
|
||
| let window = xkev.window; | ||
| let window_id = mkwid(window); | ||
|
|
||
| // Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable | ||
| // value, though this should only be an issue under multiseat configurations. | ||
| let device = util::VIRTUAL_CORE_KEYBOARD; | ||
| let device_id = mkdid(device); | ||
| let keycode = xkev.keycode; | ||
|
|
||
| // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with | ||
| // a keycode of 0. | ||
| if keycode != 0 { | ||
| let scancode = keycode - KEYCODE_OFFSET as u32; | ||
| let keysym = wt.xconn.lookup_keysym(xkev); | ||
| let virtual_keycode = events::keysym_to_element(keysym as c_uint); | ||
|
|
||
| update_modifiers!( | ||
| ModifiersState::from_x11_mask(xkev.state), | ||
| self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode) | ||
| ); | ||
|
|
||
| let modifiers = self.device_mod_state.modifiers(); | ||
|
|
||
| #[allow(deprecated)] | ||
| callback(Event::WindowEvent { | ||
| window_id, | ||
| event: WindowEvent::KeyboardInput { | ||
| device_id, | ||
| input: KeyboardInput { | ||
| state, | ||
| scancode, | ||
| virtual_keycode, | ||
| modifiers, | ||
| }, | ||
| is_synthetic: false, | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| if state == Pressed { | ||
| let written = if let Some(ic) = wt.ime.borrow().get_context(window) { | ||
| wt.xconn.lookup_utf8(ic, xkev) | ||
| } else { | ||
| return; | ||
| }; | ||
|
|
||
| for chr in written.chars() { | ||
| let event = Event::WindowEvent { | ||
| window_id, | ||
| event: WindowEvent::ReceivedCharacter(chr), | ||
| }; | ||
| callback(event); | ||
| } | ||
| } | ||
| Self::handle_key_event( | ||
| xev, | ||
| false, | ||
| wt, | ||
| &self.mod_keymap, | ||
| &mut self.key_event_times, | ||
| &mut self.device_mod_state, | ||
| self.active_window, | ||
| &mut callback, | ||
| ); | ||
| } | ||
|
|
||
| ffi::GenericEvent => { | ||
|
|
@@ -645,7 +582,13 @@ impl<T: 'static> EventProcessor<T> { | |
| } | ||
|
|
||
| let modifiers = ModifiersState::from_x11(&xev.mods); | ||
| update_modifiers!(modifiers, None); | ||
| Self::update_modifiers( | ||
| &mut self.device_mod_state, | ||
| self.active_window, | ||
| modifiers, | ||
| None, | ||
| &mut callback, | ||
| ); | ||
|
|
||
| let state = if xev.evtype == ffi::XI_ButtonPress { | ||
| Pressed | ||
|
|
@@ -722,7 +665,13 @@ impl<T: 'static> EventProcessor<T> { | |
| let new_cursor_pos = (xev.event_x, xev.event_y); | ||
|
|
||
| let modifiers = ModifiersState::from_x11(&xev.mods); | ||
| update_modifiers!(modifiers, None); | ||
| Self::update_modifiers( | ||
| &mut self.device_mod_state, | ||
| self.active_window, | ||
| modifiers, | ||
| None, | ||
| &mut callback, | ||
| ); | ||
|
|
||
| let cursor_moved = self.with_window(xev.event, |window| { | ||
| let mut shared_state_lock = window.shared_state.lock(); | ||
|
|
@@ -1225,6 +1174,122 @@ impl<T: 'static> EventProcessor<T> { | |
| } | ||
| } | ||
|
|
||
| fn update_modifiers<F>( | ||
| device_mod_state: &mut ModifierKeyState, | ||
| active_window: Option<ffi::Window>, | ||
| state: ModifiersState, | ||
| modifier: Option<Modifier>, | ||
| callback: &mut F, | ||
| ) where | ||
| F: FnMut(Event<'_, T>), | ||
| { | ||
| if let Some(modifiers) = device_mod_state.update_state(&state, modifier) { | ||
| if let Some(window_id) = active_window { | ||
| callback(Event::WindowEvent { | ||
| window_id: mkwid(window_id), | ||
| event: WindowEvent::ModifiersChanged(modifiers), | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn handle_key_event<F>( | ||
| xev: &mut ffi::XEvent, | ||
| filtered_by_im: bool, | ||
| wt: &super::EventLoopWindowTarget<T>, | ||
| mod_keymap: &ModifierKeymap, | ||
| key_event_times: &mut [ffi::Time; 256], | ||
| device_mod_state: &mut ModifierKeyState, | ||
| active_window: Option<ffi::Window>, | ||
| callback: &mut F, | ||
| ) where | ||
| F: FnMut(Event<'_, T>), | ||
| { | ||
| use crate::event::ElementState::{Pressed, Released}; | ||
|
|
||
| // Note that in compose/pre-edit sequences, this will always be Released. | ||
| let state = if xev.get_type() == ffi::KeyPress { | ||
| Pressed | ||
| } else { | ||
| Released | ||
| }; | ||
|
|
||
| let xkev: &mut ffi::XKeyEvent = xev.as_mut(); | ||
|
|
||
| let window = xkev.window; | ||
| let window_id = mkwid(window); | ||
|
|
||
| // Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable | ||
| // value, though this should only be an issue under multiseat configurations. | ||
| let device = util::VIRTUAL_CORE_KEYBOARD; | ||
| let device_id = mkdid(device); | ||
| let keycode = xkev.keycode; | ||
|
|
||
| // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with | ||
| // a keycode of 0. | ||
| if keycode != 0 { | ||
| let signed_time = xkev.time as i64; | ||
| let time_since_previous_event = | ||
| signed_time.wrapping_sub(key_event_times[keycode as usize] as i64); | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this look suspicious - casting the new time and the time that it's being compared with into i64 values will defeat the purpose of using wrapping_sub to account for the 32bit timestamp wrapping around I think? |
||
| // An event is a replay of a previous event if it: | ||
| // 1) Isn't filtered by the IM. | ||
| // 2) Isn't the first time that an event with this keycode arrives. | ||
| // 3) Has a timestamp earlier or equal to the latest non-replayed event with this keycode. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could maybe also be good for future readers to have some comments about why it's necessary to filter out these repeat events. This is kind of a hacky workaround really, so it feels like that should be really clearly sign posted in the code. |
||
| let replayed_event = !filtered_by_im | ||
| && time_since_previous_event != signed_time | ||
| && time_since_previous_event <= 0; | ||
|
|
||
| if !replayed_event { | ||
| key_event_times[keycode as usize] = xkev.time; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. considering the |
||
| let scancode = keycode - KEYCODE_OFFSET as u32; | ||
| let keysym = wt.xconn.lookup_keysym(xkev); | ||
| let virtual_keycode = events::keysym_to_element(keysym as c_uint); | ||
|
|
||
| Self::update_modifiers( | ||
| device_mod_state, | ||
| active_window, | ||
| ModifiersState::from_x11_mask(xkev.state), | ||
| mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode), | ||
| callback, | ||
| ); | ||
|
|
||
| let modifiers = device_mod_state.modifiers(); | ||
|
|
||
| #[allow(deprecated)] | ||
| callback(Event::WindowEvent { | ||
| window_id, | ||
| event: WindowEvent::KeyboardInput { | ||
| device_id, | ||
| input: KeyboardInput { | ||
| state, | ||
| scancode, | ||
| virtual_keycode, | ||
| modifiers, | ||
| }, | ||
| is_synthetic: false, | ||
| }, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| if state == Pressed && !filtered_by_im { | ||
| let written = if let Some(ic) = wt.ime.borrow().get_context(window) { | ||
| wt.xconn.lookup_utf8(ic, xkev) | ||
| } else { | ||
| return; | ||
| }; | ||
|
|
||
| for chr in written.chars() { | ||
| let event = Event::WindowEvent { | ||
| window_id, | ||
| event: WindowEvent::ReceivedCharacter(chr), | ||
| }; | ||
| callback(event); | ||
| } | ||
| } | ||
|
Comment on lines
+1276
to
+1290
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This early return feels a little easy to break in a future refactor. Perhaps something a bit less brittle like the following? if state == Pressed && !filtered_by_im {
if let Some(ic) = wt.ime.borrow().get_context(window) {
let written = wt.xconn.lookup_utf8(ic, xkev);
for chr in written.chars() {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(chr),
};
callback(event);
}
}
} |
||
| } | ||
|
|
||
| fn handle_pressed_keys<F>( | ||
| wt: &super::EventLoopWindowTarget<T>, | ||
| window_id: crate::window::WindowId, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps
256 - KEYCODE_OFFSETgiven that we know the last 8 entries will never be used?