Skip to content

Commit

Permalink
On X11, replay modifiers consumed by XIM
Browse files Browse the repository at this point in the history
  • Loading branch information
kchibisov committed Feb 26, 2024
1 parent c7a4a3c commit d5a2a18
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 14 deletions.
102 changes: 96 additions & 6 deletions src/platform_impl/linux/x11/event_processor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::collections::{HashMap, VecDeque};
use std::os::raw::{c_char, c_int, c_long, c_ulong};
use std::slice;
use std::sync::{Arc, Mutex};
Expand Down Expand Up @@ -38,6 +38,9 @@ use crate::platform_impl::x11::{
GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
};

/// The maximum amount of X modifiers to replay.
pub const MAX_MOD_REPLAY_LEN: usize = 32;

/// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`".
const KEYCODE_OFFSET: u8 = 8;

Expand All @@ -63,6 +66,8 @@ pub struct EventProcessor<T: 'static> {
pub active_window: Option<xproto::Window>,
/// Latest modifiers we've sent for the user to trigger change in event.
pub modifiers: Cell<ModifiersState>,
pub xfiltered_modifiers: VecDeque<c_ulong>,
pub xmodmap: util::ModifierKeymap,
pub is_composing: bool,
}

Expand Down Expand Up @@ -138,11 +143,25 @@ impl<T: 'static> EventProcessor<T> {
where
F: FnMut(&RootELW<T>, Event<T>),
{
let event_type = xev.get_type();

if self.filter_event(xev) {
if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {
let xev: &XKeyEvent = xev.as_ref();
if self.xmodmap.is_modifier(xev.keycode as u8) {
// Don't grow the buffer past the `MAX_MOD_REPLAY_LEN`. This could happen
// when the modifiers are consumed entirely or serials are altered.
//
// Both cases shouldn't happen in well behaving clients.
if self.xfiltered_modifiers.len() == MAX_MOD_REPLAY_LEN {
self.xfiltered_modifiers.pop_back();
}
self.xfiltered_modifiers.push_front(xev.serial);
}
}
return;
}

let event_type = xev.get_type();
match event_type {
xlib::ClientMessage => self.client_message(xev.as_ref(), &mut callback),
xlib::SelectionNotify => self.selection_notify(xev.as_ref(), &mut callback),
Expand Down Expand Up @@ -936,10 +955,35 @@ impl<T: 'static> EventProcessor<T> {
false
};

// Always update the modifiers.
self.udpate_mods_from_core_event(window_id, xev.state as u16, &mut callback);
// NOTE: When the modifier was captured by the XFilterEvents the modifiers for the modifier
// itself are out of sync due to XkbState being delivered before XKeyEvent, since it's
// being replayed by the XIM, thus we should replay ourselves.
let replay = if let Some(position) = self
.xfiltered_modifiers
.iter()
.rev()
.position(|&s| s == xev.serial)
{
// We don't have to replay modifiers pressed before the current event if some events
// were not forwarded to us, since their state is irrelevant.
self.xfiltered_modifiers
.resize(self.xfiltered_modifiers.len() - 1 - position, 0);
true
} else {
false
};

// Always update the modifiers when we're not replaying.
if !replay {
self.udpate_mods_from_core_event(window_id, xev.state as u16, &mut callback);
}

if keycode != 0 && !self.is_composing {
// Don't alter the modifiers state from replaying.
if replay {
self.send_synthic_modifier_from_core(window_id, xev.state as u16, &mut callback);
}

if let Some(mut key_processor) = self.xkb_context.key_context() {
let event = key_processor.process_key_event(keycode, state, repeat);
let event = Event::WindowEvent {
Expand All @@ -953,6 +997,11 @@ impl<T: 'static> EventProcessor<T> {
callback(&self.target, event);
}

// Restore the client's modifiers state after replay.
if replay {
self.send_modifiers(window_id, self.modifiers.get(), true, &mut callback);
}

return;
}

Expand Down Expand Up @@ -982,6 +1031,41 @@ impl<T: 'static> EventProcessor<T> {
}
}

fn send_synthic_modifier_from_core<F>(
&mut self,
window_id: crate::window::WindowId,
state: u16,
mut callback: F,
) where
F: FnMut(&RootELW<T>, Event<T>),
{
let keymap = match self.xkb_context.keymap_mut() {
Some(keymap) => keymap,
None => return,
};

let wt = Self::window_target(&self.target);
let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection();

// Use synthetic state since we're replaying the modifier. The user modifier state
// will be restored later.
let mut xkb_state = match XkbState::new_x11(xcb, keymap) {
Some(xkb_state) => xkb_state,
None => return,
};

let mask = self.xkb_mod_mask_from_core(state);
xkb_state.update_modifiers(mask, 0, 0, 0, 0, Self::core_keyboard_group(state));
let mods: ModifiersState = xkb_state.modifiers().into();

let event = Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(mods.into()),
};

