Skip to content

Commit

Permalink
macos: add keyboard support (#81)
Browse files Browse the repository at this point in the history
* macos: add keyboard support

* macos: handle key repeat

* update README
  • Loading branch information
kiwiz committed Jan 26, 2024
1 parent 8084b52 commit 5cc8cda
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 21 deletions.
69 changes: 65 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ clap = { version="4.4.11", features = ["derive"] }
gtk = { package = "gtk4", version = "0.7.2", features = ["v4_2"], optional = true }
adw = { package = "libadwaita", version = "0.5.2", features = ["v1_1"], optional = true }
async-channel = { version = "2.1.1", optional = true }
keycode = "0.4.0"

[target.'cfg(unix)'.dependencies]
libc = "0.2.148"
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ input capture (to send events *to* other clients) on different operating systems
| Wayland (Gnome) | :heavy_check_mark: | WIP |
| X11 | :heavy_check_mark: | WIP |
| Windows | :heavy_check_mark: | WIP |
| MacOS | ( :heavy_check_mark: ) | WIP |

Keycode translation is not yet implemented so on MacOS only mouse emulation works as of right now.
| MacOS | :heavy_check_mark: | WIP |

> [!Important]
> If you are using [Wayfire](https://github.com/WayfireWM/wayfire), make sure to use a recent version (must be newer than October 23rd) and **add `shortcuts-inhibit` to the list of plugins in your wayfire config!**
Expand Down
69 changes: 55 additions & 14 deletions src/backend/consumer/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@ 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,
CGEvent, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField, ScrollEventUnit,
};
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
use keycode::{KeyMap, KeyMapping};
use std::ops::{Index, IndexMut};
use std::time::Duration;
use tokio::task::AbortHandle;

const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);

pub struct MacOSConsumer {
pub event_source: CGEventSource,
repeat_task: Option<AbortHandle>,
button_state: ButtonState,
}

Expand Down Expand Up @@ -59,13 +66,45 @@ impl MacOSConsumer {
Ok(Self {
event_source,
button_state,
repeat_task: None,
})
}

fn get_mouse_location(&self) -> Option<CGPoint> {
let event: CGEvent = CGEvent::new(self.event_source.clone()).ok()?;
Some(event.location())
}

async fn spawn_repeat_task(&mut self, key: u16) {
// there can only be one repeating key and it's
// always the last to be pressed
self.kill_repeat_task();
let event_source = self.event_source.clone();
let repeat_task = tokio::task::spawn_local(async move {
tokio::time::sleep(DEFAULT_REPEAT_DELAY).await;
loop {
key_event(event_source.clone(), key, 1);
tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await;
}
});
self.repeat_task = Some(repeat_task.abort_handle());
}
fn kill_repeat_task(&mut self) {
if let Some(task) = self.repeat_task.take() {
task.abort();
}
}
}

fn key_event(event_source: CGEventSource, key: u16, state: u8) {
let event = match CGEvent::new_keyboard_event(event_source, key, state != 0) {
Ok(e) => e,
Err(_) => {
log::warn!("unable to create key event");
return;
}
};
event.post(CGEventTapLocation::HID);
}

#[async_trait]
Expand Down Expand Up @@ -209,22 +248,24 @@ impl EventConsumer for MacOSConsumer {
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,
KeyboardEvent::Key {
time: _,
key,
state,
} => {
let code = match KeyMap::from_key_mapping(KeyMapping::Evdev(key as u16)) {
Ok(k) => k.mac as CGKeyCode,
Err(_) => {
log::warn!("unable to create key event");
return
log::warn!("unable to map key event");
return;
}
};
event.post(CGEventTapLocation::HID);
*/
match state {
// pressed
1 => self.spawn_repeat_task(code).await,
_ => self.kill_repeat_task(),
}
key_event(self.event_source.clone(), code, state)
}
KeyboardEvent::Modifiers { .. } => {}
},
Expand Down

0 comments on commit 5cc8cda

Please sign in to comment.