mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-04-01 21:21:27 +03:00
macos: enable running lan-mouse on macos (#42)
* macos: initial support - adapted conditional compilation - moved lan-mouse socket to ~/Library/Caches/lan-mouse-socket.sock instead of XDG_RUNTIME_DIR - support for mouse input emulation TODO: Keycode translation, input capture
This commit is contained in:
committed by
GitHub
parent
5a7e0cf89c
commit
e3f9947284
@@ -1,14 +1,17 @@
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(all(unix, feature = "x11"))]
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
pub mod x11;
|
||||
|
||||
#[cfg(all(unix, feature = "wayland"))]
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
pub mod wlroots;
|
||||
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal"))]
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
pub mod xdg_desktop_portal;
|
||||
|
||||
#[cfg(all(unix, feature = "libei"))]
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
pub mod libei;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
221
src/backend/consumer/macos.rs
Normal file
221
src/backend/consumer/macos.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
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::CGPoint;
|
||||
use core_graphics::event::{
|
||||
CGEvent, CGEventTapLocation, CGEventType, CGMouseButton, 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<CGMouseButton> 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<CGMouseButton> 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<Self> {
|
||||
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<CGPoint> {
|
||||
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,
|
||||
} => {
|
||||
let mut mouse_location = match self.get_mouse_location() {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
log::warn!("could not get mouse location!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
mouse_location.x += relative_x;
|
||||
mouse_location.y += relative_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
|
||||
};
|
||||
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.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] = if state == 1 { true } else { false };
|
||||
|
||||
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 { .. } => {}
|
||||
},
|
||||
Event::Release() => {}
|
||||
Event::Ping() => {}
|
||||
Event::Pong() => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn notify(&mut self, _client_event: ClientEvent) {}
|
||||
|
||||
async fn destroy(&mut self) {}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
#[cfg(all(unix, feature = "libei"))]
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
pub mod libei;
|
||||
#[cfg(all(unix, feature = "wayland"))]
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
pub mod wayland;
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
#[cfg(all(unix, feature = "x11"))]
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
pub mod x11;
|
||||
|
||||
28
src/backend/producer/macos.rs
Normal file
28
src/backend/producer/macos.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use crate::client::{ClientEvent, ClientHandle};
|
||||
use crate::event::Event;
|
||||
use crate::producer::EventProducer;
|
||||
use futures_core::Stream;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{io, pin::Pin};
|
||||
|
||||
pub struct MacOSProducer;
|
||||
|
||||
impl MacOSProducer {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for MacOSProducer {
|
||||
type Item = io::Result<(ClientHandle, Event)>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl EventProducer for MacOSProducer {
|
||||
fn notify(&mut self, _event: ClientEvent) {}
|
||||
|
||||
fn release(&mut self) {}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use std::future;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
use std::env;
|
||||
|
||||
use crate::{
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[derive(Debug)]
|
||||
enum Backend {
|
||||
Wlroots,
|
||||
@@ -37,7 +37,10 @@ pub async fn create() -> Result<Box<dyn EventConsumer>> {
|
||||
#[cfg(windows)]
|
||||
return Ok(Box::new(consumer::windows::WindowsConsumer::new()));
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(target_os = "macos")]
|
||||
return Ok(Box::new(consumer::macos::MacOSConsumer::new()?));
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
let backend = match env::var("XDG_SESSION_TYPE") {
|
||||
Ok(session_type) => match session_type.as_str() {
|
||||
"x11" => {
|
||||
@@ -87,7 +90,7 @@ pub async fn create() -> Result<Box<dyn EventConsumer>> {
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
match backend {
|
||||
Backend::Libei => {
|
||||
#[cfg(not(feature = "libei"))]
|
||||
|
||||
@@ -3,6 +3,11 @@ use std::{
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
// FIXME
|
||||
pub(crate) const BTN_LEFT: u32 = 0x110;
|
||||
pub(crate) const BTN_RIGHT: u32 = 0x111;
|
||||
pub(crate) const BTN_MIDDLE: u32 = 0x112;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PointerEvent {
|
||||
Motion {
|
||||
|
||||
@@ -126,7 +126,7 @@ pub struct FrontendListener {
|
||||
}
|
||||
|
||||
impl FrontendListener {
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn socket_path() -> Result<PathBuf> {
|
||||
let xdg_runtime_dir = match env::var("XDG_RUNTIME_DIR") {
|
||||
Ok(d) => d,
|
||||
@@ -136,6 +136,20 @@ impl FrontendListener {
|
||||
Ok(xdg_runtime_dir.join("lan-mouse-socket.sock"))
|
||||
}
|
||||
|
||||
#[cfg(all(unix, target_os = "macos"))]
|
||||
pub fn socket_path() -> Result<PathBuf> {
|
||||
let home = match env::var("HOME") {
|
||||
Ok(d) => d,
|
||||
Err(e) => return Err(anyhow!("could not find HOME: {e}")),
|
||||
};
|
||||
let home = Path::new(home.as_str());
|
||||
let path = home
|
||||
.join("Library")
|
||||
.join("Caches")
|
||||
.join("lan-mouse-socket.sock");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub async fn new() -> Option<Result<Self>> {
|
||||
#[cfg(unix)]
|
||||
let (socket_path, listener) = {
|
||||
|
||||
@@ -9,10 +9,10 @@ use crate::{
|
||||
event::Event,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
use std::env;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
enum Backend {
|
||||
LayerShell,
|
||||
Libei,
|
||||
@@ -20,10 +20,13 @@ enum Backend {
|
||||
}
|
||||
|
||||
pub async fn create() -> Result<Box<dyn EventProducer>> {
|
||||
#[cfg(target_os = "macos")]
|
||||
return Ok(Box::new(producer::macos::MacOSProducer::new()));
|
||||
|
||||
#[cfg(windows)]
|
||||
return Ok(Box::new(producer::windows::WindowsProducer::new()));
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
let backend = match env::var("XDG_SESSION_TYPE") {
|
||||
Ok(session_type) => match session_type.as_str() {
|
||||
"x11" => {
|
||||
@@ -56,7 +59,7 @@ pub async fn create() -> Result<Box<dyn EventProducer>> {
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
match backend {
|
||||
Backend::X11 => {
|
||||
#[cfg(not(feature = "x11"))]
|
||||
|
||||
Reference in New Issue
Block a user