Add Windows implementation

This commit is contained in:
amrbashir
2022-12-13 00:09:45 +02:00
parent b15f622102
commit 74160d88e2
13 changed files with 2861 additions and 5 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target
/.vscode

2062
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,14 +3,31 @@ name = "global-hotkey"
version = "0.0.0"
description = "Global hotkeys for Desktop Applications"
edition = "2021"
keywords = [ "windowing", "global", "global-hotkey", "hotkey" ]
keywords = ["windowing", "global", "global-hotkey", "hotkey"]
license = "Apache-2.0 OR MIT"
readme = "README.md"
repository = "https://github.com/amrbashir/global-hotkey"
documentation = "https://docs.rs/global-hotkey"
categories = [ "gui" ]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
categories = ["gui"]
[dependencies]
crossbeam-channel = "0.5"
keyboard-types = "0.6"
once_cell = "1.16.0"
thiserror = "1.0"
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
version = "0.42"
features = [
"Win32_UI_WindowsAndMessaging",
"Win32_Foundation",
"Win32_System_SystemServices",
"Win32_Graphics_Gdi",
"Win32_UI_Shell",
"Win32_UI_Input_KeyboardAndMouse",
]
[dev-dependencies]
winit = "0.27"
tao = { git = "https://github.com/tauri-apps/tao", branch = "muda" }

37
examples/tao.rs Normal file
View File

@@ -0,0 +1,37 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use global_hotkey::{
global_hotkey_event_receiver,
hotkey::{Code, HotKey, Modifiers},
GlobalHotKeyManager,
};
use tao::event_loop::{ControlFlow, EventLoopBuilder};
fn main() {
let event_loop = EventLoopBuilder::new().build();
let hotkeys_manager = GlobalHotKeyManager::new().unwrap();
let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyD);
let hotkey2 = HotKey::new(Some(Modifiers::SHIFT | Modifiers::ALT), Code::KeyD);
hotkeys_manager.register(hotkey).unwrap();
hotkeys_manager.register(hotkey2).unwrap();
let global_hotkey_channel = global_hotkey_event_receiver();
event_loop.run(move |_event, _, control_flow| {
*control_flow = ControlFlow::Poll;
if let Ok(event) = global_hotkey_channel.try_recv() {
println!("{:?}", event);
if hotkey2.id() == event.id() {
hotkeys_manager.unregister(hotkey2).unwrap();
}
}
})
}

37
examples/winit.rs Normal file
View File

@@ -0,0 +1,37 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use global_hotkey::{
global_hotkey_event_receiver,
hotkey::{Code, HotKey, Modifiers},
GlobalHotKeyManager,
};
use winit::event_loop::{ControlFlow, EventLoopBuilder};
fn main() {
let event_loop = EventLoopBuilder::new().build();
let hotkeys_manager = GlobalHotKeyManager::new().unwrap();
let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyD);
let hotkey2 = HotKey::new(Some(Modifiers::SHIFT | Modifiers::ALT), Code::KeyD);
hotkeys_manager.register(hotkey).unwrap();
hotkeys_manager.register(hotkey2).unwrap();
let global_hotkey_channel = global_hotkey_event_receiver();
event_loop.run(move |_event, _, control_flow| {
*control_flow = ControlFlow::Poll;
if let Ok(event) = global_hotkey_channel.try_recv() {
println!("{:?}", event);
if hotkey2.id() == event.id() {
hotkeys_manager.unregister(hotkey2).unwrap();
}
}
})
}

17
src/counter.rs Normal file
View File

@@ -0,0 +1,17 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::sync::atomic::{AtomicU32, Ordering};
pub struct Counter(AtomicU32);
impl Counter {
pub const fn new() -> Self {
Self(AtomicU32::new(1))
}
pub fn next(&self) -> u32 {
self.0.fetch_add(1, Ordering::Relaxed)
}
}

22
src/error.rs Normal file
View File