callback(&self.target, event);
}

fn xinput2_button_input<F>(&self, event: &XIDeviceEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootELW<T>, Event<T>),
Expand Down Expand Up @@ -1551,6 +1635,7 @@ impl<T: 'static> EventProcessor<T> {
{
let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection();
self.xkb_context.set_keymap_from_x11(xcb);
self.xmodmap.reload_from_x_connection(&wt.xconn);

let window_id = match self.active_window.map(super::mkwid) {
Some(window_id) => window_id,
Expand All @@ -1566,6 +1651,7 @@ impl<T: 'static> EventProcessor<T> {
xlib::XkbMapNotify => {
let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection();
self.xkb_context.set_keymap_from_x11(xcb);
self.xmodmap.reload_from_x_connection(&wt.xconn);
let window_id = match self.active_window.map(super::mkwid) {
Some(window_id) => window_id,
None => return,
Expand Down Expand Up @@ -1694,14 +1780,18 @@ impl<T: 'static> EventProcessor<T> {
locked,
0,
0,
// Bits 13 and 14 report the state keyboard group.
((state >> 13) & 3) as u32,
Self::core_keyboard_group(state),
);

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

// Bits 13 and 14 report the state keyboard group.
pub fn core_keyboard_group(state: u16) -> u32 {
((state >> 13) & 3) as u32
}

pub fn xkb_mod_mask_from_core(&mut self, state: u16) -> xkb_mod_mask_t {
let mods_indices = match self.xkb_context.keymap_mut() {
Some(keymap) => keymap.mods_indices(),
Expand Down
9 changes: 7 additions & 2 deletions src/platform_impl/linux/x11/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![cfg(x11_platform)]

use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::CStr;
use std::fmt;
use std::marker::PhantomData;
Expand Down Expand Up @@ -58,7 +58,7 @@ mod xsettings;

use atoms::*;
use dnd::{Dnd, DndState};
use event_processor::EventProcessor;
use event_processor::{EventProcessor, MAX_MOD_REPLAY_LEN};
use ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender};
pub(crate) use monitor::{MonitorHandle, VideoMode};
use window::UnownedWindow;
Expand Down Expand Up @@ -289,6 +289,9 @@ impl<T: 'static> EventLoop<T> {
let xkb_context =
Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();

let mut xmodmap = util::ModifierKeymap::new();
xmodmap.reload_from_x_connection(&xconn);

let window_target = EventLoopWindowTarget {
ime,
root,
Expand Down Expand Up @@ -327,6 +330,8 @@ impl<T: 'static> EventLoop<T> {
ime_receiver,
ime_event_receiver,
xi2ext,
xfiltered_modifiers: VecDeque::with_capacity(MAX_MOD_REPLAY_LEN),
xmodmap,
xkbext,
xkb_context,
num_touch: 0,
Expand Down
15 changes: 9 additions & 6 deletions src/platform_impl/linux/x11/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// Welcome to the util module, where we try to keep you from shooting yourself in the foot.
// *results may vary

use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
};

mod client_msg;
mod cursor;
mod geometry;
Expand All @@ -12,13 +18,10 @@ pub(crate) mod memory;
mod randr;
mod window_property;
mod wm;
mod xmodmap;

pub use self::{geometry::*, hint::*, input::*, window_property::*, wm::*};

use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
pub use self::{
geometry::*, hint::*, input::*, window_property::*, wm::*, xmodmap::ModifierKeymap,
};

use super::{atoms::*, ffi, VoidCookie, X11Error, XConnection, XError};
Expand Down
56 changes: 56 additions & 0 deletions src/platform_impl/linux/x11/util/xmodmap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::collections::HashSet;
use std::slice;

use x11_dl::xlib::{KeyCode as XKeyCode, XModifierKeymap};

// 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 NUM_MODS: usize = 8;

/// Track which keys are modifiers, so we can properly replay them when they were filtered.
#[derive(Debug, Default)]
pub struct ModifierKeymap {
// Maps keycodes to modifiers
modifers: HashSet<XKeyCode>,
}

impl ModifierKeymap {
pub fn new() -> ModifierKeymap {
ModifierKeymap::default()
}

pub fn is_modifier(&self, keycode: XKeyCode) -> bool {
self.modifers.contains(&keycode)
}

pub fn reload_from_x_connection(&mut self, xconn: &super::XConnection) {
unsafe {
let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display);

if keymap.is_null() {
return;
}

self.reset_from_x_keymap(&*keymap);

(xconn.xlib.XFreeModifiermap)(keymap);
}
}

fn reset_from_x_keymap(&mut self, keymap: &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.modifers.clear();
for key in keys {
self.modifers.insert(*key);
}
}
}

0 comments on commit d5a2a18

Please sign in to comment.