Skip to content

Commit 3aaefe9

Browse files
committed
macos: emulate double / triple click
1 parent 99c8bc5 commit 3aaefe9

File tree

2 files changed

+59
-20
lines changed

2 files changed

+59
-20
lines changed

input-emulation/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ tokio = { version = "1.32.0", features = [
2121
"rt",
2222
"sync",
2323
"signal",
24+
"time"
2425
] }
2526
once_cell = "1.19.0"
2627

input-emulation/src/macos.rs

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,37 @@ use core_graphics::event::{
1010
ScrollEventUnit,
1111
};
1212
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
13-
use input_event::{scancode, Event, KeyboardEvent, PointerEvent};
13+
use input_event::{scancode, Event, KeyboardEvent, PointerEvent, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT};
1414
use keycode::{KeyMap, KeyMapping};
1515
use std::cell::Cell;
1616
use std::ops::{Index, IndexMut};
1717
use std::rc::Rc;
1818
use std::sync::Arc;
19-
use std::time::Duration;
19+
use std::time::{Duration, Instant};
2020
use tokio::{sync::Notify, task::JoinHandle};
2121

2222
use super::error::MacOSEmulationCreationError;
2323

2424
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
2525
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
26+
const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
2627

2728
pub(crate) struct MacOSEmulation {
29+
/// global event source for all events
2830
event_source: CGEventSource,
31+
/// task handle for key repeats
2932
repeat_task: Option<JoinHandle<()>>,
33+
/// current state of the mouse buttons
3034
button_state: ButtonState,
35+
/// button previously pressed
36+
previous_button: Option<CGMouseButton>,
37+
/// timestamp of previous click (button down)
38+
previous_button_click: Option<Instant>,
39+
/// click state, i.e. number of clicks in quick succession
40+
button_click_state: i64,
41+
/// current modifier state
3142
modifier_state: Rc<Cell<XMods>>,
43+
/// notify to cancel key repeats
3244
notify_repeat_task: Arc<Notify>,
3345
}
3446

@@ -74,6 +86,9 @@ impl MacOSEmulation {
7486
Ok(Self {
7587
event_source,
7688
button_state,
89+
previous_button: None,
90+
previous_button_click: None,
91+
button_click_state: 1,
7792
repeat_task: None,
7893
notify_repeat_task: Arc::new(Notify::new()),
7994
modifier_state: Rc::new(Cell::new(XMods::empty())),
@@ -271,24 +286,12 @@ impl Emulation for MacOSEmulation {
271286
state,
272287
} => {
273288
let (event_type, mouse_button) = match (button, state) {
274-
(b, 1) if b == input_event::BTN_LEFT => {
275-
(CGEventType::LeftMouseDown, CGMouseButton::Left)
276-
}
277-
(b, 0) if b == input_event::BTN_LEFT => {
278-
(CGEventType::LeftMouseUp, CGMouseButton::Left)
279-
}
280-
(b, 1) if b == input_event::BTN_RIGHT => {
281-
(CGEventType::RightMouseDown, CGMouseButton::Right)
282-
}
283-
(b, 0) if b == input_event::BTN_RIGHT => {
284-
(CGEventType::RightMouseUp, CGMouseButton::Right)
285-
}
286-
(b, 1) if b == input_event::BTN_MIDDLE => {
287-
(CGEventType::OtherMouseDown, CGMouseButton::Center)
288-
}
289-
(b, 0) if b == input_event::BTN_MIDDLE => {
290-
(CGEventType::OtherMouseUp, CGMouseButton::Center)
291-
}
289+
(BTN_LEFT, 1) => (CGEventType::LeftMouseDown, CGMouseButton::Left),
290+
(BTN_LEFT, 0) => (CGEventType::LeftMouseUp, CGMouseButton::Left),
291+
(BTN_RIGHT, 1) => (CGEventType::RightMouseDown, CGMouseButton::Right),
292+
(BTN_RIGHT, 0) => (CGEventType::RightMouseUp, CGMouseButton::Right),
293+
(BTN_MIDDLE, 1) => (CGEventType::OtherMouseDown, CGMouseButton::Center),
294+
(BTN_MIDDLE, 0) => (CGEventType::OtherMouseUp, CGMouseButton::Center),
292295
_ => {
293296
log::warn!("invalid button event: {button},{state}");
294297
return Ok(());
@@ -297,6 +300,22 @@ impl Emulation for MacOSEmulation {
297300
// store button state
298301
self.button_state[mouse_button] = state == 1;
299302

303+
// update previous button state
304+
if state == 1 {
305+
if self.previous_button.is_some_and(|b| b.eq(&mouse_button))
306+
&& self
307+
.previous_button_click
308+
.is_some_and(|i| i.elapsed() < DOUBLE_CLICK_INTERVAL)
309+
{
310+
self.button_click_state += 1;
311+
} else {
312+
self.button_click_state = 1;
313+
}
314+
self.previous_button = Some(mouse_button);
315+
self.previous_button_click = Some(Instant::now());
316+
}
317+
318+
log::debug!("click_state: {}", self.button_click_state);
300319
let location = self.get_mouse_location().unwrap();
301320
let event = match CGEvent::new_mouse_event(
302321
self.event_source.clone(),
@@ -310,6 +329,10 @@ impl Emulation for MacOSEmulation {
310329
return Ok(());
311330
}
312331
};
332+
event.set_integer_value_field(
333+
EventField::MOUSE_EVENT_CLICK_STATE,
334+
self.button_click_state,
335+
);
313336
event.post(CGEventTapLocation::HID);
314337
}
315338
PointerEvent::Axis {
@@ -417,6 +440,21 @@ impl Emulation for MacOSEmulation {
417440
async fn terminate(&mut self) {}
418441
}
419442

443+
trait ButtonEq {
444+
fn eq(&self, other: &Self) -> bool;
445+
}
446+
447+
impl ButtonEq for CGMouseButton {
448+
fn eq(&self, other: &Self) -> bool {
449+
matches!(
450+
(self, other),
451+
(CGMouseButton::Left, CGMouseButton::Left)
452+
| (CGMouseButton::Right, CGMouseButton::Right)
453+
| (CGMouseButton::Center, CGMouseButton::Center)
454+
)
455+
}
456+
}
457+
420458
fn update_modifiers(modifiers: &Cell<XMods>, key: u32, state: u8) -> bool {
421459
if let Ok(key) = scancode::Linux::try_from(key) {
422460
let mask = match key {

0 commit comments

Comments
 (0)