@@ -0,0 +1,22 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use thiserror::Error;
/// Errors returned by tray-icon.
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
OsError(#[from] std::io::Error),
#[error("{0}")]
HotKeyParseError(String),
#[error("{0}")]
FailedToRegister(String),
#[error("Failed to unregister this hotkey")]
FailedToUnRegister,
}
/// Convenient type alias of Result type for tray-icon.
pub type Result<T> = std::result::Result<T, Error>;

254
src/hotkey.rs Normal file
View File

@@ -0,0 +1,254 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! HotKeys describe keyboard global shortcuts.
//!
//! [`HotKey`s](crate::hotkey::HotKey) are used to define a keyboard shortcut consisting
//! of an optional combination of modifier keys (provided by [`Modifiers`](crate::hotkey::Modifiers)) and
//! one key ([`Code`](crate::hotkey::Code)).
//!
//! # Examples
//! They can be created directly
//! ```no_run
//! # use global_hotkey::hotkey::{HotKey, Modifiers, Code};
//! let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyQ);
//! let hotkey_without_mods = HotKey::new(None, Code::KeyQ);
//! ```
//! or from `&str`, note that all modifiers
//! have to be listed before the non-modifier key, `shift+alt+KeyQ` is legal,
//! whereas `shift+q+alt` is not.
//! ```no_run
//! # use global_hotkey::hotkey::{HotKey};
//! let hotkey: HotKey = "shift+alt+KeyQ".parse().unwrap();
//! # // This assert exists to ensure a test breaks once the
//! # // statement above about ordering is no longer valid.
//! # assert!("shift+KeyQ+alt".parse::<HotKey>().is_err());
//! ```
//!
pub use keyboard_types::{Code, Modifiers};
use std::{borrow::Borrow, hash::Hash, str::FromStr};
use crate::counter::Counter;
static COUNTER: Counter = Counter::new();
/// A keyboard shortcut that consists of an optional combination
/// of modifier keys (provided by [`Modifiers`](crate::hotkey::Modifiers)) and
/// one key ([`Code`](crate::hotkey::Code)).
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub struct HotKey {
pub(crate) mods: Modifiers,
pub(crate) key: Code,
id: u32,
}
impl HotKey {
/// Creates a new hotkey to define keyboard shortcuts throughout your application.
/// Only [`Modifiers::ALT`], [`Modifiers::SHIFT`], [`Modifiers::CONTROL`], and [`Modifiers::META`]/[`Modifiers::SUPER`]
pub fn new(mods: Option<Modifiers>, key: Code) -> Self {
Self {
mods: mods.unwrap_or_else(Modifiers::empty),
key,
id: COUNTER.next(),
}
}
/// Returns the id associated with this HotKey
pub fn id(&self) -> u32 {
self.id
}
/// Returns `true` if this [`Code`] and [`Modifiers`] matches this `hotkey`.
pub fn matches(&self, modifiers: impl Borrow<Modifiers>, key: impl Borrow<Code>) -> bool {
// Should be a const but const bit_or doesn't work here.
let base_mods = Modifiers::SHIFT
| Modifiers::CONTROL
| Modifiers::ALT
| Modifiers::META
| Modifiers::SUPER;
let modifiers = modifiers.borrow();
let key = key.borrow();
self.mods == *modifiers & base_mods && self.key == *key
}
}
// HotKey::from_str is available to be backward
// compatible with tauri and it also open the option
// to generate hotkey from string
impl FromStr for HotKey {
type Err = crate::Error;
fn from_str(hotkey_string: &str) -> Result<Self, Self::Err> {
parse_hotkey(hotkey_string)
}
}
fn parse_hotkey(hotkey_string: &str) -> crate::Result<HotKey> {
let mut mods = Modifiers::empty();
let mut key = Code::Unidentified;
let mut split = hotkey_string.split('+');
let len = split.clone().count();
let parse_key = |token: &str| -> crate::Result<Code> {
if let Ok(code) = Code::from_str(token) {
match code {
Code::Unidentified => Err(crate::Error::HotKeyParseError(format!(
"Couldn't identify \"{}\" as a valid `Code`",
token
))),
_ => Ok(code),
}
} else {
Err(crate::Error::HotKeyParseError(format!(
"Couldn't identify \"{}\" as a valid `Code`",
token
)))
}
};
if len == 1 {
let token = split.next().unwrap();
key = parse_key(token)?;
} else {
for raw in split {
let token = raw.trim().to_string();
if token.is_empty() {
return Err(crate::Error::HotKeyParseError(
"Unexpected empty token while parsing hotkey".into(),
));
}
if key != Code::Unidentified {
// at this point we already parsed the modifiers and found a main key but
// the function received more then one main key or it is not in the right order
// examples:
// 1. "Ctrl+Shift+C+A" => only one main key should be allowd.
// 2. "Ctrl+C+Shift" => wrong order
return Err(crate::Error::HotKeyParseError(format!(
"Unexpected hotkey string format: \"{}\"",
hotkey_string
)));
}
match token.to_uppercase().as_str() {
"OPTION" | "ALT" => {
mods.set(Modifiers::ALT, true);
}
"CONTROL" | "CTRL" => {
mods.set(Modifiers::CONTROL, true);
}
"COMMAND" | "CMD" | "SUPER" => {
mods.set(Modifiers::META, true);
}
"SHIFT" => {
mods.set(Modifiers::SHIFT, true);
}
"COMMANDORCONTROL" | "COMMANDORCTRL" | "CMDORCTRL" | "CMDORCONTROL" => {
#[cfg(target_os = "macos")]
mods.set(Modifiers::META, true);
#[cfg(not(target_os = "macos"))]
mods.set(Modifiers::CONTROL, true);
}
_ => {
key = parse_key(token.as_str())?;
}
}
}
}
Ok(HotKey {
key,
mods,
id: COUNTER.next(),
})
}
#[test]
fn test_parse_hotkey() {
assert_eq!(
parse_hotkey("KeyX").unwrap(),
HotKey {
mods: Modifiers::empty(),
key: Code::KeyX,
id: 0,
}
);
assert_eq!(
parse_hotkey("CTRL+KeyX").unwrap(),
HotKey {
mods: Modifiers::CONTROL,
key: Code::KeyX,
id: 0,
}
);
assert_eq!(
parse_hotkey("SHIFT+KeyC").unwrap(),
HotKey {
mods: Modifiers::SHIFT,
key: Code::KeyC,
id: 0,
}
);
assert_eq!(
parse_hotkey("CTRL+KeyZ").unwrap(),
HotKey {
mods: Modifiers::CONTROL,
key: Code::KeyZ,
id: 0,
}
);
assert_eq!(
parse_hotkey("super+ctrl+SHIFT+alt+ArrowUp").unwrap(),
HotKey {
mods: Modifiers::META | Modifiers::CONTROL | Modifiers::SHIFT | Modifiers::ALT,
key: Code::ArrowUp,
id: 0,
}
);
assert_eq!(
parse_hotkey("Digit5").unwrap(),
HotKey {
mods: Modifiers::empty(),
key: Code::Digit5,
id: 0,
}
);
assert_eq!(
parse_hotkey("KeyG").unwrap(),
HotKey {
mods: Modifiers::empty(),
key: Code::KeyG,
id: 0,
}
);
let hotkey = parse_hotkey("+G");
assert!(hotkey.is_err());
let hotkey = parse_hotkey("SHGSH+G");
assert!(hotkey.is_err());
assert_eq!(
parse_hotkey("SHiFT+F12").unwrap(),
HotKey {
mods: Modifiers::SHIFT,
key: Code::F12,
id: 0,
}
);
assert_eq!(
parse_hotkey("CmdOrCtrl+Space").unwrap(),
HotKey {
#[cfg(target_os = "macos")]
mods: Modifiers::META,
#[cfg(not(target_os = "macos"))]
mods: Modifiers::CONTROL,
key: Code::Space,
id: 0,
}
);
let hotkey = parse_hotkey("CTRL+");
assert!(hotkey.is_err());
}

