Skip to content

Commit

Permalink
On X11, force resend modifiers when focus changes
Browse files Browse the repository at this point in the history
Given that `ModifiersChanged` is a window event, it means that clients
may track it for each window individually, thus not sending it between
focus changes may result in modifiers getting desynced on the consumer
side.
  • Loading branch information
kchibisov authored Feb 22, 2024
1 parent a8c7109 commit 4a8648b
Showing 1 changed file with 109 additions and 31 deletions.
140 changes: 109 additions & 31 deletions src/platform_impl/linux/x11/event_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use x11_dl::xinput2::{
use x11_dl::xlib::{
self, Display as XDisplay, Window as XWindow, XAnyEvent, XClientMessageEvent, XConfigureEvent,
XDestroyWindowEvent, XEvent, XExposeEvent, XKeyEvent, XMapEvent, XPropertyEvent,
XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent,
XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent, XkbStateRec,
};
use x11rb::protocol::xinput;
use x11rb::protocol::xkb::ID as XkbId;
use x11rb::protocol::xproto::{self, ConnectionExt as _, ModMask};
use x11rb::x11_utils::ExtensionInformation;
use x11rb::x11_utils::Serialize;
Expand Down Expand Up @@ -180,12 +181,22 @@ impl EventProcessor {
};

let xev: &XIDeviceEvent = unsafe { &*(xev.data as *const _) };
self.update_mods_from_xinput2_event(&xev.mods, &xev.group, &mut callback);
self.update_mods_from_xinput2_event(
&xev.mods,
&xev.group,
false,
&mut callback,
);
self.xinput2_button_input(xev, state, &mut callback);
}
xinput2::XI_Motion => {
let xev: &XIDeviceEvent = unsafe { &*(xev.data as *const _) };
self.update_mods_from_xinput2_event(&xev.mods, &xev.group, &mut callback);
self.update_mods_from_xinput2_event(
&xev.mods,
&xev.group,
false,
&mut callback,
);
self.xinput2_mouse_motion(xev, &mut callback);
}
xinput2::XI_Enter => {
Expand All @@ -194,7 +205,12 @@ impl EventProcessor {
}
xinput2::XI_Leave => {
let xev: &XILeaveEvent = unsafe { &*(xev.data as *const _) };
self.update_mods_from_xinput2_event(&xev.mods, &xev.group, &mut callback);
self.update_mods_from_xinput2_event(
&xev.mods,
&xev.group,
false,
&mut callback,
);
self.xinput2_mouse_left(xev, &mut callback);
}
xinput2::XI_FocusIn => {
Expand Down Expand Up @@ -925,7 +941,7 @@ impl EventProcessor {
};

// Always update the modifiers.
self.udpate_mods_from_core_event(xev.state as u16, &mut callback);
self.udpate_mods_from_core_event(window_id, xev.state as u16, &mut callback);

if keycode != 0 && !self.is_composing {
if let Some(mut key_processor) = self.xkb_context.key_context() {
Expand Down Expand Up @@ -1255,10 +1271,7 @@ impl EventProcessor {
&mut callback,
);

if let Some(state) = self.xkb_context.state_mut() {
let mods = state.modifiers().into();
self.send_modifiers(mods, &mut callback);
}
self.update_mods_from_query(window_id, &mut callback);

// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
Expand Down Expand Up @@ -1304,7 +1317,12 @@ impl EventProcessor {

wt.update_listen_device_events(false);

self.send_modifiers(ModifiersState::empty(), &mut callback);
// Clear the modifiers when unfocusing the window.
if let Some(xkb_state) = self.xkb_context.state_mut() {
xkb_state.update_modifiers(0, 0, 0, 0, 0, 0);
let mods = xkb_state.modifiers();
self.send_modifiers(window_id, mods.into(), true, &mut callback);
}

// Issue key release events for all pressed keys
Self::handle_pressed_keys(
Expand Down Expand Up @@ -1554,18 +1572,28 @@ impl EventProcessor {
let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection();
self.xkb_context.set_keymap_from_x11(xcb);

let window_id = match self.active_window.map(super::mkwid) {
Some(window_id) => window_id,
None => return,
};

if let Some(state) = self.xkb_context.state_mut() {
let mods = state.modifiers().into();
self.send_modifiers(mods, &mut callback);
self.send_modifiers(window_id, mods, true, &mut callback);
}
}
}
xlib::XkbMapNotify => {
let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection();
self.xkb_context.set_keymap_from_x11(xcb);
let window_id = match self.active_window.map(super::mkwid) {
Some(window_id) => window_id,
None => return,
};

if let Some(state) = self.xkb_context.state_mut() {
let mods = state.modifiers().into();
self.send_modifiers(mods, &mut callback);
self.send_modifiers(window_id, mods, true, &mut callback);
}
}
xlib::XkbStateNotify => {
Expand All @@ -1583,8 +1611,14 @@ impl EventProcessor {
xev.latched_group as u32,
xev.locked_group as u32,
);

let window_id = match self.active_window.map(super::mkwid) {
Some(window_id) => window_id,
None => return,
};

let mods = state.modifiers().into();
self.send_modifiers(mods, &mut callback);
self.send_modifiers(window_id, mods, true, &mut callback);
}
}
_ => {}
Expand All @@ -1595,6 +1629,7 @@ impl EventProcessor {
&mut self,
mods: &XIModifierState,
group: &XIModifierState,
force: bool,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
Expand All @@ -1609,13 +1644,58 @@ impl EventProcessor {
group.locked as u32,
);

// NOTE: we use active window since generally sub windows don't have keyboard input,
// and winit assumes that unfocused window doesn't have modifiers.
let window_id = match self.active_window.map(super::mkwid) {
Some(window_id) => window_id,
None => return,
};

let mods = state.modifiers();
self.send_modifiers(mods.into(), &mut callback);
self.send_modifiers(window_id, mods.into(), force, &mut callback);
}
}

pub fn udpate_mods_from_core_event<T: 'static, F>(&mut self, state: u16, mut callback: F)
where
fn update_mods_from_query<T: 'static, F>(
&mut self,
window_id: crate::window::WindowId,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);

let xkb_state = match self.xkb_context.state_mut() {
Some(xkb_state) => xkb_state,
None => return,
};

unsafe {
let mut state: XkbStateRec = std::mem::zeroed();
if (wt.xconn.xlib.XkbGetState)(wt.xconn.display, XkbId::USE_CORE_KBD.into(), &mut state)
== xlib::True
{
xkb_state.update_modifiers(
state.base_mods as u32,
state.latched_mods as u32,
state.locked_mods as u32,
state.base_group as u32,
state.latched_group as u32,
state.locked_group as u32,
);
}
}

let mods = xkb_state.modifiers();
self.send_modifiers(window_id, mods.into(), true, &mut callback)
}

pub fn udpate_mods_from_core_event<T: 'static, F>(
&mut self,
window_id: crate::window::WindowId,
state: u16,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let xkb_mask = self.xkb_mod_mask_from_core(state);
Expand All @@ -1642,7 +1722,7 @@ impl EventProcessor {
);

let mods = xkb_state.modifiers();
self.send_modifiers(mods.into(), &mut callback);
self.send_modifiers(window_id, mods.into(), false, &mut callback);
}

pub fn xkb_mod_mask_from_core(&mut self, state: u16) -> xkb_mod_mask_t {
Expand Down Expand Up @@ -1692,25 +1772,23 @@ impl EventProcessor {

/// Send modifiers for the active window.
///
/// The event won't be sent when the `modifiers` match the previously `sent` modifiers value.
/// The event won't be sent when the `modifiers` match the previously `sent` modifiers value,
/// unless `force` is passed. The `force` should be passed when the active window changes.
fn send_modifiers<T: 'static, F: FnMut(&RootAEL, Event<T>)>(
&self,
window_id: crate::window::WindowId,
modifiers: ModifiersState,
force: bool,
callback: &mut F,
) {
let window_id = match self.active_window {
Some(window) => mkwid(window),
None => return,
};

if self.modifiers.replace(modifiers) != modifiers {
callback(
&self.target,
Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(self.modifiers.get().into()),
},
);
// NOTE: Always update the modifiers to account for case when they've changed
// and forced was `true`.
if self.modifiers.replace(modifiers) != modifiers || force {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(self.modifiers.get().into()),
};
callback(&self.target, event);
}
}

Expand Down

0 comments on commit 4a8648b

Please sign in to comment.