Windows Input Capture (#100)

initial support for windows input capture.
Some things need fixing;
- scrolling
- mouse buttons > 2
This commit is contained in:
Ferdinand Schober
2024-04-11 03:55:42 +02:00
committed by GitHub
parent 9ca7e2378c
commit f9eeb254d3
4 changed files with 743 additions and 22 deletions

22
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -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<std::thread::JoinHandle<()>>,
}
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<ClientEvent> = Vec::new();
static mut ACTIVE_CLIENT: Option<ClientHandle> = None;
static mut CLIENT_FOR_POS: Lazy<HashMap<Position, ClientHandle>> = Lazy::new(HashMap::new);
static mut EVENT_TX: Option<Sender<(ClientHandle, Event)>> = 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<u32> {
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<PointerEvent> {
let mouse_low_level: MSLLHOOKSTRUCT =
unsafe { *std::mem::transmute::<LPARAM, *const MSLLHOOKSTRUCT>(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<KeyboardEvent> {
let kybrdllhookstruct: KBDLLHOOKSTRUCT =
*std::mem::transmute::<LPARAM, *const KBDLLHOOKSTRUCT>(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<Linux, ()> = 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, *const MSLLHOOKSTRUCT>(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<HMONITOR>;
(*monitors).push(hmon);
TRUE // continue enumeration
}
fn enumerate_displays() -> Vec<RECT> {
unsafe {
let mut display_rects = vec![];
let mut monitors: Vec<HMONITOR> = Vec::new();
let ret = EnumDisplayMonitors(
HDC::default(),
None,
Some(monitor_enum_proc),
LPARAM(&mut monitors as *mut Vec<HMONITOR> 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::<MONITORINFO>() 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<RECT> {
static mut DISPLAYS: Vec<RECT> = 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> {
[
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<MSG> {
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<Self> {
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<Option<Self::Item>> {
Poll::Pending
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
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();
}
}

View File

@@ -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<u32> for Linux {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
if value >= Self::KeyCount as u32 {
return Err(());
}
let code: Linux = unsafe { std::mem::transmute(value) };
Ok(code)
}
}
impl TryFrom<Linux> for Windows {
type Error = ();
@@ -698,3 +687,161 @@ impl TryFrom<Linux> for Windows {
}
}
}
impl TryFrom<Windows> for Linux {
type Error = ();
fn try_from(value: Windows) -> Result<Self, Self::Error> {
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),
}
}
}