use std::ffi::OsStr;
use std::mem::MaybeUninit;
use std::os::windows::ffi::OsStrExt;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::mpsc;
use std::time::Duration;
use std::{iter, mem, ptr};
use winapi::shared::{hidusage, minwindef, windef};
use winapi::um::{libloaderapi, winuser};
use crate::input::{Action, Button};
use crate::vk::Vk;
use crate::{WheelDirection, WindowsError};
static STATE: AtomicU8 = AtomicU8::new(0);
static mut SENDER: MaybeUninit<mpsc::Sender<Event>> = MaybeUninit::uninit();
static mut BUFFER: MaybeUninit<Vec<u8>> = MaybeUninit::uninit();
#[inline]
fn has_flags(short: u16, mask: u16) -> bool {
short & mask == mask
}
unsafe extern "system" fn window_proc(
hwnd: windef::HWND,
msg: minwindef::UINT,
w_param: minwindef::WPARAM,
l_param: minwindef::LPARAM,
) -> minwindef::LRESULT {
match msg {
winuser::WM_INPUT => loop {
let mut size = 0;
let mut result = winuser::GetRawInputData(
l_param as winuser::HRAWINPUT,
winuser::RID_INPUT,
ptr::null_mut(),
&mut size,
mem::size_of::<winuser::RAWINPUTHEADER>() as _,
);
if result == -1i32 as u32 {
break;
}
let buffer = &mut *BUFFER.as_mut_ptr();
buffer.clear();
buffer.reserve(size as _);
result = winuser::GetRawInputData(
l_param as winuser::HRAWINPUT,
winuser::RID_INPUT,
buffer.as_mut_ptr() as _,
&mut size,
mem::size_of::<winuser::RAWINPUTHEADER>() as _,
);
if result != size {
break;
}
let raw_input = &*(buffer.as_mut_ptr() as winuser::PRAWINPUT);
let sender = &mut *SENDER.as_mut_ptr();
match raw_input.header.dwType {
winuser::RIM_TYPEMOUSE => {
let data = raw_input.data.mouse();
if has_flags(data.usFlags, winuser::MOUSE_MOVE_RELATIVE) {
sender
.send(Event::MouseMoveRelative {
x: data.lLastX,
y: data.lLastY,
})
.unwrap();
}
if has_flags(data.usFlags, winuser::MOUSE_MOVE_ABSOLUTE) {
sender
.send(Event::MouseMoveAbsolute {
x: data.lLastX as f32 / 65535.0,
y: data.lLastY as f32 / 65535.0,
virtual_desk: data.usFlags
& winuser::MOUSE_VIRTUAL_DESKTOP
== winuser::MOUSE_VIRTUAL_DESKTOP,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_LEFT_BUTTON_DOWN) {
sender
.send(Event::MouseButton {
action: Action::Press,
button: Button::Left,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_LEFT_BUTTON_UP) {
sender
.send(Event::MouseButton {
action: Action::Release,
button: Button::Left,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_RIGHT_BUTTON_DOWN)
{
sender
.send(Event::MouseButton {
action: Action::Press,
button: Button::Right,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_RIGHT_BUTTON_UP) {
sender
.send(Event::MouseButton {
action: Action::Release,
button: Button::Right,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_MIDDLE_BUTTON_DOWN)
{
sender
.send(Event::MouseButton {
action: Action::Press,
button: Button::Middle,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_MIDDLE_BUTTON_UP) {
sender
.send(Event::MouseButton {
action: Action::Release,
button: Button::Middle,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_BUTTON_4_DOWN) {
sender
.send(Event::MouseButton {
action: Action::Press,
button: Button::X1,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_BUTTON_4_UP) {
sender
.send(Event::MouseButton {
action: Action::Release,
button: Button::X1,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_BUTTON_5_DOWN) {
sender
.send(Event::MouseButton {
action: Action::Press,
button: Button::X2,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_BUTTON_5_UP) {
sender
.send(Event::MouseButton {
action: Action::Release,
button: Button::X2,
})
.unwrap();
}
if has_flags(data.usButtonFlags, winuser::RI_MOUSE_WHEEL) {
sender
.send(Event::MouseWheel {
delta: data.usButtonData as i16 as f32 / 120.0,
direction: WheelDirection::Vertical,
})
.unwrap();
}
if has_flags(data.usButtonFlags, 0x0800) {
sender
.send(Event::MouseWheel {
delta: data.usButtonData as i16 as f32 / 120.0,
direction: WheelDirection::Horizontal,
})
.unwrap();
}
}
winuser::RIM_TYPEKEYBOARD => {
let data = raw_input.data.keyboard();
sender
.send(Event::Keyboard {
vk: Vk::from_u8(data.VKey as u8),
scan_code: data.MakeCode as u32,
action: Action::from_press(data.Flags & 1 == 0),
})
.unwrap();
}
2 => (),
_ => unreachable!("Invalid message"),
}
break;
},
_ => (),
}
winuser::DefWindowProcW(hwnd, msg, w_param, l_param)
}
#[derive(Clone, Debug)]
pub enum MessageLoopError {
AlreadyActive,
OsError(WindowsError),
}
#[inline]
pub fn is_active() -> bool {
STATE.load(Ordering::Acquire) != 0
}
pub fn start() -> Result<EventReceiver, MessageLoopError> {
loop {
match STATE.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst) {
Ok(0) => break,
Err(3) => (),
_ => return Err(MessageLoopError::AlreadyActive),
}
std::hint::spin_loop();
}
let (s, r) = mpsc::channel();
unsafe {
SENDER = MaybeUninit::new(s);
BUFFER = MaybeUninit::new(Vec::new());
}
let (error_s, error_r) = mpsc::channel();
std::thread::spawn(move || {
unsafe {
let h_instance = libloaderapi::GetModuleHandleW(ptr::null());
let class_name = OsStr::new("winput_message_loop")
.encode_wide()
.chain(iter::once(0))
.collect::<Vec<_>>();
let mut wnd_class: winuser::WNDCLASSW = mem::zeroed();
wnd_class.hInstance = h_instance;
wnd_class.lpszClassName = class_name.as_ptr();
wnd_class.lpfnWndProc = Some(window_proc);
let class = winuser::RegisterClassW(&wnd_class);
if class == 0 {
error_s
.send(Err(MessageLoopError::OsError(
WindowsError::from_last_error(),
)))
.unwrap();
return;
}
let h_wnd = winuser::CreateWindowExW(
0,
class_name.as_ptr(),
class_name.as_ptr(),
0,
0,
0,
0,
0,
winuser::HWND_MESSAGE,
ptr::null_mut(),
h_instance,
ptr::null_mut(),
);
if h_wnd.is_null() {
error_s
.send(Err(MessageLoopError::OsError(
WindowsError::from_last_error(),
)))
.unwrap();
return;
}
let mut rid: [winuser::RAWINPUTDEVICE; 2] = mem::zeroed();
rid[0].dwFlags = winuser::RIDEV_NOLEGACY | winuser::RIDEV_INPUTSINK;
rid[0].usUsagePage = hidusage::HID_USAGE_PAGE_GENERIC;
rid[0].usUsage = hidusage::HID_USAGE_GENERIC_KEYBOARD;
rid[0].hwndTarget = h_wnd;
rid[1].dwFlags = winuser::RIDEV_NOLEGACY | winuser::RIDEV_INPUTSINK;
rid[1].usUsagePage = hidusage::HID_USAGE_PAGE_GENERIC;
rid[1].usUsage = hidusage::HID_USAGE_GENERIC_MOUSE;
rid[1].hwndTarget = h_wnd;
let result = winuser::RegisterRawInputDevices(
rid.as_ptr(),
rid.len() as _,
mem::size_of::<winuser::RAWINPUTDEVICE>() as _,
);
if result == 0 {
error_s
.send(Err(MessageLoopError::OsError(
WindowsError::from_last_error(),
)))
.unwrap();
return;
}
STATE.store(2, Ordering::SeqCst);
error_s.send(Ok(())).unwrap();
drop(error_s);
let mut msg = mem::zeroed();
while STATE.load(Ordering::SeqCst) == 2 {
let result = winuser::GetMessageW(&mut msg, h_wnd, 0, 0);
if result == -1 {
break;
} else {
winuser::TranslateMessage(&msg);
winuser::DispatchMessageW(&msg);
}
}
ptr::drop_in_place(SENDER.as_mut_ptr());
ptr::drop_in_place(BUFFER.as_mut_ptr());
STATE.store(0, Ordering::SeqCst);
}
});
error_r
.recv()
.unwrap()
.map(|()| EventReceiver { receiver: r })
}
#[derive(Clone, Copy, Debug)]
pub enum Event {
Keyboard {
vk: Vk,
scan_code: u32,
action: Action,
},
MouseMoveRelative {
x: i32,
y: i32,
},
MouseMoveAbsolute {
x: f32,
y: f32,
virtual_desk: bool,
},
MouseButton {
action: Action,
button: Button,
},
MouseWheel {
delta: f32,
direction: WheelDirection,
},
}
pub struct EventReceiver {
receiver: mpsc::Receiver<Event>,
}
impl EventReceiver {
#[inline]
pub fn clear(&self) {
if is_active() {
while let Some(_) = self.try_next_event() {}
}
}
#[inline]
pub fn next_event(&self) -> Event {
self.receiver
.recv()
.expect("The message loop is not active")
}
#[inline]
pub fn next_event_timeout(&self, timeout: Duration) -> Option<Event> {
match self.receiver.recv_timeout(timeout) {
Ok(val) => Some(val),
Err(mpsc::RecvTimeoutError::Timeout) => None,
Err(mpsc::RecvTimeoutError::Disconnected) => {
panic!("The message loop is not active")
}
}
}
#[inline]
pub fn try_next_event(&self) -> Option<Event> {
match self.receiver.try_recv() {
Ok(val) => Some(val),
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => {
panic!("The message loop is not active")
}
}
}
}
impl Drop for EventReceiver {
fn drop(&mut self) {
stop();
}
}
pub fn stop() {
if !is_active() {
return;
}
STATE.store(3, Ordering::SeqCst);
while STATE.load(Ordering::Acquire) != 0 {
std::hint::spin_loop();
}
}