use std::{
mem, ptr,
sync::{Arc, Mutex, OnceLock},
};
use raw_window_handle::RawWindowHandle;
use winapi::{
shared::{
minwindef::{FALSE, LPARAM, UINT, WPARAM},
windef::HWND,
},
um::winuser::{
CallWindowProcW, DefWindowProcW, GetRawInputData, GetWindowLongPtrW,
RegisterRawInputDevices, SetWindowLongPtrW, GWLP_WNDPROC, RAWINPUT,
RAWINPUTDEVICE, RAWINPUTHEADER, RIDEV_INPUTSINK, RIDEV_REMOVE,
RID_INPUT, RIM_TYPEMOUSE, WM_INPUT, WNDPROC,
},
};
use super::{MouseDelta, RawMouseInputTrait};
use crate::settings::{MapType, SettingsMapType};
static GLOBAL_MOUSE_DELTA: OnceLock<Arc<Mutex<MouseDelta>>> = OnceLock::new();
static ORIGINAL_PROCS: OnceLock<Arc<Mutex<MapType<isize, WNDPROC>>>> =
OnceLock::new();
fn get_global_delta() -> &'static Arc<Mutex<MouseDelta>> {
GLOBAL_MOUSE_DELTA
.get_or_init(|| Arc::new(Mutex::new(MouseDelta::default())))
}
fn get_original_procs() -> &'static Arc<Mutex<MapType<isize, WNDPROC>>> {
ORIGINAL_PROCS.get_or_init(|| Arc::new(Mutex::new(MapType::new_map())))
}
fn update_global_delta(delta: MouseDelta) {
if let Ok(mut global_delta) = get_global_delta().lock() {
global_delta.dx += delta.dx;
global_delta.dy += delta.dy;
}
}
fn reset_global_delta() {
if let Ok(mut global_delta) = get_global_delta().lock() {
*global_delta = MouseDelta::default();
}
}
fn get_original_proc(hwnd: HWND) -> Option<WNDPROC> {
get_original_procs().lock().ok()?.get(&(hwnd as isize)).copied()
}
fn store_original_proc(hwnd: HWND, proc: WNDPROC) {
if let Ok(mut procs) = get_original_procs().lock() {
procs.insert(hwnd as isize, proc);
}
}
fn remove_original_proc(hwnd: HWND) {
if let Ok(mut procs) = get_original_procs().lock() {
procs.remove_thingy(&(hwnd as isize));
}
}
unsafe fn process_raw_input(lparam: LPARAM) {
let mut size: UINT = 0;
let result = GetRawInputData(
lparam as *mut _,
RID_INPUT,
ptr::null_mut(),
&raw mut size,
mem::size_of::<RAWINPUTHEADER>() as UINT,
);
if result == u32::MAX || size == 0 {
return;
}
let mut buffer = vec![0u8; size as usize];
let result = GetRawInputData(
lparam as *mut _,
RID_INPUT,
buffer.as_mut_ptr().cast(),
&raw mut size,
mem::size_of::<RAWINPUTHEADER>() as UINT,
);
if result == u32::MAX {
return;
}
#[allow(clippy::cast_ptr_alignment)]
let raw_input = &*buffer.as_ptr().cast::<RAWINPUT>();
if raw_input.header.dwType == RIM_TYPEMOUSE {
let mouse = &raw_input.data.mouse();
let dx = mouse.lLastX;
let dy = mouse.lLastY;
if dx != 0 || dy != 0 {
let delta = MouseDelta {
dx,
dy,
};
update_global_delta(delta);
}
}
}
unsafe extern "system" fn raw_mouse_window_proc(
hwnd: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
) -> isize {
if msg == WM_INPUT {
process_raw_input(lparam);
}
get_original_proc(hwnd).map_or_else(
|| DefWindowProcW(hwnd, msg, wparam, lparam),
|original_proc| {
CallWindowProcW(original_proc, hwnd, msg, wparam, lparam)
},
)
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "c_compatible", repr(C))]
pub struct RawMouseInputWindows {
hwnd: HWND,
original_proc: WNDPROC,
}
impl RawMouseInputTrait for RawMouseInputWindows {
fn new(handle: RawWindowHandle) -> Result<Self, &'static str> {
let hwnd = match handle {
RawWindowHandle::Win32(win32_handle) => {
win32_handle.hwnd.get() as HWND
}
_ => return Err("Only Win32 window handles are supported"),
};
if hwnd.is_null() {
return Err("Invalid window handle");
}
unsafe {
let original_proc_ptr = GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
if original_proc_ptr == 0 {
return Err("Failed to get original window procedure");
}
let original_proc: WNDPROC = std::mem::transmute(original_proc_ptr);
store_original_proc(hwnd, original_proc);
#[allow(clippy::fn_to_numeric_cast)]
#[allow(function_casts_as_integer)]
let result = SetWindowLongPtrW(
hwnd,
GWLP_WNDPROC,
raw_mouse_window_proc as isize,
);
if result == 0 {
remove_original_proc(hwnd);
return Err("Failed to set window procedure");
}
let rid = RAWINPUTDEVICE {
usUsagePage: 0x01, usUsage: 0x02, dwFlags: RIDEV_INPUTSINK, hwndTarget: hwnd, };
let register_result = RegisterRawInputDevices(
&raw const rid,
1,
mem::size_of::<RAWINPUTDEVICE>() as UINT,
);
if register_result == FALSE {
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, original_proc_ptr);
remove_original_proc(hwnd);
return Err("Failed to register raw input device");
}
Ok(Self {
hwnd,
original_proc,
})
}
}
fn get_delta(&self) -> (i32, i32) {
let delta = get_global_delta()
.lock()
.map_or_else(|_| MouseDelta::default(), |buffer| *buffer);
reset_global_delta();
(delta.dx, delta.dy)
}
}
impl Drop for RawMouseInputWindows {
fn drop(&mut self) {
unsafe {
#[allow(clippy::missing_transmute_annotations)]
SetWindowLongPtrW(
self.hwnd,
GWLP_WNDPROC,
std::mem::transmute(self.original_proc),
);
remove_original_proc(self.hwnd);
let rid = RAWINPUTDEVICE {
usUsagePage: 0x01,
usUsage: 0x02,
dwFlags: RIDEV_REMOVE,
hwndTarget: ptr::null_mut(),
};
RegisterRawInputDevices(
&raw const rid,
1,
mem::size_of::<RAWINPUTDEVICE>() as UINT,
);
}
}
}