diff --git a/input-capture/src/macos.rs b/input-capture/src/macos.rs index ff8317d..2abb15e 100644 --- a/input-capture/src/macos.rs +++ b/input-capture/src/macos.rs @@ -18,7 +18,9 @@ use core_graphics::{ event_source::{CGEventSource, CGEventSourceStateID}, }; use futures_core::Stream; -use input_event::{BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, Event, KeyboardEvent, PointerEvent}; +use input_event::{ + BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, Event, KeyboardEvent, PointerEvent, +}; use keycode::{KeyMap, KeyMapping}; use libc::c_void; use once_cell::unsync::Lazy; @@ -304,16 +306,28 @@ fn get_events( }))) } CGEventType::OtherMouseDown => { + let btn_num = ev.get_integer_value_field(EventField::MOUSE_EVENT_BUTTON_NUMBER); + let button = match btn_num { + 3 => BTN_BACK, + 4 => BTN_FORWARD, + _ => BTN_MIDDLE, + }; result.push(CaptureEvent::Input(Event::Pointer(PointerEvent::Button { time: 0, - button: BTN_MIDDLE, + button, state: 1, }))) } CGEventType::OtherMouseUp => { + let btn_num = ev.get_integer_value_field(EventField::MOUSE_EVENT_BUTTON_NUMBER); + let button = match btn_num { + 3 => BTN_BACK, + 4 => BTN_FORWARD, + _ => BTN_MIDDLE, + }; result.push(CaptureEvent::Input(Event::Pointer(PointerEvent::Button { time: 0, - button: BTN_MIDDLE, + button, state: 0, }))) } diff --git a/input-emulation/src/macos.rs b/input-emulation/src/macos.rs index a9178b5..ae5f307 100644 --- a/input-emulation/src/macos.rs +++ b/input-emulation/src/macos.rs @@ -10,10 +10,13 @@ use core_graphics::event::{ ScrollEventUnit, }; use core_graphics::event_source::{CGEventSource, CGEventSourceStateID}; -use input_event::{BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, Event, KeyboardEvent, PointerEvent, scancode}; +use input_event::{ + BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, Event, KeyboardEvent, PointerEvent, + scancode, +}; use keycode::{KeyMap, KeyMapping}; use std::cell::Cell; -use std::ops::{Index, IndexMut}; +use std::collections::HashSet; use std::rc::Rc; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -30,10 +33,10 @@ pub(crate) struct MacOSEmulation { event_source: CGEventSource, /// task handle for key repeats repeat_task: Option>, - /// current state of the mouse buttons - button_state: ButtonState, - /// button previously pressed - previous_button: Option, + /// current state of the mouse buttons (tracked by evdev button code) + pressed_buttons: HashSet, + /// button previously pressed (evdev button code) + previous_button: Option, /// timestamp of previous click (button down) previous_button_click: Option, /// click state, i.e. number of clicks in quick succession @@ -44,31 +47,13 @@ pub(crate) struct MacOSEmulation { notify_repeat_task: Arc, } -struct ButtonState { - left: bool, - right: bool, - center: bool, -} - -impl Index 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 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, - } +/// Maps an evdev button code to the CGEventType used for drag events. +fn drag_event_type(button: u32) -> CGEventType { + match button { + BTN_LEFT => CGEventType::LeftMouseDragged, + BTN_RIGHT => CGEventType::RightMouseDragged, + // middle, back, forward, and any other button all use OtherMouseDragged + _ => CGEventType::OtherMouseDragged, } } @@ -78,14 +63,9 @@ impl MacOSEmulation { pub(crate) fn new() -> Result { 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, + pressed_buttons: HashSet::new(), previous_button: None, previous_button_click: None, button_click_state: 0, @@ -261,14 +241,14 @@ impl Emulation for MacOSEmulation { 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 - }; + // If any button is held, emit a drag event for it; + // otherwise emit a normal mouse-moved event. + let event_type = self + .pressed_buttons + .iter() + .next() + .map(|&btn| drag_event_type(btn)) + .unwrap_or(CGEventType::MouseMoved); let event = match CGEvent::new_mouse_event( self.event_source.clone(), event_type, @@ -290,6 +270,12 @@ impl Emulation for MacOSEmulation { button, state, } => { + // button number for OtherMouse events (3 = back, 4 = forward, etc.) + let cg_button_number: Option = match button { + BTN_BACK => Some(3), + BTN_FORWARD => Some(4), + _ => None, + }; let (event_type, mouse_button) = match (button, state) { (BTN_LEFT, 1) => (CGEventType::LeftMouseDown, CGMouseButton::Left), (BTN_LEFT, 0) => (CGEventType::LeftMouseUp, CGMouseButton::Left), @@ -297,17 +283,29 @@ impl Emulation for MacOSEmulation { (BTN_RIGHT, 0) => (CGEventType::RightMouseUp, CGMouseButton::Right), (BTN_MIDDLE, 1) => (CGEventType::OtherMouseDown, CGMouseButton::Center), (BTN_MIDDLE, 0) => (CGEventType::OtherMouseUp, CGMouseButton::Center), + (BTN_BACK, 1) | (BTN_FORWARD, 1) => { + (CGEventType::OtherMouseDown, CGMouseButton::Center) + } + (BTN_BACK, 0) | (BTN_FORWARD, 0) => { + (CGEventType::OtherMouseUp, CGMouseButton::Center) + } _ => { log::warn!("invalid button event: {button},{state}"); return Ok(()); } }; - // store button state - self.button_state[mouse_button] = state == 1; - - // update previous button state + // store button state using the evdev button code so + // back, forward, and middle are tracked independently if state == 1 { - if self.previous_button.is_some_and(|b| b.eq(&mouse_button)) + self.pressed_buttons.insert(button); + } else { + self.pressed_buttons.remove(&button); + } + + // update double-click tracking using the evdev button + // code so that back/forward don't alias with middle + if state == 1 { + if self.previous_button == Some(button) && self .previous_button_click .is_some_and(|i| i.elapsed() < DOUBLE_CLICK_INTERVAL) @@ -316,7 +314,7 @@ impl Emulation for MacOSEmulation { } else { self.button_click_state = 1; } - self.previous_button = Some(mouse_button); + self.previous_button = Some(button); self.previous_button_click = Some(Instant::now()); } @@ -338,6 +336,13 @@ impl Emulation for MacOSEmulation { EventField::MOUSE_EVENT_CLICK_STATE, self.button_click_state, ); + // Set the button number for extra buttons (back=3, forward=4) + if let Some(btn_num) = cg_button_number { + event.set_integer_value_field( + EventField::MOUSE_EVENT_BUTTON_NUMBER, + btn_num, + ); + } event.post(CGEventTapLocation::HID); } PointerEvent::Axis { @@ -448,21 +453,6 @@ impl Emulation for MacOSEmulation { 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, key: u32, state: u8) -> bool { if let Ok(key) = scancode::Linux::try_from(key) { let mask = match key {