use super::HidBackend;
use crate::error::{Error, Result};
#[cfg(target_os = "windows")]
use windows::Win32::UI::Input::KeyboardAndMouse::{
SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, INPUT_MOUSE,
KEYBDINPUT, KEYBD_EVENT_FLAGS, KEYEVENTF_KEYUP,
MOUSEINPUT, MOUSE_EVENT_FLAGS,
MOUSEEVENTF_MOVE, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP,
MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP,
MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP,
MOUSEEVENTF_WHEEL,
VIRTUAL_KEY,
};
#[cfg(target_os = "windows")]
const WHEEL_DELTA: i32 = 120;
#[cfg(target_os = "windows")]
use std::mem::size_of;
#[cfg(target_os = "windows")]
pub struct WindowsMouseBackend {
buttons: std::cell::Cell<u8>,
}
#[cfg(target_os = "windows")]
unsafe impl Send for WindowsMouseBackend {}
#[cfg(target_os = "windows")]
unsafe impl Sync for WindowsMouseBackend {}
#[cfg(target_os = "windows")]
impl WindowsMouseBackend {
pub fn new() -> Result<Self> {
Ok(Self { buttons: std::cell::Cell::new(0) })
}
fn send_mouse_input(&self, flags: MOUSE_EVENT_FLAGS, dx: i32, dy: i32, data: i32) -> Result<()> {
let input = INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx,
dy,
mouseData: data as u32,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
},
},
};
unsafe {
let sent = SendInput(&[input], size_of::<INPUT>() as i32);
if sent == 0 {
return Err(Error::SendFailed("SendInput failed".into()));
}
}
Ok(())
}
}
#[cfg(target_os = "windows")]
impl HidBackend for WindowsMouseBackend {
fn send_report(&self, report: &[u8]) -> Result<()> {
if report.len() < 4 {
return Err(Error::SendFailed("Invalid mouse report".into()));
}
let buttons = report[0];
let dx = report[1] as i8 as i32;
let dy = report[2] as i8 as i32;
let wheel = report[3] as i8 as i32;
if dx != 0 || dy != 0 {
self.send_mouse_input(MOUSEEVENTF_MOVE, dx, dy, 0)?;
}
let old_buttons = self.buttons.get();
self.buttons.set(buttons);
if (buttons & 0x01) != 0 && (old_buttons & 0x01) == 0 {
self.send_mouse_input(MOUSEEVENTF_LEFTDOWN, 0, 0, 0)?;
} else if (buttons & 0x01) == 0 && (old_buttons & 0x01) != 0 {
self.send_mouse_input(MOUSEEVENTF_LEFTUP, 0, 0, 0)?;
}
if (buttons & 0x02) != 0 && (old_buttons & 0x02) == 0 {
self.send_mouse_input(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0)?;
} else if (buttons & 0x02) == 0 && (old_buttons & 0x02) != 0 {
self.send_mouse_input(MOUSEEVENTF_RIGHTUP, 0, 0, 0)?;
}
if (buttons & 0x04) != 0 && (old_buttons & 0x04) == 0 {
self.send_mouse_input(MOUSEEVENTF_MIDDLEDOWN, 0, 0, 0)?;
} else if (buttons & 0x04) == 0 && (old_buttons & 0x04) != 0 {
self.send_mouse_input(MOUSEEVENTF_MIDDLEUP, 0, 0, 0)?;
}
if wheel != 0 {
self.send_mouse_input(MOUSEEVENTF_WHEEL, 0, 0, wheel * WHEEL_DELTA as i32)?;
}
Ok(())
}
fn destroy(self: Box<Self>) -> Result<()> {
Ok(())
}
}
#[cfg(target_os = "windows")]
pub struct WindowsKeyboardBackend;
#[cfg(target_os = "windows")]
unsafe impl Send for WindowsKeyboardBackend {}
#[cfg(target_os = "windows")]
unsafe impl Sync for WindowsKeyboardBackend {}
#[cfg(target_os = "windows")]
impl WindowsKeyboardBackend {
pub fn new() -> Result<Self> {
Ok(Self)
}
fn send_key(&self, vk: u16, down: bool) -> Result<()> {
let flags = if down {
KEYBD_EVENT_FLAGS::default()
} else {
KEYEVENTF_KEYUP
};
let input = INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: VIRTUAL_KEY(vk),
wScan: 0,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
},
},
};
unsafe {
let sent = SendInput(&[input], size_of::<INPUT>() as i32);
if sent == 0 {
return Err(Error::SendFailed("SendInput failed".into()));
}
}
Ok(())
}
fn hid_to_vk(hid: u8) -> Option<u16> {
Some(match hid {
0x04..=0x1D => (hid - 0x04 + 0x41) as u16, 0x1E..=0x26 => (hid - 0x1E + 0x31) as u16, 0x27 => 0x30, 0x28 => 0x0D, 0x29 => 0x1B, 0x2A => 0x08, 0x2B => 0x09, 0x2C => 0x20, 0x2D => 0xBD, 0x2E => 0xBB, 0x2F => 0xDB, 0x30 => 0xDD, 0x31 => 0xDC, 0x33 => 0xBA, 0x34 => 0xDE, 0x35 => 0xC0, 0x36 => 0xBC, 0x37 => 0xBE, 0x38 => 0xBF, 0x39 => 0x14, 0x3A..=0x45 => (hid - 0x3A + 0x70) as u16, 0x49 => 0x2D, 0x4A => 0x24, 0x4B => 0x21, 0x4C => 0x2E, 0x4D => 0x23, 0x4E => 0x22, 0x4F => 0x27, 0x50 => 0x25, 0x51 => 0x28, 0x52 => 0x26, _ => return None,
})
}
}
#[cfg(target_os = "windows")]
impl HidBackend for WindowsKeyboardBackend {
fn send_report(&self, report: &[u8]) -> Result<()> {
if report.len() < 8 {
return Err(Error::SendFailed("Invalid keyboard report".into()));
}
let modifiers = report[0];
let keys = &report[2..8];
if modifiers & 0x01 != 0 {
self.send_key(0xA2, true)?; }
if modifiers & 0x02 != 0 {
self.send_key(0xA0, true)?; }
if modifiers & 0x04 != 0 {
self.send_key(0xA4, true)?; }
if modifiers & 0x08 != 0 {
self.send_key(0x5B, true)?; }
for &key in keys {
if key == 0 { continue; }
if let Some(vk) = Self::hid_to_vk(key) {
self.send_key(vk, true)?;
std::thread::sleep(std::time::Duration::from_millis(5));
self.send_key(vk, false)?;
}
}
if modifiers & 0x01 != 0 {
self.send_key(0xA2, false)?;
}
if modifiers & 0x02 != 0 {
self.send_key(0xA0, false)?;
}
if modifiers & 0x04 != 0 {
self.send_key(0xA4, false)?;
}
if modifiers & 0x08 != 0 {
self.send_key(0x5B, false)?;
}
Ok(())
}
fn destroy(self: Box<Self>) -> Result<()> {
Ok(())
}
}
#[cfg(target_os = "windows")]
pub struct WindowsGamepadBackend;
#[cfg(target_os = "windows")]
impl HidBackend for WindowsGamepadBackend {
fn send_report(&self, _report: &[u8]) -> Result<()> {
Err(Error::PlatformNotSupported(
"Windows gamepad requires ViGEmBus driver. Install from https://github.com/ViGEm/ViGEmBus".into()
))
}
fn destroy(self: Box<Self>) -> Result<()> {
Ok(())
}
}
#[cfg(target_os = "windows")]
pub fn create_mouse_backend(_name: &str) -> Result<Box<dyn HidBackend>> {
Ok(Box::new(WindowsMouseBackend::new()?))
}
#[cfg(target_os = "windows")]
pub fn create_keyboard_backend(_name: &str) -> Result<Box<dyn HidBackend>> {
Ok(Box::new(WindowsKeyboardBackend::new()?))
}
#[cfg(target_os = "windows")]
pub fn create_gamepad_backend(_name: &str) -> Result<Box<dyn HidBackend>> {
Err(Error::PlatformNotSupported(
"Windows gamepad requires ViGEmBus driver".into()
))
}
#[cfg(not(target_os = "windows"))]
pub fn create_mouse_backend(_name: &str) -> Result<Box<dyn HidBackend>> {
Err(Error::PlatformNotSupported("Not on Windows".into()))
}
#[cfg(not(target_os = "windows"))]
pub fn create_keyboard_backend(_name: &str) -> Result<Box<dyn HidBackend>> {
Err(Error::PlatformNotSupported("Not on Windows".into()))
}
#[cfg(not(target_os = "windows"))]
pub fn create_gamepad_backend(_name: &str) -> Result<Box<dyn HidBackend>> {
Err(Error::PlatformNotSupported("Not on Windows".into()))
}