Compare commits

...

5 Commits

Author SHA1 Message Date
Ferdinand Schober
295be87a29 macos: fix duplicated key release event 2025-10-29 17:46:57 +01:00
Ferdinand Schober
057f6e2567 macos: emulate double / triple click (#338) 2025-10-29 17:46:15 +01:00
Ferdinand Schober
99c8bc5567 macsos: use ScrollEventUnit::LINE for mousewheel (#337) 2025-10-29 16:18:46 +01:00
Ferdinand Schober
0dd413e989 prevent authorization request spamming windows (#335) 2025-10-28 07:25:01 +01:00
Ferdinand Schober
4e5a66340a Partially Revert "slow scrolling chrome with emulation=wlroots capture=layer-shell (#318) (#325)" (#334)
The division by 120 was correct.
2025-10-27 15:56:30 +01:00
6 changed files with 81 additions and 32 deletions

View File

@@ -21,6 +21,7 @@ tokio = { version = "1.32.0", features = [
"rt", "rt",
"sync", "sync",
"signal", "signal",
"time"
] } ] }
once_cell = "1.19.0" once_cell = "1.19.0"

View File

@@ -10,25 +10,37 @@ use core_graphics::event::{
ScrollEventUnit, ScrollEventUnit,
}; };
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID}; use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
use input_event::{scancode, Event, KeyboardEvent, PointerEvent}; use input_event::{scancode, Event, KeyboardEvent, PointerEvent, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT};
use keycode::{KeyMap, KeyMapping}; use keycode::{KeyMap, KeyMapping};
use std::cell::Cell; use std::cell::Cell;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::{Duration, Instant};
use tokio::{sync::Notify, task::JoinHandle}; use tokio::{sync::Notify, task::JoinHandle};
use super::error::MacOSEmulationCreationError; use super::error::MacOSEmulationCreationError;
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500); const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32); const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
pub(crate) struct MacOSEmulation { pub(crate) struct MacOSEmulation {
/// global event source for all events
event_source: CGEventSource, event_source: CGEventSource,
/// task handle for key repeats
repeat_task: Option<JoinHandle<()>>, repeat_task: Option<JoinHandle<()>>,
/// current state of the mouse buttons
button_state: ButtonState, button_state: ButtonState,
/// button previously pressed
previous_button: Option<CGMouseButton>,
/// timestamp of previous click (button down)
previous_button_click: Option<Instant>,
/// click state, i.e. number of clicks in quick succession
button_click_state: i64,
/// current modifier state
modifier_state: Rc<Cell<XMods>>, modifier_state: Rc<Cell<XMods>>,
/// notify to cancel key repeats
notify_repeat_task: Arc<Notify>, notify_repeat_task: Arc<Notify>,
} }
@@ -74,6 +86,9 @@ impl MacOSEmulation {
Ok(Self { Ok(Self {
event_source, event_source,
button_state, button_state,
previous_button: None,
previous_button_click: None,
button_click_state: 1,
repeat_task: None, repeat_task: None,
notify_repeat_task: Arc::new(Notify::new()), notify_repeat_task: Arc::new(Notify::new()),
modifier_state: Rc::new(Cell::new(XMods::empty())), modifier_state: Rc::new(Cell::new(XMods::empty())),
@@ -89,6 +104,9 @@ impl MacOSEmulation {
// 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.cancel_repeat_task().await; self.cancel_repeat_task().await;
// initial key event
key_event(self.event_source.clone(), key, 1, self.modifier_state.get());
// repeat task
let event_source = self.event_source.clone(); let event_source = self.event_source.clone();
let notify = self.notify_repeat_task.clone(); let notify = self.notify_repeat_task.clone();
let modifiers = self.modifier_state.clone(); let modifiers = self.modifier_state.clone();
@@ -224,6 +242,7 @@ impl Emulation for MacOSEmulation {
event: Event, event: Event,
_handle: EmulationHandle, _handle: EmulationHandle,
) -> Result<(), EmulationError> { ) -> Result<(), EmulationError> {
log::trace!("{event:?}");
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 } => {
@@ -271,24 +290,12 @@ impl Emulation for MacOSEmulation {
state, state,
} => { } => {
let (event_type, mouse_button) = match (button, state) { let (event_type, mouse_button) = match (button, state) {
(b, 1) if b == input_event::BTN_LEFT => { (BTN_LEFT, 1) => (CGEventType::LeftMouseDown, CGMouseButton::Left),
(CGEventType::LeftMouseDown, CGMouseButton::Left) (BTN_LEFT, 0) => (CGEventType::LeftMouseUp, CGMouseButton::Left),
} (BTN_RIGHT, 1) => (CGEventType::RightMouseDown, CGMouseButton::Right),
(b, 0) if b == input_event::BTN_LEFT => { (BTN_RIGHT, 0) => (CGEventType::RightMouseUp, CGMouseButton::Right),
(CGEventType::LeftMouseUp, CGMouseButton::Left) (BTN_MIDDLE, 1) => (CGEventType::OtherMouseDown, CGMouseButton::Center),
} (BTN_MIDDLE, 0) => (CGEventType::OtherMouseUp, CGMouseButton::Center),
(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}"); log::warn!("invalid button event: {button},{state}");
return Ok(()); return Ok(());
@@ -297,6 +304,22 @@ impl Emulation for MacOSEmulation {
// store button state // store button state
self.button_state[mouse_button] = state == 1; self.button_state[mouse_button] = state == 1;
// update previous button state
if state == 1 {
if self.previous_button.is_some_and(|b| b.eq(&mouse_button))
&& self
.previous_button_click
.is_some_and(|i| i.elapsed() < DOUBLE_CLICK_INTERVAL)
{
self.button_click_state += 1;
} else {
self.button_click_state = 1;
}
self.previous_button = Some(mouse_button);
self.previous_button_click = Some(Instant::now());
}
log::debug!("click_state: {}", self.button_click_state);
let location = self.get_mouse_location().unwrap(); let location = self.get_mouse_location().unwrap();
let event = match CGEvent::new_mouse_event( let event = match CGEvent::new_mouse_event(
self.event_source.clone(), self.event_source.clone(),
@@ -310,6 +333,10 @@ impl Emulation for MacOSEmulation {
return Ok(()); return Ok(());
} }
}; };
event.set_integer_value_field(
EventField::MOUSE_EVENT_CLICK_STATE,
self.button_click_state,
);
event.post(CGEventTapLocation::HID); event.post(CGEventTapLocation::HID);
} }
PointerEvent::Axis { PointerEvent::Axis {
@@ -343,9 +370,10 @@ impl Emulation for MacOSEmulation {
event.post(CGEventTapLocation::HID); event.post(CGEventTapLocation::HID);
} }
PointerEvent::AxisDiscrete120 { axis, value } => { PointerEvent::AxisDiscrete120 { axis, value } => {
const LINES_PER_STEP: i32 = 3;
let (count, wheel1, wheel2, wheel3) = match axis { let (count, wheel1, wheel2, wheel3) = match axis {
0 => (1, value, 0, 0), // 0 = vertical => 1 scroll wheel device (y axis) 0 => (1, value / (120 / LINES_PER_STEP), 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) 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}"); log::warn!("invalid scroll event: {axis}, {value}");
return Ok(()); return Ok(());
@@ -353,7 +381,7 @@ impl Emulation for MacOSEmulation {
}; };
let event = match CGEvent::new_scroll_event( let event = match CGEvent::new_scroll_event(
self.event_source.clone(), self.event_source.clone(),
ScrollEventUnit::PIXEL, ScrollEventUnit::LINE,
count, count,
wheel1, wheel1,
wheel2, wheel2,
@@ -381,18 +409,12 @@ impl Emulation for MacOSEmulation {
return Ok(()); return Ok(());
} }
}; };
update_modifiers(&self.modifier_state, key, state);
match state { match state {
// pressed // pressed
1 => self.spawn_repeat_task(code).await, 1 => self.spawn_repeat_task(code).await,
_ => self.cancel_repeat_task().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 { KeyboardEvent::Modifiers {
depressed, depressed,
@@ -416,6 +438,21 @@ impl Emulation for MacOSEmulation {
async fn terminate(&mut self) {} async fn terminate(&mut self) {}
} }
trait ButtonEq {
fn eq(&self, other: &Self) -> bool;
}
impl ButtonEq for CGMouseButton {
fn eq(&self, other: &Self) -> bool {
matches!(
(self, other),
(CGMouseButton::Left, CGMouseButton::Left)
| (CGMouseButton::Right, CGMouseButton::Right)
| (CGMouseButton::Center, CGMouseButton::Center)
)
}
}
fn update_modifiers(modifiers: &Cell<XMods>, key: u32, state: u8) -> bool { fn update_modifiers(modifiers: &Cell<XMods>, key: u32, state: u8) -> bool {
if let Ok(key) = scancode::Linux::try_from(key) { if let Ok(key) = scancode::Linux::try_from(key) {
let mask = match key { let mask = match key {

View File

@@ -210,7 +210,7 @@ impl VirtualInput {
PointerEvent::AxisDiscrete120 { axis, value } => { PointerEvent::AxisDiscrete120 { axis, value } => {
let axis: Axis = (axis as u32).try_into()?; let axis: Axis = (axis as u32).try_into()?;
self.pointer self.pointer
.axis_discrete(now, axis, value as f64 / 8., value); .axis_discrete(now, axis, value as f64 / 8., value / 120);
self.pointer.axis_source(AxisSource::Wheel); self.pointer.axis_source(AxisSource::Wheel);
self.pointer.frame(); self.pointer.frame();
} }

View File

@@ -474,6 +474,9 @@ impl Window {
} }
pub(super) fn request_authorization(&self, fingerprint: &str) { pub(super) fn request_authorization(&self, fingerprint: &str) {
if let Some(w) = self.imp().authorization_window.borrow_mut().take() {
w.close();
}
let window = AuthorizationWindow::new(fingerprint); let window = AuthorizationWindow::new(fingerprint);
window.set_transient_for(Some(self)); window.set_transient_for(Some(self));
window.connect_closure( window.connect_closure(
@@ -496,5 +499,6 @@ impl Window {
}), }),
); );
window.present(); window.present();
self.imp().authorization_window.replace(Some(window));
} }
} }

View File

@@ -8,6 +8,8 @@ use gtk::{gdk, gio, glib, Button, CompositeTemplate, Entry, Image, Label, ListBo
use lan_mouse_ipc::{FrontendRequestWriter, DEFAULT_PORT}; use lan_mouse_ipc::{FrontendRequestWriter, DEFAULT_PORT};
use crate::authorization_window::AuthorizationWindow;
#[derive(CompositeTemplate, Default)] #[derive(CompositeTemplate, Default)]
#[template(resource = "/de/feschber/LanMouse/window.ui")] #[template(resource = "/de/feschber/LanMouse/window.ui")]
pub struct Window { pub struct Window {
@@ -49,6 +51,7 @@ pub struct Window {
pub port: Cell<u16>, pub port: Cell<u16>,
pub capture_active: Cell<bool>, pub capture_active: Cell<bool>,
pub emulation_active: Cell<bool>, pub emulation_active: Cell<bool>,
pub authorization_window: RefCell<Option<AuthorizationWindow>>,
} }
#[glib::object_subclass] #[glib::object_subclass]

View File

@@ -128,6 +128,7 @@ impl ListenTask {
async fn run(mut self) { async fn run(mut self) {
let mut interval = tokio::time::interval(Duration::from_secs(5)); let mut interval = tokio::time::interval(Duration::from_secs(5));
let mut last_response = HashMap::new(); let mut last_response = HashMap::new();
let mut rejected_connections = HashMap::new();
loop { loop {
select! { select! {
e = self.listener.next() => {match e { e = self.listener.next() => {match e {
@@ -156,7 +157,10 @@ impl ListenTask {
self.event_tx.send(EmulationEvent::Connected { addr, fingerprint }).expect("channel closed"); self.event_tx.send(EmulationEvent::Connected { addr, fingerprint }).expect("channel closed");
} }
Some(ListenEvent::Rejected { fingerprint }) => { Some(ListenEvent::Rejected { fingerprint }) => {
self.event_tx.send(EmulationEvent::ConnectionAttempt { fingerprint }).expect("channel closed"); if rejected_connections.insert(fingerprint.clone(), Instant::now())
.is_none_or(|i| i.elapsed() >= Duration::from_secs(2)) {
self.event_tx.send(EmulationEvent::ConnectionAttempt { fingerprint }).expect("channel closed");
}
} }
None => break None => break
}} }}