Compare commits

..

4 Commits

Author SHA1 Message Date
Ferdinand Schober
196d01fe9e macos: fix key-release with repeat logic 2024-09-23 19:20:39 +02:00
Jacob Barber
b071201dcb Fix multimonitors (#202)
Co-authored-by: Jacob Barber <jacob.barber@disney.com>

closes #83
2024-09-20 20:50:37 +02:00
Nick Bolton
f52f19d2e3 Add link to Synergy (open source) (#194) 2024-09-10 19:06:09 +02:00
Ferdinand Schober
39fed0344c cleanup server code + fix a lost update case (#191) 2024-09-05 02:31:10 +02:00
2 changed files with 108 additions and 25 deletions

View File

@@ -20,7 +20,7 @@ Focus lies on performance and a clean, manageable implementation that can easily
***blazingly fast™*** because it's written in rust. ***blazingly fast™*** because it's written in rust.
For an alternative (with slightly different goals) you may check out [Input Leap](https://github.com/input-leap). For an alternative (with slightly different goals) you may check out [Synergy 1 Community Edition](https://github.com/symless/synergy) or [Input Leap](https://github.com/input-leap) (Synergy fork).
> [!WARNING] > [!WARNING]

View File

@@ -1,6 +1,9 @@
use super::{error::EmulationError, Emulation, EmulationHandle}; use super::{error::EmulationError, Emulation, EmulationHandle};
use async_trait::async_trait; use async_trait::async_trait;
use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint}; use core_graphics::base::CGFloat;
use core_graphics::display::{
CGDirectDisplayID, CGDisplayBounds, CGGetDisplaysWithRect, CGPoint, CGRect, CGSize,
};
use core_graphics::event::{ use core_graphics::event::{
CGEvent, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField, ScrollEventUnit, CGEvent, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField, ScrollEventUnit,
}; };
@@ -8,8 +11,9 @@ use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
use input_event::{Event, KeyboardEvent, PointerEvent}; use input_event::{Event, KeyboardEvent, PointerEvent};
use keycode::{KeyMap, KeyMapping}; use keycode::{KeyMap, KeyMapping};
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::task::AbortHandle; use tokio::{sync::Notify, task::JoinHandle};
use super::error::MacOSEmulationCreationError; use super::error::MacOSEmulationCreationError;
@@ -18,8 +22,9 @@ const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
pub(crate) struct MacOSEmulation { pub(crate) struct MacOSEmulation {
event_source: CGEventSource, event_source: CGEventSource,
repeat_task: Option<AbortHandle>, repeat_task: Option<JoinHandle<()>>,
button_state: ButtonState, button_state: ButtonState,
notify_repeat_task: Arc<Notify>,
} }
struct ButtonState { struct ButtonState {
@@ -65,6 +70,7 @@ impl MacOSEmulation {
event_source, event_source,
button_state, button_state,
repeat_task: None, repeat_task: None,
notify_repeat_task: Arc::new(Notify::new()),
}) })
} }
@@ -76,20 +82,33 @@ impl MacOSEmulation {
async fn spawn_repeat_task(&mut self, key: u16) { async fn spawn_repeat_task(&mut self, key: u16) {
// there can only be one repeating key and it's // there can only be one repeating key and it's
// always the last to be pressed // always the last to be pressed
self.kill_repeat_task(); self.cancel_repeat_task().await;
let event_source = self.event_source.clone(); let event_source = self.event_source.clone();
let notify = self.notify_repeat_task.clone();
let repeat_task = tokio::task::spawn_local(async move { let repeat_task = tokio::task::spawn_local(async move {
tokio::time::sleep(DEFAULT_REPEAT_DELAY).await; let stop = tokio::select! {
_ = tokio::time::sleep(DEFAULT_REPEAT_DELAY) => false,
_ = notify.notified() => true,
};
if !stop {
loop { loop {
key_event(event_source.clone(), key, 1); key_event(event_source.clone(), key, 1);
tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await; tokio::select! {
_ = tokio::time::sleep(DEFAULT_REPEAT_INTERVAL) => {},
_ = notify.notified() => break,
} }
}
}
// release key when cancelled
key_event(event_source.clone(), key, 0);
}); });
self.repeat_task = Some(repeat_task.abort_handle()); self.repeat_task = Some(repeat_task);
} }
fn kill_repeat_task(&mut self) {
async fn cancel_repeat_task(&mut self) {
if let Some(task) = self.repeat_task.take() { if let Some(task) = self.repeat_task.take() {
task.abort(); self.notify_repeat_task.notify_waiters();
let _ = task.await;
} }
} }
} }
@@ -105,6 +124,77 @@ fn key_event(event_source: CGEventSource, key: u16, state: u8) {
event.post(CGEventTapLocation::HID); event.post(CGEventTapLocation::HID);
} }
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;
}
return 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] #[async_trait]
impl Emulation for MacOSEmulation { impl Emulation for MacOSEmulation {
async fn consume( async fn consume(
@@ -115,16 +205,6 @@ impl Emulation for MacOSEmulation {
match event { match event {
Event::Pointer(pointer_event) => match pointer_event { Event::Pointer(pointer_event) => match pointer_event {
PointerEvent::Motion { time: _, dx, dy } => { PointerEvent::Motion { time: _, dx, dy } => {
// FIXME secondary displays?
let (min_x, min_y, max_x, max_y) = unsafe {
let display = CGMainDisplayID();
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)
};
let mut mouse_location = match self.get_mouse_location() { let mut mouse_location = match self.get_mouse_location() {
Some(l) => l, Some(l) => l,
None => { None => {
@@ -133,8 +213,11 @@ impl Emulation for MacOSEmulation {
} }
}; };
mouse_location.x = (mouse_location.x + dx).clamp(min_x, max_x - 1.); let (new_mouse_x, new_mouse_y) =
mouse_location.y = (mouse_location.y + dy).clamp(min_y, max_y - 1.); 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; let mut event_type = CGEventType::MouseMoved;
if self.button_state.left { if self.button_state.left {
@@ -279,7 +362,7 @@ impl Emulation for MacOSEmulation {
match state { match state {
// pressed // pressed
1 => self.spawn_repeat_task(code).await, 1 => self.spawn_repeat_task(code).await,
_ => self.kill_repeat_task(), _ => self.cancel_repeat_task().await,
} }
key_event(self.event_source.clone(), code, state) key_event(self.event_source.clone(), code, state)
} }