mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-07 11:59:59 +03:00
497 lines
18 KiB
Rust
497 lines
18 KiB
Rust
use super::{error::EmulationError, Emulation, EmulationHandle};
|
|
use async_trait::async_trait;
|
|
use bitflags::bitflags;
|
|
use core_graphics::base::CGFloat;
|
|
use core_graphics::display::{
|
|
CGDirectDisplayID, CGDisplayBounds, CGGetDisplaysWithRect, CGPoint, CGRect, CGSize,
|
|
};
|
|
use core_graphics::event::{
|
|
CGEvent, CGEventFlags, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField,
|
|
ScrollEventUnit,
|
|
};
|
|
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
|
|
use input_event::{scancode, Event, KeyboardEvent, PointerEvent};
|
|
use keycode::{KeyMap, KeyMapping};
|
|
use std::cell::Cell;
|
|
use std::ops::{Index, IndexMut};
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use tokio::{sync::Notify, task::JoinHandle};
|
|
|
|
use super::error::MacOSEmulationCreationError;
|
|
|
|
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
|
|
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
|
|
|
|
pub(crate) struct MacOSEmulation {
|
|
event_source: CGEventSource,
|
|
repeat_task: Option<JoinHandle<()>>,
|
|
button_state: ButtonState,
|
|
modifier_state: Rc<Cell<XMods>>,
|
|
notify_repeat_task: Arc<Notify>,
|
|
}
|
|
|
|
struct ButtonState {
|
|
left: bool,
|
|
right: bool,
|
|
center: bool,
|
|
}
|
|
|
|
impl Index<CGMouseButton> for ButtonState {
|
|
type Output = bool;
|
|
|
|
fn index(&self, index: CGMouseButton) -> &Self::Output {
|
|
match index {
|
|
CGMouseButton::Left => &self.left,
|
|
CGMouseButton::Right => &self.right,
|
|
CGMouseButton::Center => &self.center,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IndexMut<CGMouseButton> for ButtonState {
|
|
fn index_mut(&mut self, index: CGMouseButton) -> &mut Self::Output {
|
|
match index {
|
|
CGMouseButton::Left => &mut self.left,
|
|
CGMouseButton::Right => &mut self.right,
|
|
CGMouseButton::Center => &mut self.center,
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe impl Send for MacOSEmulation {}
|
|
|
|
impl MacOSEmulation {
|
|
pub(crate) fn new() -> Result<Self, MacOSEmulationCreationError> {
|
|
let event_source = CGEventSource::new(CGEventSourceStateID::CombinedSessionState)
|
|
.map_err(|_| MacOSEmulationCreationError::EventSourceCreation)?;
|
|
let button_state = ButtonState {
|
|
left: false,
|
|
right: false,
|
|
center: false,
|
|
};
|
|
Ok(Self {
|
|
event_source,
|
|
button_state,
|
|
repeat_task: None,
|
|
notify_repeat_task: Arc::new(Notify::new()),
|
|
modifier_state: Rc::new(Cell::new(XMods::empty())),
|
|
})
|
|
}
|
|
|
|
fn get_mouse_location(&self) -> Option<CGPoint> {
|
|
let event: CGEvent = CGEvent::new(self.event_source.clone()).ok()?;
|
|
Some(event.location())
|
|
}
|
|
|
|
async fn spawn_repeat_task(&mut self, key: u16) {
|
|
// there can only be one repeating key and it's
|
|
// always the last to be pressed
|
|
self.cancel_repeat_task().await;
|
|
let event_source = self.event_source.clone();
|
|
let notify = self.notify_repeat_task.clone();
|
|
let modifiers = self.modifier_state.clone();
|
|
let repeat_task = tokio::task::spawn_local(async move {
|
|
let stop = tokio::select! {
|
|
_ = tokio::time::sleep(DEFAULT_REPEAT_DELAY) => false,
|
|
_ = notify.notified() => true,
|
|
};
|
|
if !stop {
|
|
loop {
|
|
key_event(event_source.clone(), key, 1, modifiers.get());
|
|
tokio::select! {
|
|
_ = tokio::time::sleep(DEFAULT_REPEAT_INTERVAL) => {},
|
|
_ = notify.notified() => break,
|
|
}
|
|
}
|
|
}
|
|
// release key when cancelled
|
|
update_modifiers(&modifiers, key as u32, 0);
|
|
key_event(event_source.clone(), key, 0, modifiers.get());
|
|
});
|
|
self.repeat_task = Some(repeat_task);
|
|
}
|
|
|
|
async fn cancel_repeat_task(&mut self) {
|
|
if let Some(task) = self.repeat_task.take() {
|
|
self.notify_repeat_task.notify_waiters();
|
|
let _ = task.await;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn key_event(event_source: CGEventSource, key: u16, state: u8, modifiers: XMods) {
|
|
let event = match CGEvent::new_keyboard_event(event_source, key, state != 0) {
|
|
Ok(e) => e,
|
|
Err(_) => {
|
|
log::warn!("unable to create key event");
|
|
return;
|
|
}
|
|
};
|
|
event.set_flags(to_cgevent_flags(modifiers));
|
|
event.post(CGEventTapLocation::HID);
|
|
log::trace!("key event: {key} {state}");
|
|
}
|
|
|
|
fn modifier_event(event_source: CGEventSource, depressed: XMods) {
|
|
let Ok(event) = CGEvent::new(event_source) else {
|
|
log::warn!("could not create CGEvent");
|
|
return;
|
|
};
|
|
let flags = to_cgevent_flags(depressed);
|
|
event.set_type(CGEventType::FlagsChanged);
|
|
event.set_flags(flags);
|
|
event.post(CGEventTapLocation::HID);
|
|
log::trace!("modifiers updated: {depressed:?}");
|
|
}
|
|
|
|
fn get_display_at_point(x: CGFloat, y: CGFloat) -> Option<CGDirectDisplayID> {
|
|
let mut displays: [CGDirectDisplayID; 16] = [0; 16];
|
|
let mut display_count: u32 = 0;
|
|
let rect = CGRect::new(&CGPoint::new(x, y), &CGSize::new(0.0, 0.0));
|
|
|
|
let error = unsafe {
|
|
CGGetDisplaysWithRect(
|
|
rect,
|
|
1,
|
|
displays.as_mut_ptr(),
|
|
&mut display_count as *mut u32,
|
|
)
|
|
};
|
|
|
|
if error != 0 {
|
|
log::warn!("error getting displays at point ({x}, {y}): {error}");
|
|
return Option::None;
|
|
}
|
|
|
|
if display_count == 0 {
|
|
log::debug!("no displays found at point ({x}, {y})");
|
|
return Option::None;
|
|
}
|
|
|
|
displays.first().copied()
|
|
}
|
|
|
|
fn get_display_bounds(display: CGDirectDisplayID) -> (CGFloat, CGFloat, CGFloat, CGFloat) {
|
|
unsafe {
|
|
let bounds = CGDisplayBounds(display);
|
|
let min_x = bounds.origin.x;
|
|
let max_x = bounds.origin.x + bounds.size.width;
|
|
let min_y = bounds.origin.y;
|
|
let max_y = bounds.origin.y + bounds.size.height;
|
|
(min_x as f64, min_y as f64, max_x as f64, max_y as f64)
|
|
}
|
|
}
|
|
|
|
fn clamp_to_screen_space(
|
|
current_x: CGFloat,
|
|
current_y: CGFloat,
|
|
dx: CGFloat,
|
|
dy: CGFloat,
|
|
) -> (CGFloat, CGFloat) {
|
|
// Check which display the mouse is currently on
|
|
// Determine what the location of the mouse would be after applying the move
|
|
// Get the display at the new location
|
|
// If the point is not on a display
|
|
// Clamp the mouse to the current display
|
|
// Else If the point is on a display
|
|
// Clamp the mouse to the new display
|
|
let current_display = match get_display_at_point(current_x, current_y) {
|
|
Some(display) => display,
|
|
None => {
|
|
log::warn!("could not get current display!");
|
|
return (current_x, current_y);
|
|
}
|
|
};
|
|
|
|
let new_x = current_x + dx;
|
|
let new_y = current_y + dy;
|
|
|
|
let final_display = get_display_at_point(new_x, new_y).unwrap_or(current_display);
|
|
let (min_x, min_y, max_x, max_y) = get_display_bounds(final_display);
|
|
|
|
(
|
|
new_x.clamp(min_x, max_x - 1.),
|
|
new_y.clamp(min_y, max_y - 1.),
|
|
)
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Emulation for MacOSEmulation {
|
|
async fn consume(
|
|
&mut self,
|
|
event: Event,
|
|
_handle: EmulationHandle,
|
|
) -> Result<(), EmulationError> {
|
|
match event {
|
|
Event::Pointer(pointer_event) => match pointer_event {
|
|
PointerEvent::Motion { time: _, dx, dy } => {
|
|
let mut mouse_location = match self.get_mouse_location() {
|
|
Some(l) => l,
|
|
None => {
|
|
log::warn!("could not get mouse location!");
|
|
return Ok(());
|
|
}
|
|
};
|
|
|
|
let (new_mouse_x, new_mouse_y) =
|
|
clamp_to_screen_space(mouse_location.x, mouse_location.y, dx, dy);
|
|
|
|
mouse_location.x = new_mouse_x;
|
|
mouse_location.y = new_mouse_y;
|
|
|
|
let mut event_type = CGEventType::MouseMoved;
|
|
if self.button_state.left {
|
|
event_type = CGEventType::LeftMouseDragged
|
|
} else if self.button_state.right {
|
|
event_type = CGEventType::RightMouseDragged
|
|
} else if self.button_state.center {
|
|
event_type = CGEventType::OtherMouseDragged
|
|
};
|
|
let event = match CGEvent::new_mouse_event(
|
|
self.event_source.clone(),
|
|
event_type,
|
|
mouse_location,
|
|
CGMouseButton::Left,
|
|
) {
|
|
Ok(e) => e,
|
|
Err(_) => {
|
|
log::warn!("mouse event creation failed!");
|
|
return Ok(());
|
|
}
|
|
};
|
|
event.set_integer_value_field(EventField::MOUSE_EVENT_DELTA_X, dx as i64);
|
|
event.set_integer_value_field(EventField::MOUSE_EVENT_DELTA_Y, dy as i64);
|
|
event.post(CGEventTapLocation::HID);
|
|
}
|
|
PointerEvent::Button {
|
|
time: _,
|
|
button,
|
|
state,
|
|
} => {
|
|
let (event_type, mouse_button) = match (button, state) {
|
|
(b, 1) if b == input_event::BTN_LEFT => {
|
|
(CGEventType::LeftMouseDown, CGMouseButton::Left)
|
|
}
|
|
(b, 0) if b == input_event::BTN_LEFT => {
|
|
(CGEventType::LeftMouseUp, CGMouseButton::Left)
|
|
}
|
|
(b, 1) if b == input_event::BTN_RIGHT => {
|
|
(CGEventType::RightMouseDown, CGMouseButton::Right)
|
|
}
|
|
(b, 0) if b == input_event::BTN_RIGHT => {
|
|
(CGEventType::RightMouseUp, CGMouseButton::Right)
|
|
}
|
|
(b, 1) if b == input_event::BTN_MIDDLE => {
|
|
(CGEventType::OtherMouseDown, CGMouseButton::Center)
|
|
}
|
|
(b, 0) if b == input_event::BTN_MIDDLE => {
|
|
(CGEventType::OtherMouseUp, CGMouseButton::Center)
|
|
}
|
|
_ => {
|
|
log::warn!("invalid button event: {button},{state}");
|
|
return Ok(());
|
|
}
|
|
};
|
|
// store button state
|
|
self.button_state[mouse_button] = state == 1;
|
|
|
|
let location = self.get_mouse_location().unwrap();
|
|
let event = match CGEvent::new_mouse_event(
|
|
self.event_source.clone(),
|
|
event_type,
|
|
location,
|
|
mouse_button,
|
|
) {
|
|
Ok(e) => e,
|
|
Err(()) => {
|
|
log::warn!("mouse event creation failed!");
|
|
return Ok(());
|
|
}
|
|
};
|
|
event.post(CGEventTapLocation::HID);
|
|
}
|
|
PointerEvent::Axis {
|
|
time: _,
|
|
axis,
|
|
value,
|
|
} => {
|
|
let value = value as i32;
|
|
let (count, wheel1, wheel2, wheel3) = match axis {
|
|
0 => (1, value, 0, 0), // 0 = vertical => 1 scroll wheel device (y axis)
|
|
1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
|
|
_ => {
|
|
log::warn!("invalid scroll event: {axis}, {value}");
|
|
return Ok(());
|
|
}
|
|
};
|
|
let event = match CGEvent::new_scroll_event(
|
|
self.event_source.clone(),
|
|
ScrollEventUnit::PIXEL,
|
|
count,
|
|
wheel1,
|
|
wheel2,
|
|
wheel3,
|
|
) {
|
|
Ok(e) => e,
|
|
Err(()) => {
|
|
log::warn!("scroll event creation failed!");
|
|
return Ok(());
|
|
}
|
|
};
|
|
event.post(CGEventTapLocation::HID);
|
|
}
|
|
PointerEvent::AxisDiscrete120 { axis, value } => {
|
|
const LINES_PER_STEP: i32 = 3;
|
|
let (count, wheel1, wheel2, wheel3) = match axis {
|
|
0 => (1, value / (120 / LINES_PER_STEP), 0, 0), // 0 = vertical => 1 scroll wheel device (y axis)
|
|
1 => (2, 0, value / (120 / LINES_PER_STEP), 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
|
|
_ => {
|
|
log::warn!("invalid scroll event: {axis}, {value}");
|
|
return Ok(());
|
|
}
|
|
};
|
|
let event = match CGEvent::new_scroll_event(
|
|
self.event_source.clone(),
|
|
ScrollEventUnit::LINE,
|
|
count,
|
|
wheel1,
|
|
wheel2,
|
|
wheel3,
|
|
) {
|
|
Ok(e) => e,
|
|
Err(()) => {
|
|
log::warn!("scroll event creation failed!");
|
|
return Ok(());
|
|
}
|
|
};
|
|
event.post(CGEventTapLocation::HID);
|
|
}
|
|
},
|
|
Event::Keyboard(keyboard_event) => match keyboard_event {
|
|
KeyboardEvent::Key {
|
|
time: _,
|
|
key,
|
|
state,
|
|
} => {
|
|
let code = match KeyMap::from_key_mapping(KeyMapping::Evdev(key as u16)) {
|
|
Ok(k) => k.mac as CGKeyCode,
|
|
Err(_) => {
|
|
log::warn!("unable to map key event");
|
|
return Ok(());
|
|
}
|
|
};
|
|
match state {
|
|
// pressed
|
|
1 => self.spawn_repeat_task(code).await,
|
|
_ => self.cancel_repeat_task().await,
|
|
}
|
|
update_modifiers(&self.modifier_state, key, state);
|
|
key_event(
|
|
self.event_source.clone(),
|
|
code,
|
|
state,
|
|
self.modifier_state.get(),
|
|
);
|
|
}
|
|
KeyboardEvent::Modifiers {
|
|
depressed,
|
|
latched,
|
|
locked,
|
|
group,
|
|
} => {
|
|
set_modifiers(&self.modifier_state, depressed, latched, locked, group);
|
|
modifier_event(self.event_source.clone(), self.modifier_state.get());
|
|
}
|
|
},
|
|
}
|
|
// FIXME
|
|
Ok(())
|
|
}
|
|
|
|
async fn create(&mut self, _handle: EmulationHandle) {}
|
|
|
|
async fn destroy(&mut self, _handle: EmulationHandle) {}
|
|
|
|
async fn terminate(&mut self) {}
|
|
}
|
|
|
|
fn update_modifiers(modifiers: &Cell<XMods>, key: u32, state: u8) -> bool {
|
|
if let Ok(key) = scancode::Linux::try_from(key) {
|
|
let mask = match key {
|
|
scancode::Linux::KeyLeftShift | scancode::Linux::KeyRightShift => XMods::ShiftMask,
|
|
scancode::Linux::KeyCapsLock => XMods::LockMask,
|
|
scancode::Linux::KeyLeftCtrl | scancode::Linux::KeyRightCtrl => XMods::ControlMask,
|
|
scancode::Linux::KeyLeftAlt | scancode::Linux::KeyRightalt => XMods::Mod1Mask,
|
|
scancode::Linux::KeyLeftMeta | scancode::Linux::KeyRightmeta => XMods::Mod4Mask,
|
|
_ => XMods::empty(),
|
|
};
|
|
// unchanged
|
|
if mask.is_empty() {
|
|
return false;
|
|
}
|
|
let mut mods = modifiers.get();
|
|
match state {
|
|
1 => mods.insert(mask),
|
|
_ => mods.remove(mask),
|
|
}
|
|
modifiers.set(mods);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn set_modifiers(
|
|
active_modifiers: &Cell<XMods>,
|
|
depressed: u32,
|
|
latched: u32,
|
|
locked: u32,
|
|
group: u32,
|
|
) {
|
|
let depressed = XMods::from_bits(depressed).unwrap_or_default();
|
|
let _latched = XMods::from_bits(latched).unwrap_or_default();
|
|
let _locked = XMods::from_bits(locked).unwrap_or_default();
|
|
let _group = XMods::from_bits(group).unwrap_or_default();
|
|
|
|
// we only care about the depressed modifiers for now
|
|
active_modifiers.replace(depressed);
|
|
}
|
|
|
|
fn to_cgevent_flags(depressed: XMods) -> CGEventFlags {
|
|
let mut flags = CGEventFlags::empty();
|
|
if depressed.contains(XMods::ShiftMask) {
|
|
flags |= CGEventFlags::CGEventFlagShift;
|
|
}
|
|
if depressed.contains(XMods::LockMask) {
|
|
flags |= CGEventFlags::CGEventFlagAlphaShift;
|
|
}
|
|
if depressed.contains(XMods::ControlMask) {
|
|
flags |= CGEventFlags::CGEventFlagControl;
|
|
}
|
|
if depressed.contains(XMods::Mod1Mask) {
|
|
flags |= CGEventFlags::CGEventFlagAlternate;
|
|
}
|
|
if depressed.contains(XMods::Mod4Mask) {
|
|
flags |= CGEventFlags::CGEventFlagCommand;
|
|
}
|
|
flags
|
|
}
|
|
|
|
// From X11/X.h
|
|
bitflags! {
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
struct XMods: u32 {
|
|
const ShiftMask = (1<<0);
|
|
const LockMask = (1<<1);
|
|
const ControlMask = (1<<2);
|
|
const Mod1Mask = (1<<3);
|
|
const Mod2Mask = (1<<4);
|
|
const Mod3Mask = (1<<5);
|
|
const Mod4Mask = (1<<6);
|
|
const Mod5Mask = (1<<7);
|
|
}
|
|
}
|