X11: Sync key press/release with window focus (#1296)

* X11: Sync key press/release with window focus

* When a window loses focus, key release events are issued for all pressed keys
* When a window gains focus, key press events are issued for all pressed keys
* Adds `is_synthetic` field to `WindowEvent` variant `KeyboardInput`
  to indicate that these events are synthetic.
* Adds `is_synthetic: false` to `WindowEvent::KeyboardInput` events issued
  on all other platforms

* Clarify code with comments
This commit is contained in:
Murarth 2019-12-07 15:51:37 -07:00 committed by GitHub
parent 830d47a5f7
commit 35505a3114
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 180 additions and 31 deletions

View file

@ -10,6 +10,9 @@
- On Windows, fix focusing unfocused windows when switching from fullscreen to windowed. - On Windows, fix focusing unfocused windows when switching from fullscreen to windowed.
- On X11, fix reporting incorrect DPI factor when waking from suspend. - On X11, fix reporting incorrect DPI factor when waking from suspend.
- Change `EventLoopClosed` to contain the original event. - Change `EventLoopClosed` to contain the original event.
- Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`,
indicating that the event is generated by winit.
- On X11, generate synthetic key events for keys held when a window gains or loses focus.
# 0.20.0 Alpha 4 (2019-10-18) # 0.20.0 Alpha 4 (2019-10-18)

View file

@ -132,6 +132,15 @@ pub enum WindowEvent {
KeyboardInput { KeyboardInput {
device_id: DeviceId, device_id: DeviceId,
input: KeyboardInput, input: KeyboardInput,
/// If `true`, the event was generated synthetically by winit
/// in one of the following circumstances:
///
/// * **X11**: Synthetic key press events are generated for all keys pressed
/// when a window gains focus. Likewise, synthetic key release events
/// are generated for all keys pressed when a window goes out of focus.
///
/// Otherwise, this value is always `false`.
is_synthetic: bool,
}, },
/// The cursor has moved on the window. /// The cursor has moved on the window.

View file

@ -74,6 +74,7 @@ pub fn init_keyboard(
virtual_keycode: vkcode, virtual_keycode: vkcode,
modifiers: modifiers_tracker.lock().unwrap().clone(), modifiers: modifiers_tracker.lock().unwrap().clone(),
}, },
is_synthetic: false,
}, },
}) })
.unwrap(); .unwrap();
@ -125,6 +126,7 @@ pub fn init_keyboard(
virtual_keycode: vkcode, virtual_keycode: vkcode,
modifiers: my_modifiers.lock().unwrap().clone(), modifiers: my_modifiers.lock().unwrap().clone(),
}, },
is_synthetic: false,
}, },
}) })
.unwrap(); .unwrap();
@ -198,6 +200,7 @@ pub fn init_keyboard(
virtual_keycode: None, virtual_keycode: None,
modifiers: ModifiersState::default(), modifiers: ModifiersState::default(),
}, },
is_synthetic: false,
}, },
}) })
.unwrap(); .unwrap();

View file

@ -1,4 +1,4 @@
use std::{cell::RefCell, collections::HashMap, ptr, rc::Rc, slice}; use std::{cell::RefCell, collections::HashMap, rc::Rc, slice};
use libc::{c_char, c_int, c_long, c_uint, c_ulong}; use libc::{c_char, c_int, c_long, c_uint, c_ulong};
@ -12,7 +12,7 @@ use util::modifiers::{ModifierKeyState, ModifierKeymap};
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent}, event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::EventLoopWindowTarget as RootELW, event_loop::EventLoopWindowTarget as RootELW,
}; };
@ -557,22 +557,13 @@ impl<T: 'static> EventProcessor<T> {
// value, though this should only be an issue under multiseat configurations. // value, though this should only be an issue under multiseat configurations.
let device = util::VIRTUAL_CORE_KEYBOARD; let device = util::VIRTUAL_CORE_KEYBOARD;
let device_id = mkdid(device); 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 // 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 keycode != 0 {
let keysym = unsafe { let scancode = keycode - 8;
let mut keysym = 0; let keysym = wt.xconn.lookup_keysym(xkev);
(wt.xconn.xlib.XLookupString)(
xkev,
ptr::null_mut(),
0,
&mut keysym,
ptr::null_mut(),
);
wt.xconn.check_errors().expect("Failed to lookup keysym");
keysym
};
let virtual_keycode = events::keysym_to_element(keysym as c_uint); let virtual_keycode = events::keysym_to_element(keysym as c_uint);
update_modifiers!( update_modifiers!(
@ -588,10 +579,11 @@ impl<T: 'static> EventProcessor<T> {
device_id, device_id,
input: KeyboardInput { input: KeyboardInput {
state, state,
scancode: xkev.keycode - 8, scancode,
virtual_keycode, virtual_keycode,
modifiers, modifiers,
}, },
is_synthetic: false,
}, },
}); });
} }
@ -908,6 +900,10 @@ impl<T: 'static> EventProcessor<T> {
event: Focused(true), event: Focused(true),
}); });
let modifiers = ModifiersState::from_x11(&xev.mods);
update_modifiers!(modifiers, None);
// 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
@ -926,9 +922,12 @@ impl<T: 'static> EventProcessor<T> {
event: CursorMoved { event: CursorMoved {
device_id: mkdid(pointer_id), device_id: mkdid(pointer_id),
position, position,
modifiers: ModifiersState::from_x11(&xev.mods), modifiers,
}, },
}); });
// Issue key press events for all pressed keys
self.handle_pressed_keys(window_id, ElementState::Pressed, &mut callback);
} }
ffi::XI_FocusOut => { ffi::XI_FocusOut => {
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
@ -940,8 +939,13 @@ impl<T: 'static> EventProcessor<T> {
.unfocus(xev.event) .unfocus(xev.event)
.expect("Failed to unfocus input context"); .expect("Failed to unfocus input context");
let window_id = mkwid(xev.event);
// Issue key release events for all pressed keys
self.handle_pressed_keys(window_id, ElementState::Released, &mut callback);
callback(Event::WindowEvent { callback(Event::WindowEvent {
window_id: mkwid(xev.event), window_id,
event: Focused(false), event: Focused(false),
}) })
} }
@ -1058,20 +1062,8 @@ impl<T: 'static> EventProcessor<T> {
return; return;
} }
let scancode = (keycode - 8) as u32; let scancode = (keycode - 8) as u32;
let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode);
let keysym = unsafe {
(wt.xconn.xlib.XKeycodeToKeysym)(
wt.xconn.display,
xev.detail as ffi::KeyCode,
0,
)
};
wt.xconn
.check_errors()
.expect("Failed to lookup raw keysym");
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.device_mod_state.modifiers(); let modifiers = self.device_mod_state.modifiers();
callback(Event::DeviceEvent { callback(Event::DeviceEvent {
@ -1182,4 +1174,45 @@ impl<T: 'static> EventProcessor<T> {
Err(_) => (), Err(_) => (),
} }
} }
fn handle_pressed_keys<F>(
&self,
window_id: crate::window::WindowId,
state: ElementState,
callback: &mut F,
) where
F: FnMut(Event<T>),
{
let wt = get_xtarget(&self.target);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let modifiers = self.device_mod_state.modifiers();
// Get the set of keys currently pressed and apply Key events to each
let keys = wt.xconn.query_keymap();
for keycode in &keys {
if keycode < 8 {
continue;
}
let scancode = (keycode - 8) as u32;
let keysym = wt.xconn.keycode_to_keysym(keycode);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id,
input: KeyboardInput {
scancode,
state,
virtual_keycode,
modifiers,
},
is_synthetic: true,
},
});
}
}
} }

View file

@ -0,0 +1,92 @@
use std::{iter::Enumerate, ptr, slice::Iter};
use super::*;
pub struct Keymap {
keys: [u8; 32],
}
pub struct KeymapIter<'a> {
iter: Enumerate<Iter<'a, u8>>,
index: usize,
item: Option<u8>,
}
impl Keymap {
pub fn iter(&self) -> KeymapIter<'_> {
KeymapIter {
iter: self.keys.iter().enumerate(),
index: 0,
item: None,
}
}
}
impl<'a> IntoIterator for &'a Keymap {
type Item = ffi::KeyCode;
type IntoIter = KeymapIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl Iterator for KeymapIter<'_> {
type Item = ffi::KeyCode;
fn next(&mut self) -> Option<ffi::KeyCode> {
if self.item.is_none() {
while let Some((index, &item)) = self.iter.next() {
if item != 0 {
self.index = index;
self.item = Some(item);
break;
}
}
}
self.item.take().map(|item| {
debug_assert!(item != 0);
let bit = first_bit(item);
if item != bit {
// Remove the first bit; save the rest for further iterations
self.item = Some(item ^ bit);
}
let shift = bit.trailing_zeros() + (self.index * 8) as u32;
shift as ffi::KeyCode
})
}
}
impl XConnection {
pub fn keycode_to_keysym(&self, keycode: ffi::KeyCode) -> ffi::KeySym {
unsafe { (self.xlib.XKeycodeToKeysym)(self.display, keycode, 0) }
}
pub fn lookup_keysym(&self, xkev: &mut ffi::XKeyEvent) -> ffi::KeySym {
let mut keysym = 0;
unsafe {
(self.xlib.XLookupString)(xkev, ptr::null_mut(), 0, &mut keysym, ptr::null_mut());
}
keysym
}
pub fn query_keymap(&self) -> Keymap {
let mut keys = [0; 32];
unsafe {
(self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char);
}
Keymap { keys }
}
}
fn first_bit(b: u8) -> u8 {
1 << b.trailing_zeros()
}

View file

@ -9,6 +9,7 @@ mod geometry;
mod hint; mod hint;
mod icon; mod icon;
mod input; mod input;
pub mod keys;
mod memory; mod memory;
pub mod modifiers; pub mod modifiers;
mod randr; mod randr;

View file

@ -264,6 +264,7 @@ pub unsafe fn modifier_event(
virtual_keycode, virtual_keycode,
modifiers: event_mods(ns_event), modifiers: event_mods(ns_event),
}, },
is_synthetic: false,
}) })
} else { } else {
None None

View file

@ -582,6 +582,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
virtual_keycode, virtual_keycode,
modifiers: event_mods(event), modifiers: event_mods(event),
}, },
is_synthetic: false,
}, },
}; };
@ -633,6 +634,7 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
virtual_keycode, virtual_keycode,
modifiers: event_mods(event), modifiers: event_mods(event),
}, },
is_synthetic: false,
}, },
}; };
@ -741,6 +743,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
virtual_keycode, virtual_keycode,
modifiers: event_mods(event), modifiers: event_mods(event),
}, },
is_synthetic: false,
}, },
}; };

View file

@ -67,6 +67,7 @@ impl<T> WindowTarget<T> {
virtual_keycode, virtual_keycode,
modifiers, modifiers,
}, },
is_synthetic: false,
}, },
}); });
}); });
@ -83,6 +84,7 @@ impl<T> WindowTarget<T> {
virtual_keycode, virtual_keycode,
modifiers, modifiers,
}, },
is_synthetic: false,
}, },
}); });
}); });

View file

@ -1277,6 +1277,7 @@ unsafe extern "system" fn public_window_callback<T>(
virtual_keycode: vkey, virtual_keycode: vkey,
modifiers: event::get_key_mods(), modifiers: event::get_key_mods(),
}, },
is_synthetic: false,
}, },
}); });
// Windows doesn't emit a delete character by default, but in order to make it // Windows doesn't emit a delete character by default, but in order to make it
@ -1305,6 +1306,7 @@ unsafe extern "system" fn public_window_callback<T>(
virtual_keycode: vkey, virtual_keycode: vkey,
modifiers: event::get_key_mods(), modifiers: event::get_key_mods(),
}, },
is_synthetic: false,
}, },
}); });
} }