View File

@@ -1 +1,71 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crossbeam_channel::{unbounded, Receiver, Sender};
use once_cell::sync::Lazy;
mod counter;
mod error;
pub mod hotkey;
mod platform_impl;
pub use self::error::*;
use hotkey::HotKey;
/// Contains the id of the triggered [`HotKey`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct GlobalHotKeyEvent(pub u32);
impl GlobalHotKeyEvent {
/// Returns the id contained in this event
pub fn id(&self) -> u32 {
self.0
}
}
/// A reciever that could be used to listen to tray events.
pub type GlobalHotKeyEventReceiver = Receiver<GlobalHotKeyEvent>;
static GLOBAL_HOTKEY_CHANNEL: Lazy<(Sender<GlobalHotKeyEvent>, GlobalHotKeyEventReceiver)> =
Lazy::new(unbounded);
/// Gets a reference to the event channel's [TrayEventReceiver]
/// which can be used to listen for tray events.
pub fn global_hotkey_event_receiver<'a>() -> &'a GlobalHotKeyEventReceiver {
&GLOBAL_HOTKEY_CHANNEL.1
}
pub struct GlobalHotKeyManager {
platform_impl: platform_impl::GlobalHotKeyManager,
}
impl GlobalHotKeyManager {
pub fn new() -> crate::Result<Self> {
Ok(Self {
platform_impl: platform_impl::GlobalHotKeyManager::new()?,
})
}
pub fn register(&self, hotkey: HotKey) -> crate::Result<()> {
self.platform_impl.register(hotkey)
}
pub fn unregister(&self, hotkey: HotKey) -> crate::Result<()> {
self.platform_impl.unregister(hotkey)
}
pub fn register_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> {
for hotkey in hotkeys {
self.register(*hotkey)?;
}
Ok(())
}
pub fn unregister_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> {
for hotkey in hotkeys {
self.register(*hotkey)?;
}
Ok(())
}
}

