use super::HidBackend;
use crate::error::{Error, Result};
use std::cell::UnsafeCell;
#[link(name = "CoreGraphics", kind = "framework")]
extern "C" {
fn CGEventCreateMouseEvent(
source: *const std::ffi::c_void,
mouseType: u32,
mouseCursorPosition: CGPoint,
mouseButton: u32,
) -> *mut std::ffi::c_void;
fn CGEventCreateKeyboardEvent(
source: *const std::ffi::c_void,
virtualKey: u16,
keyDown: bool,
) -> *mut std::ffi::c_void;
fn CGEventCreateScrollWheelEvent(
source: *const std::ffi::c_void,
units: u32,
wheelCount: u32,
wheel1: i32,
) -> *mut std::ffi::c_void;
fn CGEventPost(tap: u32, event: *mut std::ffi::c_void);
fn CFRelease(cf: *const std::ffi::c_void);
fn CGEventSetFlags(event: *mut std::ffi::c_void, flags: u64);
fn CGEventGetLocation(event: *mut std::ffi::c_void) -> CGPoint;
fn CGEventCreate(source: *const std::ffi::c_void) -> *mut std::ffi::c_void;
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
struct CGPoint {
x: f64,
y: f64,
}
const K_CG_EVENT_LEFT_MOUSE_DOWN: u32 = 1;
const K_CG_EVENT_LEFT_MOUSE_UP: u32 = 2;
const K_CG_EVENT_RIGHT_MOUSE_DOWN: u32 = 3;
const K_CG_EVENT_RIGHT_MOUSE_UP: u32 = 4;
const K_CG_EVENT_MOUSE_MOVED: u32 = 5;
const K_CG_EVENT_OTHER_MOUSE_DOWN: u32 = 25;
const K_CG_EVENT_OTHER_MOUSE_UP: u32 = 26;
const K_CG_MOUSE_BUTTON_LEFT: u32 = 0;
const K_CG_MOUSE_BUTTON_RIGHT: u32 = 1;
const K_CG_MOUSE_BUTTON_CENTER: u32 = 2;
const K_CG_HID_EVENT_TAP: u32 = 0;
const K_CG_SCROLL_EVENT_UNIT_LINE: u32 = 1;
const K_CG_EVENT_FLAG_MASK_SHIFT: u64 = 0x00020000;
const K_CG_EVENT_FLAG_MASK_CONTROL: u64 = 0x00040000;
const K_CG_EVENT_FLAG_MASK_ALTERNATE: u64 = 0x00080000;
const K_CG_EVENT_FLAG_MASK_COMMAND: u64 = 0x00100000;
struct MouseState {
current_pos: CGPoint,
buttons: u8,
}
pub struct CGEventMouseBackend {
state: UnsafeCell<MouseState>,
}
unsafe impl Send for CGEventMouseBackend {}
unsafe impl Sync for CGEventMouseBackend {}
impl CGEventMouseBackend {
pub fn new() -> Result<Self> {
let pos = unsafe {
let event = CGEventCreate(std::ptr::null());
if event.is_null() {
return Err(Error::CreateFailed("Failed to create CGEvent".into()));
}
let pos = CGEventGetLocation(event);
CFRelease(event as *const std::ffi::c_void);
pos
};
Ok(Self {
state: UnsafeCell::new(MouseState {
current_pos: pos,
buttons: 0,
}),
})
}
fn post_mouse_event(&self, event_type: u32, button: u32, pos: CGPoint) -> Result<()> {
unsafe {
let event = CGEventCreateMouseEvent(
std::ptr::null(),
event_type,
pos,
button,
);
if event.is_null() {
return Err(Error::SendFailed("Failed to create mouse event".into()));
}
CGEventPost(K_CG_HID_EVENT_TAP, event);
CFRelease(event as *const std::ffi::c_void);
}
Ok(())
}
}
impl HidBackend for CGEventMouseBackend {
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;
let dy = report[2] as i8;
let wheel = report[3] as i8;
let state = unsafe { &mut *self.state.get() };
state.current_pos.x += dx as f64;
state.current_pos.y += dy as f64;
if state.current_pos.x < 0.0 { state.current_pos.x = 0.0; }
if state.current_pos.y < 0.0 { state.current_pos.y = 0.0; }
let pos = state.current_pos;
if dx != 0 || dy != 0 {
self.post_mouse_event(K_CG_EVENT_MOUSE_MOVED, K_CG_MOUSE_BUTTON_LEFT, pos)?;
}
let old_buttons = state.buttons;
state.buttons = buttons;
if (buttons & 0x01) != 0 && (old_buttons & 0x01) == 0 {
self.post_mouse_event(K_CG_EVENT_LEFT_MOUSE_DOWN, K_CG_MOUSE_BUTTON_LEFT, pos)?;
} else if (buttons & 0x01) == 0 && (old_buttons & 0x01) != 0 {
self.post_mouse_event(K_CG_EVENT_LEFT_MOUSE_UP, K_CG_MOUSE_BUTTON_LEFT, pos)?;
}
if (buttons & 0x02) != 0 && (old_buttons & 0x02) == 0 {
self.post_mouse_event(K_CG_EVENT_RIGHT_MOUSE_DOWN, K_CG_MOUSE_BUTTON_RIGHT, pos)?;
} else if (buttons & 0x02) == 0 && (old_buttons & 0x02) != 0 {
self.post_mouse_event(K_CG_EVENT_RIGHT_MOUSE_UP, K_CG_MOUSE_BUTTON_RIGHT, pos)?;
}
if (buttons & 0x04) != 0 && (old_buttons & 0x04) == 0 {
self.post_mouse_event(K_CG_EVENT_OTHER_MOUSE_DOWN, K_CG_MOUSE_BUTTON_CENTER, pos)?;
} else if (buttons & 0x04) == 0 && (old_buttons & 0x04) != 0 {
self.post_mouse_event(K_CG_EVENT_OTHER_MOUSE_UP, K_CG_MOUSE_BUTTON_CENTER, pos)?;
}
if wheel != 0 {
unsafe {
let event = CGEventCreateScrollWheelEvent(
std::ptr::null(),
K_CG_SCROLL_EVENT_UNIT_LINE,
1,
wheel as i32,
);
if !event.is_null() {
CGEventPost(K_CG_HID_EVENT_TAP, event);
CFRelease(event as *const std::ffi::c_void);
}
}
}
Ok(())
}
fn destroy(self: Box<Self>) -> Result<()> {
Ok(())
}
}
pub struct CGEventKeyboardBackend;
unsafe impl Send for CGEventKeyboardBackend {}
unsafe impl Sync for CGEventKeyboardBackend {}
impl CGEventKeyboardBackend {
pub fn new() -> Result<Self> {
Ok(Self)
}
fn get_flags(modifiers: u8) -> u64 {
let mut flags = 0u64;
if modifiers & 0x01 != 0 || modifiers & 0x10 != 0 { flags |= K_CG_EVENT_FLAG_MASK_CONTROL; }
if modifiers & 0x02 != 0 || modifiers & 0x20 != 0 { flags |= K_CG_EVENT_FLAG_MASK_SHIFT; }
if modifiers & 0x04 != 0 || modifiers & 0x40 != 0 { flags |= K_CG_EVENT_FLAG_MASK_ALTERNATE; }
if modifiers & 0x08 != 0 || modifiers & 0x80 != 0 { flags |= K_CG_EVENT_FLAG_MASK_COMMAND; }
flags
}
fn hid_to_virtual_key(hid: u8) -> Option<u16> {
Some(match hid {
0x04 => 0x00, 0x05 => 0x0B, 0x06 => 0x08, 0x07 => 0x02, 0x08 => 0x0E, 0x09 => 0x03, 0x0A => 0x05, 0x0B => 0x04, 0x0C => 0x22, 0x0D => 0x26, 0x0E => 0x28, 0x0F => 0x25, 0x10 => 0x2E, 0x11 => 0x2D, 0x12 => 0x1F, 0x13 => 0x23, 0x14 => 0x0C, 0x15 => 0x0F, 0x16 => 0x01, 0x17 => 0x11, 0x18 => 0x20, 0x19 => 0x09, 0x1A => 0x0D, 0x1B => 0x07, 0x1C => 0x10, 0x1D => 0x06, 0x1E => 0x12, 0x1F => 0x13, 0x20 => 0x14, 0x21 => 0x15, 0x22 => 0x17, 0x23 => 0x16, 0x24 => 0x1A, 0x25 => 0x1C, 0x26 => 0x19, 0x27 => 0x1D, 0x28 => 0x24, 0x29 => 0x35, 0x2A => 0x33, 0x2B => 0x30, 0x2C => 0x31, 0x2D => 0x1B, 0x2E => 0x18, 0x2F => 0x21, 0x30 => 0x1E, 0x31 => 0x2A, 0x33 => 0x29, 0x34 => 0x27, 0x35 => 0x32, 0x36 => 0x2B, 0x37 => 0x2F, 0x38 => 0x2C, 0x4F => 0x7C, 0x50 => 0x7B, 0x51 => 0x7D, 0x52 => 0x7E, _ => return None,
})
}
}
impl HidBackend for CGEventKeyboardBackend {
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];
let flags = Self::get_flags(modifiers);
for &key in keys {
if key == 0 { continue; }
if let Some(vk) = Self::hid_to_virtual_key(key) {
unsafe {
let event = CGEventCreateKeyboardEvent(std::ptr::null(), vk, true);
if !event.is_null() {
CGEventSetFlags(event, flags);
CGEventPost(K_CG_HID_EVENT_TAP, event);
CFRelease(event as *const std::ffi::c_void);
}
std::thread::sleep(std::time::Duration::from_millis(5));
let event = CGEventCreateKeyboardEvent(std::ptr::null(), vk, false);
if !event.is_null() {
CGEventSetFlags(event, flags);
CGEventPost(K_CG_HID_EVENT_TAP, event);
CFRelease(event as *const std::ffi::c_void);
}
}
}
}
Ok(())
}
fn destroy(self: Box<Self>) -> Result<()> {
Ok(())
}
}
pub fn create_cgevent_mouse_backend() -> Result<Box<dyn HidBackend>> {
Ok(Box::new(CGEventMouseBackend::new()?))
}
pub fn create_cgevent_keyboard_backend() -> Result<Box<dyn HidBackend>> {
Ok(Box::new(CGEventKeyboardBackend::new()?))
}