From f9eeb254d370a5b6ca545af89be8bfb75806d494 Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Thu, 11 Apr 2024 03:55:42 +0200 Subject: [PATCH] Windows Input Capture (#100) initial support for windows input capture. Some things need fixing; - scrolling - mouse buttons > 2 --- Cargo.lock | 22 ++ Cargo.toml | 11 +- src/capture/windows.rs | 557 ++++++++++++++++++++++++++++++++++++++++- src/scancode.rs | 175 +++++++++++-- 4 files changed, 743 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a348b13..c7fe20e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1246,6 +1246,7 @@ dependencies = [ "libc", "log", "memmap", + "num_enum", "once_cell", "reis", "serde", @@ -1427,6 +1428,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "object" version = "0.32.2" diff --git a/Cargo.toml b/Cargo.toml index 97f7992..584fd98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ adw = { package = "libadwaita", version = "0.6.0", features = ["v1_1"], optional async-channel = { version = "2.1.1", optional = true } keycode = "0.4.0" once_cell = "1.19.0" +num_enum = "0.7.2" [target.'cfg(unix)'.dependencies] libc = "0.2.148" @@ -49,7 +50,15 @@ reis = { version = "0.2", features = [ "tokio" ], optional = true } core-graphics = { version = "0.23", features = ["highsierra"] } [target.'cfg(windows)'.dependencies] -windows = { version = "0.54.0", features = [ "Win32_UI_Input_KeyboardAndMouse" ] } +windows = { version = "0.54.0", features = [ + "Win32_System_LibraryLoader", + "Win32_System_Threading", + "Win32_Foundation", + "Win32_Graphics", + "Win32_Graphics_Gdi", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_WindowsAndMessaging", +] } [build-dependencies] glib-build-tools = "0.19.0" diff --git a/src/capture/windows.rs b/src/capture/windows.rs index e132f40..8027d1d 100644 --- a/src/capture/windows.rs +++ b/src/capture/windows.rs @@ -1,35 +1,578 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use core::task::{Context, Poll}; use futures::Stream; -use std::{io, pin::Pin}; +use once_cell::unsync::Lazy; +use std::collections::HashMap; +use std::ptr::{addr_of, addr_of_mut}; + +use futures::executor::block_on; +use std::default::Default; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::task::ready; +use std::{io, pin::Pin, thread}; +use tokio::sync::mpsc::{channel, Receiver, Sender}; +use windows::core::w; +use windows::Win32::Foundation::{ + BOOL, FALSE, HINSTANCE, HWND, LPARAM, LRESULT, RECT, TRUE, WPARAM, +}; +use windows::Win32::Graphics::Gdi::{ + EnumDisplayMonitors, GetMonitorInfoW, HDC, HMONITOR, MONITORINFO, +}; +use windows::Win32::System::LibraryLoader::GetModuleHandleW; +use windows::Win32::System::Threading::GetCurrentThreadId; + +use windows::Win32::UI::WindowsAndMessaging::{ + CallNextHookEx, CreateWindowExW, DispatchMessageW, GetMessageW, PostThreadMessageW, + RegisterClassW, SetWindowsHookExW, TranslateMessage, HHOOK, HMENU, HOOKPROC, KBDLLHOOKSTRUCT, + LLKHF_EXTENDED, MSG, MSLLHOOKSTRUCT, WH_KEYBOARD_LL, WH_MOUSE_LL, WINDOW_STYLE, + WM_DISPLAYCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, + WM_MBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SYSKEYDOWN, + WM_SYSKEYUP, WM_USER, WNDCLASSW, WNDPROC, +}; + +use crate::client::Position; +use crate::event::{KeyboardEvent, PointerEvent, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT}; +use crate::scancode::Linux; use crate::{ capture::InputCapture, client::{ClientEvent, ClientHandle}, event::Event, + scancode, }; -pub struct WindowsInputCapture {} +pub struct WindowsInputCapture { + event_rx: Receiver<(ClientHandle, Event)>, + msg_thread: Option>, +} + +enum EventType { + ClientEvent = 0, + Release = 1, + Exit = 2, +} + +unsafe fn signal_message_thread(event_type: EventType) { + if let Some(event_tid) = get_event_tid() { + PostThreadMessageW(event_tid, WM_USER, WPARAM(event_type as usize), LPARAM(0)).unwrap(); + } +} impl InputCapture for WindowsInputCapture { - fn notify(&mut self, _event: ClientEvent) -> io::Result<()> { + fn notify(&mut self, event: ClientEvent) -> io::Result<()> { + unsafe { + EVENT_BUFFER.push(event); + signal_message_thread(EventType::ClientEvent); + } Ok(()) } fn release(&mut self) -> io::Result<()> { + unsafe { signal_message_thread(EventType::Release) }; Ok(()) } } +static mut EVENT_BUFFER: Vec = Vec::new(); +static mut ACTIVE_CLIENT: Option = None; +static mut CLIENT_FOR_POS: Lazy> = Lazy::new(HashMap::new); +static mut EVENT_TX: Option> = None; +static mut EVENT_THREAD_ID: AtomicU32 = AtomicU32::new(0); +unsafe fn set_event_tid(tid: u32) { + EVENT_THREAD_ID.store(tid, Ordering::SeqCst); +} +unsafe fn get_event_tid() -> Option { + match EVENT_THREAD_ID.load(Ordering::SeqCst) { + 0 => None, + id => Some(id), + } +} + +static mut ENTRY_POINT: (i32, i32) = (0, 0); + +fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option { + let mouse_low_level: MSLLHOOKSTRUCT = + unsafe { *std::mem::transmute::(lparam) }; + match wparam { + WPARAM(p) if p == WM_LBUTTONDOWN as usize => Some(PointerEvent::Button { + time: 0, + button: BTN_LEFT, + state: 1, + }), + WPARAM(p) if p == WM_MBUTTONDOWN as usize => Some(PointerEvent::Button { + time: 0, + button: BTN_MIDDLE, + state: 1, + }), + WPARAM(p) if p == WM_RBUTTONDOWN as usize => Some(PointerEvent::Button { + time: 0, + button: BTN_RIGHT, + state: 1, + }), + WPARAM(p) if p == WM_LBUTTONUP as usize => Some(PointerEvent::Button { + time: 0, + button: BTN_LEFT, + state: 0, + }), + WPARAM(p) if p == WM_MBUTTONUP as usize => Some(PointerEvent::Button { + time: 0, + button: BTN_MIDDLE, + state: 0, + }), + WPARAM(p) if p == WM_RBUTTONUP as usize => Some(PointerEvent::Button { + time: 0, + button: BTN_RIGHT, + state: 0, + }), + WPARAM(p) if p == WM_MOUSEMOVE as usize => unsafe { + let (x, y) = (mouse_low_level.pt.x, mouse_low_level.pt.y); + let (ex, ey) = ENTRY_POINT; + let (dx, dy) = (x - ex, y - ey); + Some(PointerEvent::Motion { + time: 0, + relative_x: dx as f64, + relative_y: dy as f64, + }) + }, + WPARAM(p) if p == WM_MOUSEWHEEL as usize => Some(PointerEvent::Axis { + time: 0, + axis: 0, + value: -(mouse_low_level.mouseData as i32) as f64, + }), + _ => None, + } +} + +unsafe fn to_key_event(wparam: WPARAM, lparam: LPARAM) -> Option { + let kybrdllhookstruct: KBDLLHOOKSTRUCT = + *std::mem::transmute::(lparam); + let mut scan_code = kybrdllhookstruct.scanCode; + log::trace!("scan_code: {scan_code}"); + if kybrdllhookstruct.flags.contains(LLKHF_EXTENDED) { + scan_code |= 0xE000; + } + let Ok(win_scan_code) = scancode::Windows::try_from(scan_code) else { + log::warn!("failed to translate to windows scancode: {scan_code}"); + return None; + }; + log::trace!("windows_scan: {win_scan_code:?}"); + let Ok(linux_scan_code): Result = win_scan_code.try_into() else { + log::warn!("failed to translate into linux scancode: {win_scan_code:?}"); + return None; + }; + log::trace!("windows_scan: {linux_scan_code:?}"); + let scan_code = linux_scan_code as u32; + match wparam { + WPARAM(p) if p == WM_KEYDOWN as usize => Some(KeyboardEvent::Key { + time: 0, + key: scan_code, + state: 1, + }), + WPARAM(p) if p == WM_KEYUP as usize => Some(KeyboardEvent::Key { + time: 0, + key: scan_code, + state: 0, + }), + WPARAM(p) if p == WM_SYSKEYDOWN as usize => Some(KeyboardEvent::Key { + time: 0, + key: scan_code, + state: 1, + }), + WPARAM(p) if p == WM_SYSKEYUP as usize => Some(KeyboardEvent::Key { + time: 0, + key: scan_code, + state: 1, + }), + _ => None, + } +} + +/// +/// clamp point to display bounds +/// +/// # Arguments +/// +/// * `prev_point`: coordinates, the cursor was before entering, within bounds of a display +/// * `entry_point`: point to clamp +/// +/// returns: (i32, i32), the corrected entry point +/// +fn clamp_to_display_bounds(prev_point: (i32, i32), point: (i32, i32)) -> (i32, i32) { + /* find display where movement came from */ + let display_regions = unsafe { get_display_regions() }; + let display = display_regions + .iter() + .find(|&d| is_within_dp_region(prev_point, d)) + .unwrap(); + + /* clamp to bounds (inclusive) */ + let (x, y) = point; + let (min_x, max_x) = (display.left, display.right - 1); + let (min_y, max_y) = (display.top, display.bottom - 1); + (x.clamp(min_x, max_x), y.clamp(min_y, max_y)) +} + +unsafe fn send_blocking(event: Event) { + if let Some(active) = ACTIVE_CLIENT { + block_on(async move { + let _ = EVENT_TX.as_ref().unwrap().send((active, event)).await; + }); + } +} + +unsafe fn check_client_activation(wparam: WPARAM, lparam: LPARAM) -> bool { + if wparam.0 != WM_MOUSEMOVE as usize { + return ACTIVE_CLIENT.is_some(); + } + let mouse_low_level: MSLLHOOKSTRUCT = + unsafe { *std::mem::transmute::(lparam) }; + static mut PREV_POS: Option<(i32, i32)> = None; + let curr_pos = (mouse_low_level.pt.x, mouse_low_level.pt.y); + let prev_pos = PREV_POS.unwrap_or(curr_pos); + PREV_POS.replace(curr_pos); + + /* next event is the first actual event */ + let ret = ACTIVE_CLIENT.is_some(); + + /* client already active, no need to check */ + if ACTIVE_CLIENT.is_some() { + return ret; + } + + /* check if a client was activated */ + let Some(pos) = entered_barrier(prev_pos, curr_pos, get_display_regions()) else { + return ret; + }; + + /* check if a client is registered for the barrier */ + let Some(client) = CLIENT_FOR_POS.get(&pos) else { + return ret; + }; + + /* update active client and entry point */ + ACTIVE_CLIENT.replace(*client); + ENTRY_POINT = clamp_to_display_bounds(prev_pos, curr_pos); + + /* notify main thread */ + log::debug!("ENTERED @ {prev_pos:?} -> {curr_pos:?}"); + send_blocking(Event::Enter()); + + ret +} + +unsafe extern "system" fn mouse_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let active = check_client_activation(wparam, lparam); + + /* no client was active */ + if !active { + return CallNextHookEx(HHOOK::default(), ncode, wparam, lparam); + } + + /* get active client if any */ + let Some(client) = ACTIVE_CLIENT else { + return LRESULT(1); + }; + + /* convert to lan-mouse event */ + let Some(pointer_event) = to_mouse_event(wparam, lparam) else { + return LRESULT(1); + }; + let event = (client, Event::Pointer(pointer_event)); + + /* notify mainthread (drop events if sending too fast) */ + if let Err(e) = EVENT_TX.as_ref().unwrap().try_send(event) { + log::warn!("e: {e}"); + } + + /* don't pass event to applications */ + LRESULT(1) +} + +unsafe extern "system" fn kybrd_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + /* get active client if any */ + let Some(client) = ACTIVE_CLIENT else { + return CallNextHookEx(HHOOK::default(), ncode, wparam, lparam); + }; + + /* convert to key event */ + let Some(key_event) = to_key_event(wparam, lparam) else { + return LRESULT(1); + }; + let event = (client, Event::Keyboard(key_event)); + + if let Err(e) = EVENT_TX.as_ref().unwrap().try_send(event) { + log::warn!("e: {e}"); + } + + /* don't pass event to applications */ + LRESULT(1) +} + +unsafe extern "system" fn window_proc( + _hwnd: HWND, + uint: u32, + _wparam: WPARAM, + _lparam: LPARAM, +) -> LRESULT { + match uint { + x if x == WM_DISPLAYCHANGE => { + log::debug!("display resolution changed"); + DISPLAY_RESOLUTION_CHANGED = true; + } + _ => {} + } + LRESULT(1) +} + +unsafe extern "system" fn monitor_enum_proc( + hmon: HMONITOR, + _hdc: HDC, + _lprect: *mut RECT, + lparam: LPARAM, +) -> BOOL { + let monitors = lparam.0 as *mut Vec; + (*monitors).push(hmon); + TRUE // continue enumeration +} + +fn enumerate_displays() -> Vec { + unsafe { + let mut display_rects = vec![]; + let mut monitors: Vec = Vec::new(); + let ret = EnumDisplayMonitors( + HDC::default(), + None, + Some(monitor_enum_proc), + LPARAM(&mut monitors as *mut Vec as isize), + ); + if ret != TRUE { + panic!("could not enumerate displays"); + } + for monitor in monitors { + let mut monitor_info: MONITORINFO = std::mem::zeroed(); + monitor_info.cbSize = std::mem::size_of::() as u32; + if GetMonitorInfoW(monitor, &mut monitor_info) == FALSE { + panic!(); + } + display_rects.push(monitor_info.rcMonitor); + } + display_rects + } +} + +static mut DISPLAY_RESOLUTION_CHANGED: bool = true; + +unsafe fn get_display_regions() -> &'static Vec { + static mut DISPLAYS: Vec = vec![]; + if DISPLAY_RESOLUTION_CHANGED { + DISPLAYS = enumerate_displays(); + DISPLAY_RESOLUTION_CHANGED = false; + log::debug!("displays: {DISPLAYS:?}"); + } + &*addr_of!(DISPLAYS) +} + +fn is_within_dp_region(point: (i32, i32), display: &RECT) -> bool { + [ + Position::Left, + Position::Right, + Position::Top, + Position::Bottom, + ] + .iter() + .all(|&pos| is_within_dp_boundary(point, display, pos)) +} +fn is_within_dp_boundary(point: (i32, i32), display: &RECT, pos: Position) -> bool { + let (x, y) = point; + match pos { + Position::Left => display.left <= x, + Position::Right => display.right > x, + Position::Top => display.top <= y, + Position::Bottom => display.bottom > y, + } +} + +/// returns whether the given position is within the display bounds with respect to the given +/// barrier position +/// +/// # Arguments +/// +/// * `x`: +/// * `y`: +/// * `displays`: +/// * `pos`: +/// +/// returns: bool +/// +fn in_bounds(point: (i32, i32), displays: &[RECT], pos: Position) -> bool { + displays + .iter() + .any(|d| is_within_dp_boundary(point, d, pos)) +} + +fn in_display_region(point: (i32, i32), displays: &[RECT]) -> bool { + displays.iter().any(|d| is_within_dp_region(point, d)) +} + +fn moved_across_boundary( + prev_pos: (i32, i32), + curr_pos: (i32, i32), + displays: &[RECT], + pos: Position, +) -> bool { + /* was within bounds, but is not anymore */ + in_display_region(prev_pos, displays) && !in_bounds(curr_pos, displays, pos) +} + +fn entered_barrier( + prev_pos: (i32, i32), + curr_pos: (i32, i32), + displays: &[RECT], +) -> Option { + [ + Position::Left, + Position::Right, + Position::Top, + Position::Bottom, + ] + .into_iter() + .find(|&pos| moved_across_boundary(prev_pos, curr_pos, displays, pos)) +} + +fn get_msg() -> Option { + unsafe { + let mut msg = std::mem::zeroed(); + let ret = GetMessageW(addr_of_mut!(msg), HWND::default(), 0, 0); + match ret.0 { + 0 => None, + x if x > 0 => Some(msg), + _ => panic!("error in GetMessageW"), + } + } +} + +fn message_thread() { + unsafe { + set_event_tid(GetCurrentThreadId()); + let mouse_proc: HOOKPROC = Some(mouse_proc); + let kybrd_proc: HOOKPROC = Some(kybrd_proc); + let window_proc: WNDPROC = Some(window_proc); + + /* register hooks */ + let _ = SetWindowsHookExW(WH_MOUSE_LL, mouse_proc, HINSTANCE::default(), 0).unwrap(); + let _ = SetWindowsHookExW(WH_KEYBOARD_LL, kybrd_proc, HINSTANCE::default(), 0).unwrap(); + + let instance = GetModuleHandleW(None).unwrap(); + let window_class: WNDCLASSW = WNDCLASSW { + lpfnWndProc: window_proc, + hInstance: instance.into(), + lpszClassName: w!("lan-mouse-message-window-class"), + ..Default::default() + }; + + let ret = RegisterClassW(&window_class); + if ret == 0 { + panic!("RegisterClassW"); + } + + /* window is used ro receive WM_DISPLAYCHANGE messages */ + let ret = CreateWindowExW( + Default::default(), + w!("lan-mouse-message-window-class"), + w!("lan-mouse-msg-window"), + WINDOW_STYLE::default(), + 0, + 0, + 0, + 0, + HWND::default(), + HMENU::default(), + instance, + None, + ); + if ret.0 == 0 { + panic!("CreateWindowExW"); + } + + /* run message loop */ + loop { + // mouse / keybrd proc do not actually return a message + let Some(msg) = get_msg() else { + break; + }; + if msg.hwnd.0 == 0 { + /* messages sent via PostThreadMessage */ + match msg.wParam.0 { + x if x == EventType::Exit as usize => break, + x if x == EventType::Release as usize => { + ACTIVE_CLIENT.take(); + } + x if x == EventType::ClientEvent as usize => { + while let Some(event) = EVENT_BUFFER.pop() { + update_clients(event) + } + } + _ => {} + } + } else { + /* other messages for window_procs */ + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + } +} + +fn update_clients(client_event: ClientEvent) { + match client_event { + ClientEvent::Create(handle, pos) => { + unsafe { CLIENT_FOR_POS.insert(pos, handle) }; + } + ClientEvent::Destroy(handle) => unsafe { + for pos in [ + Position::Left, + Position::Right, + Position::Top, + Position::Bottom, + ] { + if ACTIVE_CLIENT == Some(handle) { + ACTIVE_CLIENT.take(); + } + if CLIENT_FOR_POS.get(&pos).copied() == Some(handle) { + CLIENT_FOR_POS.remove(&pos); + } + } + }, + } +} + impl WindowsInputCapture { pub(crate) fn new() -> Result { - Err(anyhow!("not implemented")) + unsafe { + let (tx, rx) = channel(10); + EVENT_TX.replace(tx); + let msg_thread = Some(thread::spawn(message_thread)); + Ok(Self { + msg_thread, + event_rx: rx, + }) + } } } impl Stream for WindowsInputCapture { type Item = io::Result<(ClientHandle, Event)>; - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Pending + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match ready!(self.event_rx.poll_recv(cx)) { + None => Poll::Ready(None), + Some(e) => Poll::Ready(Some(Ok(e))), + } + } +} + +impl Drop for WindowsInputCapture { + fn drop(&mut self) { + unsafe { signal_message_thread(EventType::Exit) }; + let _ = self.msg_thread.take().unwrap().join(); } } diff --git a/src/scancode.rs b/src/scancode.rs index 254fa8d..ac84f5d 100644 --- a/src/scancode.rs +++ b/src/scancode.rs @@ -1,10 +1,11 @@ +use num_enum::TryFromPrimitive; use serde::{Deserialize, Serialize}; /* * https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input */ #[repr(u32)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, TryFromPrimitive)] pub enum Windows { Shutdown = 0xE05E, SystemSleep = 0xE05F, @@ -167,7 +168,7 @@ pub enum Windows { * https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h */ #[repr(u32)] -#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, Hash, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, Hash, PartialEq, TryFromPrimitive)] #[allow(dead_code)] pub enum Linux { KeyReserved = 0, @@ -428,18 +429,6 @@ pub enum Linux { KeyCount = 249, } -impl TryFrom for Linux { - type Error = (); - - fn try_from(value: u32) -> Result { - if value >= Self::KeyCount as u32 { - return Err(()); - } - let code: Linux = unsafe { std::mem::transmute(value) }; - Ok(code) - } -} - impl TryFrom for Windows { type Error = (); @@ -698,3 +687,161 @@ impl TryFrom for Windows { } } } +impl TryFrom for Linux { + type Error = (); + + fn try_from(value: Windows) -> Result { + match value { + Windows::Shutdown => Ok(Self::KeyPower), + Windows::SystemSleep => Ok(Self::KeySleep), + Windows::SystemWakeUp => Ok(Self::KeyWakeup), + Windows::ErrorRollOver => Ok(Self::KeyRo), + Windows::KeyA => Ok(Self::KeyA), + Windows::KeyB => Ok(Self::KeyB), + Windows::KeyC => Ok(Self::KeyC), + Windows::KeyD => Ok(Self::KeyD), + Windows::KeyE => Ok(Self::KeyE), + Windows::KeyF => Ok(Self::KeyF), + Windows::KeyG => Ok(Self::KeyG), + Windows::KeyH => Ok(Self::KeyH), + Windows::KeyI => Ok(Self::KeyI), + Windows::KeyJ => Ok(Self::KeyJ), + Windows::KeyK => Ok(Self::KeyK), + Windows::KeyL => Ok(Self::KeyL), + Windows::KeyM => Ok(Self::KeyM), + Windows::KeyN => Ok(Self::KeyN), + Windows::KeyO => Ok(Self::KeyO), + Windows::KeyP => Ok(Self::KeyP), + Windows::KeyQ => Ok(Self::KeyQ), + Windows::KeyR => Ok(Self::KeyR), + Windows::KeyS => Ok(Self::KeyS), + Windows::KeyT => Ok(Self::KeyT), + Windows::KeyU => Ok(Self::KeyU), + Windows::KeyV => Ok(Self::KeyV), + Windows::KeyW => Ok(Self::KeyW), + Windows::KeyX => Ok(Self::KeyX), + Windows::KeyY => Ok(Self::KeyY), + Windows::KeyZ => Ok(Self::KeyZ), + Windows::Key1 => Ok(Self::Key1), + Windows::Key2 => Ok(Self::Key2), + Windows::Key3 => Ok(Self::Key3), + Windows::Key4 => Ok(Self::Key4), + Windows::Key5 => Ok(Self::Key5), + Windows::Key6 => Ok(Self::Key6), + Windows::Key7 => Ok(Self::Key7), + Windows::Key8 => Ok(Self::Key8), + Windows::Key9 => Ok(Self::Key9), + Windows::Key0 => Ok(Self::Key0), + Windows::KeyEnter => Ok(Self::KeyEnter), + Windows::KeyEsc => Ok(Self::KeyEsc), + Windows::KeyDelete => Ok(Self::KeyBackspace), + Windows::KeyTab => Ok(Self::KeyTab), + Windows::KeySpace => Ok(Self::KeySpace), + Windows::KeyMinus => Ok(Self::KeyMinus), + Windows::KeyEqual => Ok(Self::KeyEqual), + Windows::KeyLeftBrace => Ok(Self::KeyLeftbrace), + Windows::KeyRightBrace => Ok(Self::KeyRightbrace), + Windows::KeyBackslash => Ok(Self::KeyBackslash), + Windows::KeySemiColon => Ok(Self::KeySemicolon), + Windows::KeyApostrophe => Ok(Self::KeyApostrophe), + Windows::KeyGrave => Ok(Self::KeyGrave), + Windows::KeyComma => Ok(Self::KeyComma), + Windows::KeyDot => Ok(Self::KeyDot), + Windows::KeySlash => Ok(Self::KeySlash), + Windows::KeyCapsLock => Ok(Self::KeyCapsLock), + Windows::KeyF1 => Ok(Self::KeyF1), + Windows::KeyF2 => Ok(Self::KeyF2), + Windows::KeyF3 => Ok(Self::KeyF3), + Windows::KeyF4 => Ok(Self::KeyF4), + Windows::KeyF5 => Ok(Self::KeyF5), + Windows::KeyF6 => Ok(Self::KeyF6), + Windows::KeyF7 => Ok(Self::KeyF7), + Windows::KeyF8 => Ok(Self::KeyF8), + Windows::KeyF9 => Ok(Self::KeyF9), + Windows::KeyF10 => Ok(Self::KeyF10), + Windows::KeyF11 => Ok(Self::KeyF11), + Windows::KeyF12 => Ok(Self::KeyF12), + Windows::KeyPrintScreen => Ok(Self::KeySysrq), + Windows::KeyScrollLock => Ok(Self::KeyScrollLock), + Windows::KeyPause => Ok(Self::KeyPause), + Windows::KeyInsert => Ok(Self::KeyInsert), + Windows::KeyHome => Ok(Self::KeyHome), + Windows::KeyPageUp => Ok(Self::KeyPageup), + Windows::KeyDeleteForward => Ok(Self::KeyDelete), + Windows::KeyEnd => Ok(Self::KeyEnd), + Windows::KeyPageDown => Ok(Self::KeyPagedown), + Windows::KeyRight => Ok(Self::KeyRight), + Windows::KeyLeft => Ok(Self::KeyLeft), + Windows::KeyDown => Ok(Self::KeyDown), + Windows::KeyUp => Ok(Self::KeyUp), + Windows::KeypadNumLock => Ok(Self::KeyNumlock), + Windows::KeypadSlash => Ok(Self::KeyKpslash), + Windows::KeypadStar => Ok(Self::KeyKpAsterisk), + Windows::KeypadDash => Ok(Self::KeyKpMinus), + Windows::KeypadPlus => Ok(Self::KeyKpplus), + Windows::KeypadEnter => Ok(Self::KeyKpEnter), + Windows::Keypad1End => Ok(Self::KeyKp1), + Windows::Keypad2DownArrow => Ok(Self::KeyKp2), + Windows::Keypad3PageDn => Ok(Self::KeyKp3), + Windows::Keypad4LeftArrow => Ok(Self::KeyKp4), + Windows::Keypad5 => Ok(Self::KeyKp5), + Windows::Keypad6RightArrow => Ok(Self::KeyKp6), + Windows::Keypad7Home => Ok(Self::KeyKp7), + Windows::Keypad8UpArrow => Ok(Self::KeyKp8), + Windows::Keypad9PageUp => Ok(Self::KeyKp9), + Windows::Keypad0Insert => Ok(Self::KeyKp0), + Windows::KeypadDot => Ok(Self::KeyKpDot), + Windows::KeyNonUSSlashBar => Ok(Self::Key102nd), + Windows::KeyApplication => Ok(Self::KeyMenu), + Windows::KeypadEquals => Ok(Self::KeyKpequal), + Windows::KeyF13 => Ok(Self::KeyF13), + Windows::KeyF14 => Ok(Self::KeyF14), + Windows::KeyF15 => Ok(Self::KeyF15), + Windows::KeyF16 => Ok(Self::KeyF16), + Windows::KeyF17 => Ok(Self::KeyF17), + Windows::KeyF18 => Ok(Self::KeyF18), + Windows::KeyF19 => Ok(Self::KeyF19), + Windows::KeyF20 => Ok(Self::KeyF20), + Windows::KeyF21 => Ok(Self::KeyF21), + Windows::KeyF22 => Ok(Self::KeyF22), + Windows::KeyF23 => Ok(Self::KeyF23), + Windows::KeyF24 => Ok(Self::KeyF24), + Windows::KeypadComma => Ok(Self::KeyKpcomma), + Windows::KeyInternational1 => Ok(Self::KeyHangeul), + Windows::KeyInternational2 => Ok(Self::KeyHanja), + Windows::KeyInternational3 => Ok(Self::KeyYen), + Windows::KeyInternational4 => Err(()), + Windows::KeyInternational5 => Err(()), + Windows::KeyLANG1 => Ok(Self::KeyKatakana), + Windows::KeyLANG2 => Ok(Self::KeyHiragana), + Windows::KeyLANG3 => Ok(Self::KeyHenkan), + Windows::KeyLANG4 => Ok(Self::KeyKatakanahiragana), + Windows::KeyLeftCtrl => Ok(Self::KeyLeftCtrl), + Windows::KeyLeftShift => Ok(Self::KeyLeftShift), + Windows::KeyLeftAlt => Ok(Self::KeyLeftAlt), + Windows::KeyLeftGUI => Ok(Self::KeyLeftMeta), + Windows::KeyRightCtrl => Ok(Self::KeyRightCtrl), + Windows::KeyRightShift => Ok(Self::KeyRightShift), + Windows::KeyRightAlt => Ok(Self::KeyRightalt), + Windows::KeyRightGUI => Ok(Self::KeyRightmeta), + Windows::KeyScanNextTrack => Ok(Self::KeyNextsong), + Windows::KeyScanPreviousTrack => Ok(Self::KeyPrevioussong), + Windows::KeyStop => Ok(Self::KeyStopcd), + Windows::KeyPlayPause => Ok(Self::KeyPlaypause), + Windows::KeyMute => Ok(Self::KeyMute), + Windows::KeyVolumeUp => Ok(Self::KeyVolumeUp), + Windows::KeyVolumeDown => Ok(Self::KeyVolumeDown), + Windows::ALConsumerControlConfiguration => Err(()), + Windows::ALEmailReader => Ok(Self::KeyMail), + Windows::ALCalculator => Ok(Self::KeyCalc), + Windows::ALLocalMachineBrowser => Ok(Self::KeyFile), + Windows::ACSearch => Ok(Self::KeyWww), + Windows::ACHome => Ok(Self::KeyHomepage), + Windows::ACBack => Ok(Self::KeyBack), + Windows::ACForward => Ok(Self::KeyForward), + Windows::ACStop => Ok(Self::KeyStop), + Windows::ACRefresh => Ok(Self::KeyRefresh), + Windows::ACBookmarks => Ok(Self::KeyBookmarks), + } + } +}