Compare commits

...

1 Commits

Author SHA1 Message Date
Ferdinand Schober
df85f0baf8 macos: implement client side modifier events
closes #198
closes #199
2024-10-25 22:59:25 +02:00
3 changed files with 123 additions and 7 deletions

1
Cargo.lock generated
View File

@@ -1226,6 +1226,7 @@ version = "0.2.1"
dependencies = [
"ashpd",
"async-trait",
"bitflags 2.6.0",
"core-graphics",
"futures",
"input-event",

View File

@@ -44,6 +44,7 @@ ashpd = { version = "0.9", default-features = false, features = [
reis = { version = "0.2", features = ["tokio"], optional = true }
[target.'cfg(target_os="macos")'.dependencies]
bitflags = "2.5.0"
core-graphics = { version = "0.23", features = ["highsierra"] }
keycode = "0.4.0"

View File

@@ -1,16 +1,20 @@
use super::{error::EmulationError, Emulation, EmulationHandle};
use async_trait::async_trait;
use bitflags::bitflags;
use core_graphics::base::CGFloat;
use core_graphics::display::{
CGDirectDisplayID, CGDisplayBounds, CGGetDisplaysWithRect, CGPoint, CGRect, CGSize,
};
use core_graphics::event::{
CGEvent, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField, ScrollEventUnit,
CGEvent, CGEventFlags, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField,
ScrollEventUnit,
};
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
use input_event::{Event, KeyboardEvent, PointerEvent};
use input_event::{scancode, Event, KeyboardEvent, PointerEvent};
use keycode::{KeyMap, KeyMapping};
use std::cell::Cell;
use std::ops::{Index, IndexMut};
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
use tokio::{sync::Notify, task::JoinHandle};
@@ -24,6 +28,7 @@ pub(crate) struct MacOSEmulation {
event_source: CGEventSource,
repeat_task: Option<JoinHandle<()>>,
button_state: ButtonState,
modifier_state: Rc<Cell<XMods>>,
notify_repeat_task: Arc<Notify>,
}
@@ -71,6 +76,7 @@ impl MacOSEmulation {
button_state,
repeat_task: None,
notify_repeat_task: Arc::new(Notify::new()),
modifier_state: Rc::new(Cell::new(XMods::empty())),
})
}
@@ -85,6 +91,7 @@ impl MacOSEmulation {
self.cancel_repeat_task().await;
let event_source = self.event_source.clone();
let notify = self.notify_repeat_task.clone();
let modifiers = self.modifier_state.clone();
let repeat_task = tokio::task::spawn_local(async move {
let stop = tokio::select! {
_ = tokio::time::sleep(DEFAULT_REPEAT_DELAY) => false,
@@ -92,7 +99,7 @@ impl MacOSEmulation {
};
if !stop {
loop {
key_event(event_source.clone(), key, 1);
key_event(event_source.clone(), key, 1, modifiers.get());
tokio::select! {
_ = tokio::time::sleep(DEFAULT_REPEAT_INTERVAL) => {},
_ = notify.notified() => break,
@@ -100,7 +107,8 @@ impl MacOSEmulation {
}
}
// release key when cancelled
key_event(event_source.clone(), key, 0);
update_modifiers(&modifiers, key as u32, 0);
key_event(event_source.clone(), key, 0, modifiers.get());
});
self.repeat_task = Some(repeat_task);
}
@@ -113,7 +121,7 @@ impl MacOSEmulation {
}
}
fn key_event(event_source: CGEventSource, key: u16, state: u8) {
fn key_event(event_source: CGEventSource, key: u16, state: u8, modifiers: XMods) {
let event = match CGEvent::new_keyboard_event(event_source, key, state != 0) {
Ok(e) => e,
Err(_) => {
@@ -121,7 +129,21 @@ fn key_event(event_source: CGEventSource, key: u16, state: u8) {
return;
}
};
event.set_flags(to_cgevent_flags(modifiers));
event.post(CGEventTapLocation::HID);
log::trace!("key event: {key} {state}");
}
fn modifier_event(event_source: CGEventSource, depressed: XMods) {
let Ok(event) = CGEvent::new(event_source) else {
log::warn!("could not create CGEvent");
return;
};
let flags = to_cgevent_flags(depressed);
event.set_type(CGEventType::FlagsChanged);
event.set_flags(flags);
event.post(CGEventTapLocation::HID);
log::trace!("modifiers updated: {depressed:?}");
}
fn get_display_at_point(x: CGFloat, y: CGFloat) -> Option<CGDirectDisplayID> {
@@ -364,9 +386,23 @@ impl Emulation for MacOSEmulation {
1 => self.spawn_repeat_task(code).await,
_ => self.cancel_repeat_task().await,
}
key_event(self.event_source.clone(), code, state)
update_modifiers(&self.modifier_state, key, state);
key_event(
self.event_source.clone(),
code,
state,
self.modifier_state.get(),
);
}
KeyboardEvent::Modifiers {
depressed,
latched,
locked,
group,
} => {
set_modifiers(&self.modifier_state, depressed, latched, locked, group);
modifier_event(self.event_source.clone(), self.modifier_state.get());
}
KeyboardEvent::Modifiers { .. } => {}
},
}
// FIXME
@@ -379,3 +415,81 @@ impl Emulation for MacOSEmulation {
async fn terminate(&mut self) {}
}
fn update_modifiers(modifiers: &Cell<XMods>, key: u32, state: u8) -> bool {
if let Ok(key) = scancode::Linux::try_from(key) {
let mask = match key {
scancode::Linux::KeyLeftShift | scancode::Linux::KeyRightShift => XMods::ShiftMask,
scancode::Linux::KeyCapsLock => XMods::LockMask,
scancode::Linux::KeyLeftCtrl | scancode::Linux::KeyRightCtrl => XMods::ControlMask,
scancode::Linux::KeyLeftAlt | scancode::Linux::KeyRightalt => XMods::Mod1Mask,
scancode::Linux::KeyLeftMeta | scancode::Linux::KeyRightmeta => XMods::Mod4Mask,
_ => XMods::empty(),
};
// unchanged
if mask.is_empty() {
return false;
}
let mut mods = modifiers.get();
match state {
1 => mods.insert(mask),
_ => mods.remove(mask),
}
modifiers.set(mods);
true
} else {
false
}
}
fn set_modifiers(
active_modifiers: &Cell<XMods>,
depressed: u32,
latched: u32,
locked: u32,
group: u32,
) {
let depressed = XMods::from_bits(depressed).unwrap_or_default();
let _latched = XMods::from_bits(latched).unwrap_or_default();
let _locked = XMods::from_bits(locked).unwrap_or_default();
let _group = XMods::from_bits(group).unwrap_or_default();
// we only care about the depressed modifiers for now
active_modifiers.replace(depressed);
}
fn to_cgevent_flags(depressed: XMods) -> CGEventFlags {
let mut flags = CGEventFlags::empty();
if depressed.contains(XMods::ShiftMask) {
flags |= CGEventFlags::CGEventFlagShift;
}
if depressed.contains(XMods::LockMask) {
flags |= CGEventFlags::CGEventFlagAlphaShift;
}
if depressed.contains(XMods::ControlMask) {
flags |= CGEventFlags::CGEventFlagControl;
}
if depressed.contains(XMods::Mod1Mask) {
flags |= CGEventFlags::CGEventFlagAlternate;
}
if depressed.contains(XMods::Mod4Mask) {
flags |= CGEventFlags::CGEventFlagCommand;
}
flags
}
// From X11/X.h
bitflags! {
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct XMods: u32 {
const ShiftMask = (1<<0);
const LockMask = (1<<1);
const ControlMask = (1<<2);
const Mod1Mask = (1<<3);
const Mod2Mask = (1<<4);
const Mod3Mask = (1<<5);
const Mod4Mask = (1<<6);
const Mod5Mask = (1<<7);
}
}