use crate::client::{ClientEvent, ClientHandle}; use crate::consumer::EventConsumer; use crate::event::{Event, KeyboardEvent, PointerEvent}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint}; use core_graphics::event::{ CGEvent, CGEventTapLocation, CGEventType, CGMouseButton, EventField, ScrollEventUnit, }; use core_graphics::event_source::{CGEventSource, CGEventSourceStateID}; use std::ops::{Index, IndexMut}; pub struct MacOSConsumer { pub event_source: CGEventSource, button_state: ButtonState, } 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, } } } unsafe impl Send for MacOSConsumer {} impl MacOSConsumer { pub fn new() -> Result { let event_source = match CGEventSource::new(CGEventSourceStateID::CombinedSessionState) { Ok(e) => e, Err(_) => return Err(anyhow!("event source creation failed!")), }; let button_state = ButtonState { left: false, right: false, center: false, }; Ok(Self { event_source, button_state, }) } fn get_mouse_location(&self) -> Option { let event: CGEvent = CGEvent::new(self.event_source.clone()).ok()?; Some(event.location()) } } #[async_trait] impl EventConsumer for MacOSConsumer { async fn consume(&mut self, event: Event, _client_handle: ClientHandle) { match event { Event::Pointer(pointer_event) => match pointer_event { PointerEvent::Motion { time: _, relative_x, relative_y, } => { // 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() { Some(l) => l, None => { log::warn!("could not get mouse location!"); return; } }; mouse_location.x = (mouse_location.x + relative_x).clamp(min_x, max_x - 1.); mouse_location.y = (mouse_location.y + relative_y).clamp(min_y, max_y - 1.); 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; } }; event.set_integer_value_field( EventField::MOUSE_EVENT_DELTA_X, relative_x as i64, ); event.set_integer_value_field( EventField::MOUSE_EVENT_DELTA_Y, relative_y as i64, ); event.post(CGEventTapLocation::HID); } PointerEvent::Button { time: _, button, state, } => { let (event_type, mouse_button) = match (button, state) { (b, 1) if b == crate::event::BTN_LEFT => { (CGEventType::LeftMouseDown, CGMouseButton::Left) } (b, 0) if b == crate::event::BTN_LEFT => { (CGEventType::LeftMouseUp, CGMouseButton::Left) } (b, 1) if b == crate::event::BTN_RIGHT => { (CGEventType::RightMouseDown, CGMouseButton::Right) } (b, 0) if b == crate::event::BTN_RIGHT => { (CGEventType::RightMouseUp, CGMouseButton::Right) } (b, 1) if b == crate::event::BTN_MIDDLE => { (CGEventType::OtherMouseDown, CGMouseButton::Center) } (b, 0) if b == crate::event::BTN_MIDDLE => { (CGEventType::OtherMouseUp, CGMouseButton::Center) } _ => { log::warn!("invalid button event: {button},{state}"); return; } }; // 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; } }; event.post(CGEventTapLocation::HID); } PointerEvent::Axis { time: _, axis, value, } => { let value = value as i32 / 10; // FIXME: high precision scroll events 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; } }; 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; } }; event.post(CGEventTapLocation::HID); } PointerEvent::Frame { .. } => {} }, Event::Keyboard(keyboard_event) => match keyboard_event { KeyboardEvent::Key { .. } => { /* let code = CGKeyCode::from_le(key as u16); let event = match CGEvent::new_keyboard_event( self.event_source.clone(), code, match state { 1 => true, _ => false } ) { Ok(e) => e, Err(_) => { log::warn!("unable to create key event"); return } }; event.post(CGEventTapLocation::HID); */ } KeyboardEvent::Modifiers { .. } => {} }, _ => (), } } async fn notify(&mut self, _client_event: ClientEvent) {} async fn destroy(&mut self) {} }