Path: blob/main/gpu_display/src/gpu_display_win/keyboard_input_manager.rs
5394 views
// Copyright 2023 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use base::error;5use base::warn;6use linux_input_sys::constants::LED_CAPSL;7use linux_input_sys::constants::LED_NUML;8use linux_input_sys::virtio_input_event;9use sync::Mutex;10use winapi::shared::minwindef::BYTE;11use winapi::shared::minwindef::LPARAM;12use winapi::shared::minwindef::WPARAM;13use winapi::um::errhandlingapi::GetLastError;14use winapi::um::winuser::GetKeyboardState;15use winapi::um::winuser::MapVirtualKeyW;16use winapi::um::winuser::MAPVK_VK_TO_VSC;17use winapi::um::winuser::VK_CAPITAL;18use winapi::um::winuser::VK_NUMLOCK;19use winapi::um::winuser::VK_SCROLL;2021use super::window::GuiWindow;22use super::window_message_dispatcher::DisplayEventDispatcher;23use crate::gpu_display_win::window_message_processor::WindowMessage;24use crate::keycode_converter::KeycodeTranslator;25use crate::keycode_converter::KeycodeTypes;26use crate::EventDeviceKind;2728// From linux/include/uapi/linux/input-event-codes.h.29const LINUX_CAPS_LOCK_KEY: u16 = 58;30const LINUX_NUM_LOCK_KEY: u16 = 69;3132pub struct KeyboardInputManager {33display_event_dispatcher: DisplayEventDispatcher,34keycode_translator: KeycodeTranslator,35guest_key_states: Mutex<KeyStates>,36}3738impl KeyboardInputManager {39pub fn new(display_event_dispatcher: DisplayEventDispatcher) -> KeyboardInputManager {40Self {41display_event_dispatcher,42keycode_translator: KeycodeTranslator::new(KeycodeTypes::WindowsScancode),43guest_key_states: Mutex::new(KeyStates {44caps_lock_state: false,45num_lock_state: false,46}),47}48}4950pub fn handle_window_message(&self, window: &GuiWindow, message: &WindowMessage) {51match message {52WindowMessage::WindowActivate { is_activated } => {53self.handle_window_activate(window, *is_activated)54}55WindowMessage::KeyboardFocus => self.sync_key_states(window),56WindowMessage::Key {57is_sys_key: _,58is_down,59w_param,60l_param,61} => self.handle_host_key_event(window, *is_down, *w_param, *l_param),62_ => (),63}64}6566pub fn handle_guest_event(&self, window: &GuiWindow, event: virtio_input_event) {67if let Some(numlock_on) = event.get_led_state(LED_NUML) {68self.guest_key_states.lock().num_lock_state = numlock_on;69self.sync_key_states(window);70} else if let Some(capslock_on) = event.get_led_state(LED_CAPSL) {71self.guest_key_states.lock().caps_lock_state = capslock_on;72self.sync_key_states(window);73}74}7576#[inline]77fn handle_window_activate(&self, window: &GuiWindow, is_activated: bool) {78if !is_activated {79// To avoid sticky keys, we release keys when our window is deactivated. This prevents80// common shortcuts like Alt+Tab from leaving a stuck alt key in the guest.81self.release_any_down_keys(window);82}83// If either caps lock or num lock is set, reflect that change in the guest.84self.sync_key_states(window);85}8687#[inline]88fn handle_host_key_event(89&self,90window: &GuiWindow,91key_down: bool,92_w_param: WPARAM,93l_param: LPARAM,94) {95let scancode = win_util::scancode_from_lparam(l_param);96let is_repeat = key_down && get_previous_key_down_from_lparam(l_param);97if let Some(linux_keycode) = self.keycode_translator.translate(scancode) {98self.dispatch_linux_key_event(window, linux_keycode, key_down, is_repeat)99} else {100error!("Unhandled scancode while handling key event.");101}102}103104/// Checks if the caps lock and num lock key states differ between the guest & host. If they do,105/// send keys to the guest to resync it with the host.106fn sync_key_states(&self, window: &GuiWindow) {107if let Some(host_key_states) = get_host_key_states() {108let mut toggle_caps_lock = false;109let mut toggle_num_lock = false;110{111let states = self.guest_key_states.lock();112if states.caps_lock_state != host_key_states.caps_lock_state {113toggle_caps_lock = true;114}115if states.num_lock_state != host_key_states.num_lock_state {116toggle_num_lock = true;117}118}119if toggle_caps_lock {120self.press_and_release_key(window, LINUX_CAPS_LOCK_KEY);121}122if toggle_num_lock {123self.press_and_release_key(window, LINUX_NUM_LOCK_KEY);124}125}126}127128/// Releases any keys that are down when the surface is no longer active. Should be called when129/// the display window becomes inactive to avoid sticky keys.130#[inline]131fn release_any_down_keys(&self, window: &GuiWindow) {132let mut events = Vec::with_capacity(256);133let mut keyboard_state: [u8; 256] = [0; 256];134// SAFETY:135// Safe because `keyboard_state` is guaranteed to exist, and is of the expected size.136if unsafe { GetKeyboardState(keyboard_state.as_mut_ptr()) } == 0 {137error!(138"Failed in GetKeyboardState: {}",139// SAFETY: trivially safe140unsafe { GetLastError() }141);142return;143}144145for (virtual_keycode, key_state) in keyboard_state.iter().enumerate() {146// Safe because virtual_keycode < 256.147let virtual_keycode = virtual_keycode as i32;148149// Ignore toggle keys.150if virtual_keycode == VK_CAPITAL151|| virtual_keycode == VK_NUMLOCK152|| virtual_keycode == VK_SCROLL153{154continue;155}156if key_state >> 7 == 0u8 {157// Key is already up.158continue;159}160161// SAFETY:162// Trivially safe (no pointers or errors to check).163let scancode = unsafe { MapVirtualKeyW(virtual_keycode as u32, MAPVK_VK_TO_VSC) };164if let Some(linux_keycode) = self.keycode_translator.translate(scancode) {165events.push(virtio_input_event::key(linux_keycode, false, false));166} else {167error!("Failed to translate key while releasing down keys.");168}169}170171if !events.is_empty() {172self.display_event_dispatcher.dispatch(173window,174events.as_slice(),175EventDeviceKind::Keyboard,176);177}178}179180/// Sends a key press and release event to Linux/Android.181fn press_and_release_key(&self, window: &GuiWindow, key: u16) {182self.dispatch_linux_key_event(183window, key, /* key_down= */ true, /* is_repeat= */ false,184);185self.dispatch_linux_key_event(186window, key, /* key_down= */ false, /* is_repeat= */ false,187);188}189190/// Directly dispatches a Linux input keycode to the guest.191fn dispatch_linux_key_event(192&self,193window: &GuiWindow,194linux_keycode: u16,195key_down: bool,196is_repeat: bool,197) {198self.display_event_dispatcher.dispatch(199window,200&[virtio_input_event::key(linux_keycode, key_down, is_repeat)],201EventDeviceKind::Keyboard,202);203}204}205206struct KeyStates {207caps_lock_state: bool,208num_lock_state: bool,209}210211/// On success, returns a tuple containing current state of caps lock and num lock keys.212fn get_host_key_states() -> Option<KeyStates> {213let mut keyboard_state: [BYTE; 256] = [0; 256];214// SAFETY:215// Safe because `keyboard_state` is guaranteed to exist, and is of the expected size.216if unsafe { GetKeyboardState(keyboard_state.as_mut_ptr()) } != 0 {217Some(KeyStates {218caps_lock_state: toggle_to_bool(keyboard_state[VK_CAPITAL as usize]),219num_lock_state: toggle_to_bool(keyboard_state[VK_NUMLOCK as usize]),220})221} else {222warn!(223"Failed in GetKeyboardState: {}",224// SAFETY: trivially safe225unsafe { GetLastError() }226);227None228}229}230231/// Returns whether the given toggle key state indicates the key is toggled/on (true).232fn toggle_to_bool(key_state: BYTE) -> bool {233key_state & 0x1 == 0x1234}235236/// Extracts the previous key up/down state from the l_param of a WM_KEY/WM_SYSKEY DOWN event.237/// The previous key state is bit #30.238fn get_previous_key_down_from_lparam(l_param: isize) -> bool {239((l_param >> 30) & 1) == 1240}241242243