View File

@@ -0,0 +1,16 @@
use std::ffi::c_void;
pub type EventRef = *const c_void;
#[link(name = "Carbon", kind = "framework")]
extern "C" {
pub fn GetEventParameter(
inEvent: isize,
inName: isize,
inDesiredType: isize,
outActualType: *mut c_void,
inBufferSize: isize,
outActualSize: *mut c_void,
outData: *mut c_void,
);
}

View File

@@ -0,0 +1,9 @@
mod ffi;
pub struct GlobalHotKeyManager {}
impl GlobalHotKeyManager {
pub fn init() -> Self {
Self {}
}
}

15
src/platform_impl/mod.rs Normal file
View File

@@ -0,0 +1,15 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#[cfg(target_os = "windows")]
#[path = "windows/mod.rs"]
mod platform;
#[cfg(target_os = "linux")]
#[path = "gtk/mod.rs"]
mod platform;
#[cfg(target_os = "macos")]
#[path = "macos/mod.rs"]
mod platform;
pub(crate) use self::platform::*;

View File

@@ -0,0 +1,299 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::ptr;
use keyboard_types::{Code, Modifiers};
use windows_sys::Win32::{
Foundation::{HWND, LPARAM, LRESULT, WPARAM},
UI::{
Input::KeyboardAndMouse::{
RegisterHotKey, UnregisterHotKey, VkKeyScanW, MOD_ALT, MOD_CONTROL, MOD_NOREPEAT,
MOD_SHIFT, MOD_WIN, VIRTUAL_KEY, VK_APPS, VK_BACK, VK_BROWSER_BACK,
VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH,
VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CAPITAL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE,
VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, VK_F19,
VK_F2, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7,
VK_F8, VK_F9, VK_HELP, VK_HOME, VK_INSERT, VK_KANA, VK_LAUNCH_MAIL, VK_LEFT,
VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_NEXT,
VK_NONCONVERT, VK_NUMLOCK, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_PAUSE,
VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SCROLL, VK_SNAPSHOT, VK_SPACE, VK_TAB, VK_UP,
VK_VOLUME_DOWN, VK_VOLUME_MUTE, VK_VOLUME_UP,
},
Shell::{DefSubclassProc, SetWindowSubclass},
WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, RegisterClassW, CW_USEDEFAULT, HMENU, WM_HOTKEY,
WNDCLASSW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT,
WS_OVERLAPPED,
},
},
};
use crate::{hotkey::HotKey, GLOBAL_HOTKEY_CHANNEL};
const GLOBAL_HOTKEY_SUBCLASS_ID: usize = 6001;
pub struct GlobalHotKeyManager {
hwnd: isize,
}
impl GlobalHotKeyManager {
pub fn new() -> crate::Result<Self> {
let class_name = encode_wide("tray_icon_app");
unsafe {
let hinstance = get_instance_handle();
unsafe extern "system" fn call_default_window_proc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
DefWindowProcW(hwnd, msg, wparam, lparam)
}
let wnd_class = WNDCLASSW {
lpfnWndProc: Some(call_default_window_proc),
lpszClassName: class_name.as_ptr(),
hInstance: hinstance,
..std::mem::zeroed()
};
RegisterClassW(&wnd_class);
let hwnd = CreateWindowExW(
WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_LAYERED |
// WS_EX_TOOLWINDOW prevents this window from ever showing up in the taskbar, which
// we want to avoid. If you remove this style, this window won't show up in the
// taskbar *initially*, but it can show up at some later point. This can sometimes
// happen on its own after several hours have passed, although this has proven
// difficult to reproduce. Alternatively, it can be manually triggered by killing
// `explorer.exe` and then starting the process back up.
// It is unclear why the bug is triggered by waiting for several hours.
WS_EX_TOOLWINDOW,
class_name.as_ptr(),
ptr::null(),
WS_OVERLAPPED,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
HWND::default(),
HMENU::default(),
hinstance,
std::ptr::null_mut(),
);
if hwnd == 0 {
return Err(crate::Error::OsError(std::io::Error::last_os_error()));
}
SetWindowSubclass(
hwnd,
Some(global_hotkey_subclass_proc),
GLOBAL_HOTKEY_SUBCLASS_ID,
0,
);
Ok(Self { hwnd })
}
}
pub fn register(&self, hotkey: HotKey) -> crate::Result<()> {
let mut mods = MOD_NOREPEAT;
if hotkey.mods.contains(Modifiers::SHIFT) {
mods |= MOD_SHIFT;
}
if hotkey.mods.intersects(Modifiers::SUPER | Modifiers::META) {
mods |= MOD_WIN;
}
if hotkey.mods.contains(Modifiers::ALT) {
mods |= MOD_ALT;
}
if hotkey.mods.contains(Modifiers::CONTROL) {
mods |= MOD_CONTROL;
}
// get key scan code
match key_to_vk(&hotkey.key) {
Some(vk_code) => {
let result =
unsafe { RegisterHotKey(self.hwnd, hotkey.id() as _, mods, vk_code as _) };
if result == 0 {
return Err(crate::Error::OsError(std::io::Error::last_os_error()));
}
}
_ => {
return Err(crate::Error::FailedToRegister(format!(
"Unable to register accelerator (unknown VKCode for this char: {}).",
hotkey.key
)))
}
}
Ok(())
}
pub fn unregister(&self, hotkey: HotKey) -> crate::Result<()> {
let result = unsafe { UnregisterHotKey(self.hwnd, hotkey.id() as _) };
if result == 0 {
return Err(crate::Error::OsError(std::io::Error::last_os_error()));
}
Ok(())
}
}
unsafe extern "system" fn global_hotkey_subclass_proc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
_id: usize,
_subclass_input_ptr: usize,
) -> LRESULT {
if msg == WM_HOTKEY {
let _ = &GLOBAL_HOTKEY_CHANNEL
.0
.send(crate::GlobalHotKeyEvent(wparam as _));
}
DefSubclassProc(hwnd, msg, wparam, lparam)
}
pub fn encode_wide<S: AsRef<std::ffi::OsStr>>(string: S) -> Vec<u16> {
std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
.chain(std::iter::once(0))
.collect()
}
pub fn get_instance_handle() -> windows_sys::Win32::Foundation::HINSTANCE {
// Gets the instance handle by taking the address of the
// pseudo-variable created by the microsoft linker:
// https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483
// This is preferred over GetModuleHandle(NULL) because it also works in DLLs:
// https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance
extern "C" {
static __ImageBase: windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER;
}
unsafe { &__ImageBase as *const _ as _ }
}
// used to build accelerators table from Key
fn key_to_vk(key: &Code) -> Option<VIRTUAL_KEY> {
Some(match key {
Code::KeyA => unsafe { VkKeyScanW('a' as u16) as u16 },
Code::KeyB => unsafe { VkKeyScanW('b' as u16) as u16 },
Code::KeyC => unsafe { VkKeyScanW('c' as u16) as u16 },
Code::KeyD => unsafe { VkKeyScanW('d' as u16) as u16 },
Code::KeyE => unsafe { VkKeyScanW('e' as u16) as u16 },
Code::KeyF => unsafe { VkKeyScanW('f' as u16) as u16 },
Code::KeyG => unsafe { VkKeyScanW('g' as u16) as u16 },
Code::KeyH => unsafe { VkKeyScanW('h' as u16) as u16 },
Code::KeyI => unsafe { VkKeyScanW('i' as u16) as u16 },
Code::KeyJ => unsafe { VkKeyScanW('j' as u16) as u16 },
Code::KeyK => unsafe { VkKeyScanW('k' as u16) as u16 },
Code::KeyL => unsafe { VkKeyScanW('l' as u16) as u16 },
Code::KeyM => unsafe { VkKeyScanW('m' as u16) as u16 },
Code::KeyN => unsafe { VkKeyScanW('n' as u16) as u16 },
Code::KeyO => unsafe { VkKeyScanW('o' as u16) as u16 },
Code::KeyP => unsafe { VkKeyScanW('p' as u16) as u16 },
Code::KeyQ => unsafe { VkKeyScanW('q' as u16) as u16 },
Code::KeyR => unsafe { VkKeyScanW('r' as u16) as u16 },
Code::KeyS => unsafe { VkKeyScanW('s' as u16) as u16 },
Code::KeyT => unsafe { VkKeyScanW('t' as u16) as u16 },
Code::KeyU => unsafe { VkKeyScanW('u' as u16) as u16 },
Code::KeyV => unsafe { VkKeyScanW('v' as u16) as u16 },
Code::KeyW => unsafe { VkKeyScanW('w' as u16) as u16 },
Code::KeyX => unsafe { VkKeyScanW('x' as u16) as u16 },
Code::KeyY => unsafe { VkKeyScanW('y' as u16) as u16 },
Code::KeyZ => unsafe { VkKeyScanW('z' as u16) as u16 },
Code::Digit0 => unsafe { VkKeyScanW('0' as u16) as u16 },
Code::Digit1 => unsafe { VkKeyScanW('1' as u16) as u16 },
Code::Digit2 => unsafe { VkKeyScanW('2' as u16) as u16 },
Code::Digit3 => unsafe { VkKeyScanW('3' as u16) as u16 },
Code::Digit4 => unsafe { VkKeyScanW('4' as u16) as u16 },
Code::Digit5 => unsafe { VkKeyScanW('5' as u16) as u16 },
Code::Digit6 => unsafe { VkKeyScanW('6' as u16) as u16 },
Code::Digit7 => unsafe { VkKeyScanW('7' as u16) as u16 },
Code::Digit8 => unsafe { VkKeyScanW('8' as u16) as u16 },
Code::Digit9 => unsafe { VkKeyScanW('9' as u16) as u16 },
Code::Comma => VK_OEM_COMMA,
Code::Minus => VK_OEM_MINUS,
Code::Period => VK_OEM_PERIOD,
Code::Equal => unsafe { VkKeyScanW('=' as u16) as u16 },
Code::Semicolon => unsafe { VkKeyScanW(';' as u16) as u16 },
Code::Slash => unsafe { VkKeyScanW('/' as u16) as u16 },
Code::Backslash => unsafe { VkKeyScanW('\\' as u16) as u16 },
Code::Quote => unsafe { VkKeyScanW('\'' as u16) as u16 },
Code::Backquote => unsafe { VkKeyScanW('`' as u16) as u16 },
Code::BracketLeft => unsafe { VkKeyScanW('[' as u16) as u16 },
Code::BracketRight => unsafe { VkKeyScanW(']' as u16) as u16 },
Code::Backspace => VK_BACK,
Code::Tab => VK_TAB,
Code::Space => VK_SPACE,
Code::Enter => VK_RETURN,
Code::Pause => VK_PAUSE,
Code::CapsLock => VK_CAPITAL,
Code::KanaMode => VK_KANA,
Code::Escape => VK_ESCAPE,
Code::NonConvert => VK_NONCONVERT,
Code::PageUp => VK_PRIOR,
Code::PageDown => VK_NEXT,
Code::End => VK_END,
Code::Home => VK_HOME,
Code::ArrowLeft => VK_LEFT,
Code::ArrowUp => VK_UP,
Code::ArrowRight => VK_RIGHT,
Code::ArrowDown => VK_DOWN,
Code::PrintScreen => VK_SNAPSHOT,
Code::Insert => VK_INSERT,
Code::Delete => VK_DELETE,
Code::Help => VK_HELP,
Code::ContextMenu => VK_APPS,
Code::F1 => VK_F1,
Code::F2 => VK_F2,
Code::F3 => VK_F3,
Code::F4 => VK_F4,
Code::F5 => VK_F5,
Code::F6 => VK_F6,
Code::F7 => VK_F7,
Code::F8 => VK_F8,
Code::F9 => VK_F9,
Code::F10 => VK_F10,
Code::F11 => VK_F11,
Code::F12 => VK_F12,
Code::F13 => VK_F13,
Code::F14 => VK_F14,
Code::F15 => VK_F15,
Code::F16 => VK_F16,
Code::F17 => VK_F17,
Code::F18 => VK_F18,
Code::F19 => VK_F19,
Code::F20 => VK_F20,
Code::F21 => VK_F21,
Code::F22 => VK_F22,
Code::F23 => VK_F23,
Code::F24 => VK_F24,
Code::NumLock => VK_NUMLOCK,
Code::ScrollLock => VK_SCROLL,
Code::BrowserBack => VK_BROWSER_BACK,
Code::BrowserForward => VK_BROWSER_FORWARD,
Code::BrowserRefresh => VK_BROWSER_REFRESH,
Code::BrowserStop => VK_BROWSER_STOP,
Code::BrowserSearch => VK_BROWSER_SEARCH,
Code::BrowserFavorites => VK_BROWSER_FAVORITES,
Code::BrowserHome => VK_BROWSER_HOME,
Code::AudioVolumeMute => VK_VOLUME_MUTE,
Code::AudioVolumeDown => VK_VOLUME_DOWN,
Code::AudioVolumeUp => VK_VOLUME_UP,
Code::MediaTrackNext => VK_MEDIA_NEXT_TRACK,
Code::MediaTrackPrevious => VK_MEDIA_PREV_TRACK,
Code::MediaStop => VK_MEDIA_STOP,
Code::MediaPlayPause => VK_MEDIA_PLAY_PAUSE,
Code::LaunchMail => VK_LAUNCH_MAIL,
Code::Convert => VK_INSERT,
_ => return None,
})
}