mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-25 06:41:31 +11:00
Implement WindowEvent ModifiersChanged for X11 and Wayland (#1132)
* Implement WindowEvent ModifiersChanged for X11 and Wayland * Fix modifier key state desync on X11 * Run cargo fmt
This commit is contained in:
parent
bfcd85ab15
commit
206c3c246c
|
@ -133,6 +133,9 @@ pub enum WindowEvent {
|
||||||
input: KeyboardInput,
|
input: KeyboardInput,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Keyboard modifiers have changed
|
||||||
|
ModifiersChanged { modifiers: ModifiersState },
|
||||||
|
|
||||||
/// The cursor has moved on the window.
|
/// The cursor has moved on the window.
|
||||||
CursorMoved {
|
CursorMoved {
|
||||||
device_id: DeviceId,
|
device_id: DeviceId,
|
||||||
|
|
|
@ -83,7 +83,17 @@ pub fn init_keyboard(
|
||||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
||||||
KbEvent::Modifiers {
|
KbEvent::Modifiers {
|
||||||
modifiers: event_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, _| {
|
move |repeat_event: KeyRepeatEvent, _| {
|
||||||
|
|
|
@ -8,6 +8,8 @@ use super::{
|
||||||
XExtension,
|
XExtension,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use util::modifiers::{ModifierKeyState, ModifierKeymap};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
|
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
|
||||||
|
@ -21,6 +23,9 @@ pub(super) struct EventProcessor<T: 'static> {
|
||||||
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
|
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
|
||||||
pub(super) xi2ext: XExtension,
|
pub(super) xi2ext: XExtension,
|
||||||
pub(super) target: Rc<RootELW<T>>,
|
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> {
|
impl<T: 'static> EventProcessor<T> {
|
||||||
|
@ -112,12 +117,22 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
let event_type = xev.get_type();
|
let event_type = xev.get_type();
|
||||||
match event_type {
|
match event_type {
|
||||||
ffi::MappingNotify => {
|
ffi::MappingNotify => {
|
||||||
unsafe {
|
let mapping: &ffi::XMappingEvent = xev.as_ref();
|
||||||
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
|
|
||||||
|
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 => {
|
ffi::ClientMessage => {
|
||||||
|
@ -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
|
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
|
||||||
// a keycode of 0.
|
// a keycode of 0.
|
||||||
if xkev.keycode != 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 keysym = unsafe {
|
||||||
let mut keysym = 0;
|
let mut keysym = 0;
|
||||||
(wt.xconn.xlib.XLookupString)(
|
(wt.xconn.xlib.XLookupString)(
|
||||||
|
@ -535,6 +543,8 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
};
|
};
|
||||||
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
||||||
|
|
||||||
|
let modifiers = self.window_mod_state.modifiers();
|
||||||
|
|
||||||
callback(Event::WindowEvent {
|
callback(Event::WindowEvent {
|
||||||
window_id,
|
window_id,
|
||||||
event: WindowEvent::KeyboardInput {
|
event: WindowEvent::KeyboardInput {
|
||||||
|
@ -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 {
|
if state == Pressed {
|
||||||
|
@ -859,6 +890,21 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
event: Focused(true),
|
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,
|
// The deviceid for this event is for a keyboard instead of a pointer,
|
||||||
// so we have to do a little extra work.
|
// so we have to do a little extra work.
|
||||||
let pointer_id = self
|
let pointer_id = self
|
||||||
|
@ -890,6 +936,22 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.unfocus(xev.event)
|
.unfocus(xev.event)
|
||||||
.expect("Failed to unfocus input context");
|
.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 {
|
callback(Event::WindowEvent {
|
||||||
window_id: mkwid(xev.event),
|
window_id: mkwid(xev.event),
|
||||||
event: Focused(false),
|
event: Focused(false),
|
||||||
|
@ -1022,18 +1084,25 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
|
|
||||||
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
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 {
|
callback(Event::DeviceEvent {
|
||||||
device_id: mkdid(device_id),
|
device_id: mkdid(device_id),
|
||||||
event: DeviceEvent::Key(KeyboardInput {
|
event: DeviceEvent::Key(KeyboardInput {
|
||||||
scancode,
|
scancode,
|
||||||
virtual_keycode,
|
virtual_keycode,
|
||||||
state,
|
state,
|
||||||
// So, in an ideal world we can use libxkbcommon to get modifiers.
|
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(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ use self::{
|
||||||
dnd::{Dnd, DndState},
|
dnd::{Dnd, DndState},
|
||||||
event_processor::EventProcessor,
|
event_processor::EventProcessor,
|
||||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
|
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
|
||||||
|
util::modifiers::ModifierKeymap,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
error::OsError as RootOsError,
|
error::OsError as RootOsError,
|
||||||
|
@ -143,6 +144,9 @@ impl<T: 'static> EventLoop<T> {
|
||||||
|
|
||||||
xconn.update_cached_wm_info(root);
|
xconn.update_cached_wm_info(root);
|
||||||
|
|
||||||
|
let mut mod_keymap = ModifierKeymap::new();
|
||||||
|
mod_keymap.reset_from_x_connection(&xconn);
|
||||||
|
|
||||||
let target = Rc::new(RootELW {
|
let target = Rc::new(RootELW {
|
||||||
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
|
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
|
||||||
ime,
|
ime,
|
||||||
|
@ -186,6 +190,9 @@ impl<T: 'static> EventLoop<T> {
|
||||||
randr_event_offset,
|
randr_event_offset,
|
||||||
ime_receiver,
|
ime_receiver,
|
||||||
xi2ext,
|
xi2ext,
|
||||||
|
mod_keymap,
|
||||||
|
device_mod_state: Default::default(),
|
||||||
|
window_mod_state: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register for device hotplug events
|
// Register for device hotplug events
|
||||||
|
|
|
@ -10,6 +10,7 @@ mod hint;
|
||||||
mod icon;
|
mod icon;
|
||||||
mod input;
|
mod input;
|
||||||
mod memory;
|
mod memory;
|
||||||
|
pub mod modifiers;
|
||||||
mod randr;
|
mod randr;
|
||||||
mod window_property;
|
mod window_property;
|
||||||
mod wm;
|
mod wm;
|
||||||
|
|
149
src/platform_impl/linux/x11/util/modifiers.rs
Normal file
149
src/platform_impl/linux/x11/util/modifiers.rs
Normal file
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue