diff --git a/CHANGELOG.md b/CHANGELOG.md index a9f2e19d..20a58b4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ - On Windows, fix focusing unfocused windows when switching from fullscreen to windowed. - On X11, fix reporting incorrect DPI factor when waking from suspend. - 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) diff --git a/src/event.rs b/src/event.rs index ff94aa84..318a3578 100644 --- a/src/event.rs +++ b/src/event.rs @@ -132,6 +132,15 @@ pub enum WindowEvent { KeyboardInput { device_id: DeviceId, 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. diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index 31811422..ef026c3a 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -74,6 +74,7 @@ pub fn init_keyboard( virtual_keycode: vkcode, modifiers: modifiers_tracker.lock().unwrap().clone(), }, + is_synthetic: false, }, }) .unwrap(); @@ -125,6 +126,7 @@ pub fn init_keyboard( virtual_keycode: vkcode, modifiers: my_modifiers.lock().unwrap().clone(), }, + is_synthetic: false, }, }) .unwrap(); @@ -198,6 +200,7 @@ pub fn init_keyboard( virtual_keycode: None, modifiers: ModifiersState::default(), }, + is_synthetic: false, }, }) .unwrap(); diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 87c644f0..6f544726 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -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}; @@ -12,7 +12,7 @@ use util::modifiers::{ModifierKeyState, ModifierKeymap}; use crate::{ dpi::{LogicalPosition, LogicalSize}, - event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent}, + event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, event_loop::EventLoopWindowTarget as RootELW, }; @@ -557,22 +557,13 @@ impl EventProcessor { // value, though this should only be an issue under multiseat configurations. let device = util::VIRTUAL_CORE_KEYBOARD; 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 // a keycode of 0. - if xkev.keycode != 0 { - let keysym = unsafe { - let mut keysym = 0; - (wt.xconn.xlib.XLookupString)( - xkev, - ptr::null_mut(), - 0, - &mut keysym, - ptr::null_mut(), - ); - wt.xconn.check_errors().expect("Failed to lookup keysym"); - keysym - }; + if keycode != 0 { + let scancode = keycode - 8; + let keysym = wt.xconn.lookup_keysym(xkev); let virtual_keycode = events::keysym_to_element(keysym as c_uint); update_modifiers!( @@ -588,10 +579,11 @@ impl EventProcessor { device_id, input: KeyboardInput { state, - scancode: xkev.keycode - 8, + scancode, virtual_keycode, modifiers, }, + is_synthetic: false, }, }); } @@ -908,6 +900,10 @@ impl EventProcessor { 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, // so we have to do a little extra work. let pointer_id = self @@ -926,9 +922,12 @@ impl EventProcessor { event: CursorMoved { device_id: mkdid(pointer_id), 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 => { let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; @@ -940,8 +939,13 @@ impl EventProcessor { .unfocus(xev.event) .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 { - window_id: mkwid(xev.event), + window_id, event: Focused(false), }) } @@ -1058,20 +1062,8 @@ impl EventProcessor { return; } let scancode = (keycode - 8) as u32; - - 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 keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode); let virtual_keycode = events::keysym_to_element(keysym as c_uint); - let modifiers = self.device_mod_state.modifiers(); callback(Event::DeviceEvent { @@ -1182,4 +1174,45 @@ impl EventProcessor { Err(_) => (), } } + + fn handle_pressed_keys( + &self, + window_id: crate::window::WindowId, + state: ElementState, + callback: &mut F, + ) where + F: FnMut(Event), + { + 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, + }, + }); + } + } } diff --git a/src/platform_impl/linux/x11/util/keys.rs b/src/platform_impl/linux/x11/util/keys.rs new file mode 100644 index 00000000..fc0c9d90 --- /dev/null +++ b/src/platform_impl/linux/x11/util/keys.rs @@ -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>, + index: usize, + item: Option, +} + +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 { + 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() +} diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 1410da28..4e854236 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -9,6 +9,7 @@ mod geometry; mod hint; mod icon; mod input; +pub mod keys; mod memory; pub mod modifiers; mod randr; diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index ebad2482..508347e6 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -264,6 +264,7 @@ pub unsafe fn modifier_event( virtual_keycode, modifiers: event_mods(ns_event), }, + is_synthetic: false, }) } else { None diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 307573db..8da79e4a 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -582,6 +582,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { virtual_keycode, modifiers: event_mods(event), }, + is_synthetic: false, }, }; @@ -633,6 +634,7 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) { virtual_keycode, modifiers: event_mods(event), }, + is_synthetic: false, }, }; @@ -741,6 +743,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { virtual_keycode, modifiers: event_mods(event), }, + is_synthetic: false, }, }; diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index d302edb8..2e0f32db 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -67,6 +67,7 @@ impl WindowTarget { virtual_keycode, modifiers, }, + is_synthetic: false, }, }); }); @@ -83,6 +84,7 @@ impl WindowTarget { virtual_keycode, modifiers, }, + is_synthetic: false, }, }); }); diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3c19702d..c1b1a983 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1277,6 +1277,7 @@ unsafe extern "system" fn public_window_callback( virtual_keycode: vkey, modifiers: event::get_key_mods(), }, + is_synthetic: false, }, }); // 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( virtual_keycode: vkey, modifiers: event::get_key_mods(), }, + is_synthetic: false, }, }); }