Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ pub enum WindowEvent {
input: KeyboardInput,
},

/// Keyboard modifiers have changed
ModifiersChanged { modifiers: ModifiersState },

/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
Expand Down
12 changes: 11 additions & 1 deletion src/platform_impl/linux/wayland/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,17 @@ pub fn init_keyboard(
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
KbEvent::Modifiers {
modifiers: event_modifiers,
} => *modifiers_tracker.lock().unwrap() = event_modifiers.into(),
} => {
let modifiers = event_modifiers.into();

*modifiers_tracker.lock().unwrap() = modifiers;

if let Some(wid) = *target.lock().unwrap() {
my_sink
.send((WindowEvent::ModifiersChanged { modifiers }, wid))
.unwrap();
}
}
}
},
move |repeat_event: KeyRepeatEvent, _| {
Expand Down
105 changes: 87 additions & 18 deletions src/platform_impl/linux/x11/event_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use super::{
XExtension,
};

use util::modifiers::{ModifierKeyState, ModifierKeymap};

use crate::{
dpi::{LogicalPosition, LogicalSize},
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
Expand All @@ -21,6 +23,9 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
pub(super) xi2ext: XExtension,
pub(super) target: Rc<RootELW<T>>,
pub(super) mod_keymap: ModifierKeymap,
pub(super) device_mod_state: ModifierKeyState,
pub(super) window_mod_state: ModifierKeyState,
}

impl<T: 'static> EventProcessor<T> {
Expand Down Expand Up @@ -112,12 +117,22 @@ impl<T: 'static> EventProcessor<T> {
let event_type = xev.get_type();
match event_type {
ffi::MappingNotify => {
unsafe {
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
let mapping: &ffi::XMappingEvent = xev.as_ref();

if mapping.request == ffi::MappingModifier
|| mapping.request == ffi::MappingKeyboard
{
unsafe {
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
}
wt.xconn
.check_errors()
.expect("Failed to call XRefreshKeyboardMapping");

self.mod_keymap.reset_from_x_connection(&wt.xconn);
self.device_mod_state.update(&self.mod_keymap);
self.window_mod_state.update(&self.mod_keymap);
}
wt.xconn
.check_errors()
.expect("Failed to call XRefreshKeyboardMapping");
}

ffi::ClientMessage => {
Expand Down Expand Up @@ -514,13 +529,6 @@ impl<T: 'static> EventProcessor<T> {
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
// a keycode of 0.
if xkev.keycode != 0 {
let modifiers = ModifiersState {
alt: xkev.state & ffi::Mod1Mask != 0,
shift: xkev.state & ffi::ShiftMask != 0,
ctrl: xkev.state & ffi::ControlMask != 0,
logo: xkev.state & ffi::Mod4Mask != 0,
};

let keysym = unsafe {
let mut keysym = 0;
(wt.xconn.xlib.XLookupString)(
Expand All @@ -535,6 +543,8 @@ impl<T: 'static> EventProcessor<T> {
};
let virtual_keycode = events::keysym_to_element(keysym as c_uint);

let modifiers = self.window_mod_state.modifiers();

callback(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
Expand All @@ -547,6 +557,27 @@ impl<T: 'static> EventProcessor<T> {
},
},
});

if let Some(modifier) =
self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode)
{
self.window_mod_state.key_event(
state,
xkev.keycode as ffi::KeyCode,
modifier,
);

let new_modifiers = self.window_mod_state.modifiers();

if modifiers != new_modifiers {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged {
modifiers: new_modifiers,
},
});
}
}
}

if state == Pressed {
Expand Down Expand Up @@ -859,6 +890,21 @@ impl<T: 'static> EventProcessor<T> {
event: Focused(true),
});

// When focus is gained, send any existing modifiers
// to the window in a ModifiersChanged event. This is
// done to compensate for modifier keys that may be
// changed while a window is out of focus.
if !self.device_mod_state.is_empty() {
self.window_mod_state = self.device_mod_state.clone();

let modifiers = self.window_mod_state.modifiers();

callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged { modifiers },
});
}

// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
let pointer_id = self
Expand Down Expand Up @@ -890,6 +936,22 @@ impl<T: 'static> EventProcessor<T> {
.borrow_mut()
.unfocus(xev.event)
.expect("Failed to unfocus input context");

// When focus is lost, send a ModifiersChanged event
// containing no modifiers set. This is done to compensate
// for modifier keys that may be changed while a window
// is out of focus.
if !self.window_mod_state.is_empty() {
self.window_mod_state.clear();

callback(Event::WindowEvent {
window_id: mkwid(xev.event),
event: WindowEvent::ModifiersChanged {
modifiers: ModifiersState::default(),
},
});
}

