use std::sync::mpsc::channel;
use std::thread::{self, JoinHandle};
use windows::core::w;
use windows::Win32::Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::UI::Input::{
GetRawInputData, RegisterRawInputDevices, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTHEADER,
RIDEV_INPUTSINK, RIDEV_REMOVE, RID_INPUT, RIM_TYPEKEYBOARD,
};
use windows::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetMessageW,
GetWindowLongPtrW, PostMessageW, PostQuitMessage, RegisterClassW, SetWindowLongPtrW,
TranslateMessage, GWLP_USERDATA, MSG, WM_CLOSE, WM_DESTROY, WM_INPUT, WNDCLASSW,
WS_EX_TOOLWINDOW, WS_POPUP,
};
const HID_USAGE_PAGE_GENERIC: u16 = 0x01;
const HID_USAGE_GENERIC_KEYBOARD: u16 = 0x06;
const RI_KEY_BREAK: u16 = 0x01;
#[derive(Debug, Clone, Copy)]
pub(crate) struct RawKeyEvent {
pub vk: u16,
pub is_down: bool,
}
pub(crate) struct RawKeyboardListener {
hwnd: isize,
thread: Option<JoinHandle<()>>,
}
impl RawKeyboardListener {
pub(crate) fn start<F>(callback: F) -> Result<Self, String>
where
F: Fn(RawKeyEvent) + Send + 'static,
{
let (ready_tx, ready_rx) = channel::<Result<isize, String>>();
let thread = thread::Builder::new()
.name("zerolaunch-raw-keyboard".to_string())
.spawn(move || {
let callback: Box<dyn Fn(RawKeyEvent) + Send> = Box::new(callback);
let hmodule = unsafe { GetModuleHandleW(None) }.unwrap_or_default();
let hinstance = HINSTANCE(hmodule.0);
let class_name = w!("ZeroLaunchRawKeyboardSink");
let wnd_class = WNDCLASSW {
lpfnWndProc: Some(wnd_proc),
hInstance: hinstance,
lpszClassName: class_name,
..Default::default()
};
unsafe { RegisterClassW(&wnd_class) };
let hwnd = unsafe {
CreateWindowExW(
WS_EX_TOOLWINDOW,
class_name,
w!("ZeroLaunchRawKeyboardSink"),
WS_POPUP,
0,
0,
0,
0,
None,
None,
Some(hinstance),
None,
)
};
let hwnd = match hwnd {
Ok(hwnd) => hwnd,
Err(e) => {
let _ = ready_tx.send(Err(format!("创建原始输入隐藏窗口失败: {e}")));
return;
}
};
unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, &callback as *const _ as isize) };
let registration = RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_KEYBOARD,
dwFlags: RIDEV_INPUTSINK,
hwndTarget: hwnd,
};
if let Err(e) = unsafe {
RegisterRawInputDevices(
&[registration],
std::mem::size_of::<RAWINPUTDEVICE>() as u32,
)
} {
let _ = ready_tx.send(Err(format!("注册原始输入键盘设备失败: {e}")));
let _ = unsafe { DestroyWindow(hwnd) };
return;
}
let _ = ready_tx.send(Ok(hwnd.0 as isize));
let mut msg = MSG::default();
unsafe {
while GetMessageW(&mut msg, None, 0, 0).as_bool() {
let _ = TranslateMessage(&msg);
DispatchMessageW(&msg);
}
let unregister = RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_KEYBOARD,
dwFlags: RIDEV_REMOVE,
hwndTarget: HWND::default(),
};
let _ = RegisterRawInputDevices(
&[unregister],
std::mem::size_of::<RAWINPUTDEVICE>() as u32,
);
}
})
.map_err(|e| format!("创建原始输入监听线程失败: {e}"))?;
let hwnd = ready_rx
.recv()
.map_err(|e| format!("原始输入监听线程未就绪: {e}"))??;
Ok(Self {
hwnd,
thread: Some(thread),
})
}
pub(crate) fn stop(mut self) {
let hwnd = HWND(self.hwnd as *mut std::ffi::c_void);
let _ = unsafe { PostMessageW(Some(hwnd), WM_CLOSE, WPARAM(0), LPARAM(0)) };
if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}
unsafe extern "system" fn wnd_proc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
match msg {
WM_INPUT => {
deliver_key_event(hwnd, lparam);
DefWindowProcW(hwnd, msg, wparam, lparam)
}
WM_CLOSE => {
let _ = DestroyWindow(hwnd);
LRESULT(0)
}
WM_DESTROY => {
PostQuitMessage(0);
LRESULT(0)
}
_ => DefWindowProcW(hwnd, msg, wparam, lparam),
}
}
unsafe fn deliver_key_event(hwnd: HWND, lparam: LPARAM) {
let handle = HRAWINPUT(lparam.0 as *mut std::ffi::c_void);
let header_size = std::mem::size_of::<RAWINPUTHEADER>() as u32;
let mut size = 0u32;
if GetRawInputData(handle, RID_INPUT, None, &mut size, header_size) == u32::MAX || size == 0 {
return;
}
let mut buffer = vec![0u64; (size as usize).div_ceil(8)];
let read = GetRawInputData(
handle,
RID_INPUT,
Some(buffer.as_mut_ptr() as *mut std::ffi::c_void),
&mut size,
header_size,
);
if read == u32::MAX || read == 0 {
return;
}
let raw = &*(buffer.as_ptr() as *const RAWINPUT);
if raw.header.dwType != RIM_TYPEKEYBOARD.0 {
return;
}
let keyboard = raw.data.keyboard;
let event = RawKeyEvent {
vk: keyboard.VKey,
is_down: keyboard.Flags & RI_KEY_BREAK == 0,
};
let callback = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *const Box<dyn Fn(RawKeyEvent) + Send>;
if !callback.is_null() {
(*callback)(event);
}
}