callback(Event::WindowEvent {
window_id: mkwid(xev.event),
event: Focused(false),
Expand Down Expand Up @@ -1022,18 +1084,25 @@ impl<T: 'static> EventProcessor<T> {

let virtual_keycode = events::keysym_to_element(keysym as c_uint);

if let Some(modifier) =
self.mod_keymap.get_modifier(keycode as ffi::KeyCode)
{
self.device_mod_state.key_event(
state,
keycode as ffi::KeyCode,
modifier,
);
}

let modifiers = self.device_mod_state.modifiers();

callback(Event::DeviceEvent {
device_id: mkdid(device_id),
event: DeviceEvent::Key(KeyboardInput {
scancode,
virtual_keycode,
state,
// So, in an ideal world we can use libxkbcommon to get modifiers.
// However, libxkbcommon-x11 isn't as commonly installed as one
// would hope. We can still use the Xkb extension to get
// comprehensive keyboard state updates, but interpreting that
// info manually is going to be involved.
modifiers: ModifiersState::default(),
modifiers,
}),
});
}
Expand Down
7 changes: 7 additions & 0 deletions src/platform_impl/linux/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use self::{
dnd::{Dnd, DndState},
event_processor::EventProcessor,
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
util::modifiers::ModifierKeymap,
};
use crate::{
error::OsError as RootOsError,
Expand Down Expand Up @@ -143,6 +144,9 @@ impl<T: 'static> EventLoop<T> {

xconn.update_cached_wm_info(root);

let mut mod_keymap = ModifierKeymap::new();
mod_keymap.reset_from_x_connection(&xconn);

let target = Rc::new(RootELW {
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
ime,
Expand Down Expand Up @@ -186,6 +190,9 @@ impl<T: 'static> EventLoop<T> {
randr_event_offset,
ime_receiver,
xi2ext,
mod_keymap,
device_mod_state: Default::default(),
window_mod_state: Default::default(),
};

// Register for device hotplug events
Expand Down
1 change: 1 addition & 0 deletions src/platform_impl/linux/x11/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod hint;
mod icon;
mod input;
mod memory;
pub mod modifiers;
mod randr;
mod window_property;
mod wm;
Expand Down
149 changes: 149 additions & 0 deletions src/platform_impl/linux/x11/util/modifiers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use std::{collections::HashMap, slice};

use super::*;

use crate::event::{ElementState, ModifiersState};

// 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 SHIFT_OFFSET: usize = 0;
const CONTROL_OFFSET: usize = 2;
const ALT_OFFSET: usize = 3;
const LOGO_OFFSET: usize = 6;
const NUM_MODS: usize = 8;

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Modifier {
Alt,
Ctrl,
Shift,
Logo,
}

#[derive(Debug, Default)]
pub struct ModifierKeymap {
// Maps keycodes to modifiers
keys: HashMap<ffi::KeyCode, Modifier>,
}

#[derive(Clone, Debug, Default)]
pub struct ModifierKeyState {
// Contains currently pressed modifier keys and their corresponding modifiers
keys: HashMap<ffi::KeyCode, Modifier>,
}

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

pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option<Modifier> {
self.keys.get(&keycode).cloned()
}

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

if keymap.is_null() {
panic!("failed to allocate XModifierKeymap");
}

self.reset_from_x_keymap(&*keymap);

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

pub fn reset_from_x_keymap(&mut self, keymap: &ffi::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.keys.clear();

self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift);
self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl);
self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt);
self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo);
}

fn read_x_keys(
&mut self,
keys: &[ffi::KeyCode],
offset: usize,
keys_per_mod: usize,
modifier: Modifier,
) {
let start = offset * keys_per_mod;
let end = start + keys_per_mod;

for &keycode in &keys[start..end] {
if keycode != 0 {
self.keys.insert(keycode, modifier);
}
}
}
}

impl ModifierKeyState {
pub fn clear(&mut self) {
self.keys.clear();
}

pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}

pub fn update(&mut self, mods: &ModifierKeymap) {
self.keys.retain(|k, v| {
if let Some(m) = mods.get_modifier(*k) {
*v = m;
true
} else {
false
}
});
}

pub fn modifiers(&self) -> ModifiersState {
let mut state = ModifiersState::default();

for &m in self.keys.values() {
set_modifier(&mut state, m);
}

state
}

pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) {
match state {
ElementState::Pressed => self.key_press(keycode, modifier),
ElementState::Released => self.key_release(keycode),
}
}

pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) {
self.keys.insert(keycode, modifier);
}

pub fn key_release(&mut self, keycode: ffi::KeyCode) {
self.keys.remove(&keycode);
}
}

fn set_modifier(state: &mut ModifiersState, modifier: Modifier) {
match modifier {
Modifier::Alt => state.alt = true,
Modifier::Ctrl => state.ctrl = true,
Modifier::Shift => state.shift = true,
Modifier::Logo => state.logo = true,
}
}