mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
feat(desktop): portal-based Linux global shortcuts and PTT (#3185)
This commit is contained in:
1 parent
5799ef705d
commit
8f58fcc4c4
149 files changed
+26849
-8769
No files matched your search
@@ -19,26 +19,6 @@ export type InputEvent =
|
||||
metaKey: boolean;
|
||||
x?: number;
|
||||
y?: number;
|
||||
}
|
||||
| {
|
||||
type: 'mousemove';
|
||||
x: number;
|
||||
y: number;
|
||||
ctrlKey: boolean;
|
||||
altKey: boolean;
|
||||
shiftKey: boolean;
|
||||
metaKey: boolean;
|
||||
}
|
||||
| {
|
||||
type: 'wheel';
|
||||
x?: number;
|
||||
y?: number;
|
||||
deltaX: number;
|
||||
deltaY: number;
|
||||
ctrlKey: boolean;
|
||||
altKey: boolean;
|
||||
shiftKey: boolean;
|
||||
metaKey: boolean;
|
||||
};
|
||||
|
||||
export declare class InputHook {
|
||||
|
||||
@@ -5,6 +5,15 @@ pub const CONTROL_MASK: u64 = 1 << 18;
|
||||
pub const ALTERNATE_MASK: u64 = 1 << 19;
|
||||
pub const COMMAND_MASK: u64 = 1 << 20;
|
||||
|
||||
const DEVICE_LEFT_CONTROL_MASK: u64 = 0x0000_0001;
|
||||
const DEVICE_LEFT_SHIFT_MASK: u64 = 0x0000_0002;
|
||||
const DEVICE_RIGHT_SHIFT_MASK: u64 = 0x0000_0004;
|
||||
const DEVICE_LEFT_COMMAND_MASK: u64 = 0x0000_0008;
|
||||
const DEVICE_RIGHT_COMMAND_MASK: u64 = 0x0000_0010;
|
||||
const DEVICE_LEFT_ALTERNATE_MASK: u64 = 0x0000_0020;
|
||||
const DEVICE_RIGHT_ALTERNATE_MASK: u64 = 0x0000_0040;
|
||||
const DEVICE_RIGHT_CONTROL_MASK: u64 = 0x0000_2000;
|
||||
|
||||
const LEFT_SHIFT_KEYCODE: u16 = 0x38;
|
||||
const RIGHT_SHIFT_KEYCODE: u16 = 0x3c;
|
||||
const LEFT_CONTROL_KEYCODE: u16 = 0x3b;
|
||||
@@ -31,14 +40,64 @@ pub fn from_flags(flags: u64) -> Modifiers {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modifier_key_down_from_flags(keycode: u16, flags: u64) -> Option<bool> {
|
||||
match keycode {
|
||||
LEFT_SHIFT_KEYCODE | RIGHT_SHIFT_KEYCODE => Some((flags & SHIFT_MASK) != 0),
|
||||
LEFT_CONTROL_KEYCODE | RIGHT_CONTROL_KEYCODE => Some((flags & CONTROL_MASK) != 0),
|
||||
LEFT_OPTION_KEYCODE | RIGHT_OPTION_KEYCODE => Some((flags & ALTERNATE_MASK) != 0),
|
||||
LEFT_COMMAND_KEYCODE | RIGHT_COMMAND_KEYCODE => Some((flags & COMMAND_MASK) != 0),
|
||||
_ => None,
|
||||
struct ModifierSide {
|
||||
group_mask: u64,
|
||||
side_mask: u64,
|
||||
other_side_mask: u64,
|
||||
}
|
||||
|
||||
fn modifier_side(keycode: u16) -> Option<ModifierSide> {
|
||||
let (group_mask, side_mask, other_side_mask) = match keycode {
|
||||
LEFT_SHIFT_KEYCODE => (SHIFT_MASK, DEVICE_LEFT_SHIFT_MASK, DEVICE_RIGHT_SHIFT_MASK),
|
||||
RIGHT_SHIFT_KEYCODE => (SHIFT_MASK, DEVICE_RIGHT_SHIFT_MASK, DEVICE_LEFT_SHIFT_MASK),
|
||||
LEFT_CONTROL_KEYCODE => (
|
||||
CONTROL_MASK,
|
||||
DEVICE_LEFT_CONTROL_MASK,
|
||||
DEVICE_RIGHT_CONTROL_MASK,
|
||||
),
|
||||
RIGHT_CONTROL_KEYCODE => (
|
||||
CONTROL_MASK,
|
||||
DEVICE_RIGHT_CONTROL_MASK,
|
||||
DEVICE_LEFT_CONTROL_MASK,
|
||||
),
|
||||
LEFT_OPTION_KEYCODE => (
|
||||
ALTERNATE_MASK,
|
||||
DEVICE_LEFT_ALTERNATE_MASK,
|
||||
DEVICE_RIGHT_ALTERNATE_MASK,
|
||||
),
|
||||
RIGHT_OPTION_KEYCODE => (
|
||||
ALTERNATE_MASK,
|
||||
DEVICE_RIGHT_ALTERNATE_MASK,
|
||||
DEVICE_LEFT_ALTERNATE_MASK,
|
||||
),
|
||||
LEFT_COMMAND_KEYCODE => (
|
||||
COMMAND_MASK,
|
||||
DEVICE_LEFT_COMMAND_MASK,
|
||||
DEVICE_RIGHT_COMMAND_MASK,
|
||||
),
|
||||
RIGHT_COMMAND_KEYCODE => (
|
||||
COMMAND_MASK,
|
||||
DEVICE_RIGHT_COMMAND_MASK,
|
||||
DEVICE_LEFT_COMMAND_MASK,
|
||||
),
|
||||
_ => return None,
|
||||
};
|
||||
Some(ModifierSide {
|
||||
group_mask,
|
||||
side_mask,
|
||||
other_side_mask,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn modifier_key_down_from_flags(keycode: u16, flags: u64, was_held: bool) -> Option<bool> {
|
||||
let side = modifier_side(keycode)?;
|
||||
if flags & side.group_mask == 0 {
|
||||
return Some(false);
|
||||
}
|
||||
if flags & (side.side_mask | side.other_side_mask) != 0 {
|
||||
return Some(flags & side.side_mask != 0);
|
||||
}
|
||||
Some(!was_held)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -85,19 +144,84 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modifier_key_down_uses_matching_aggregate_flag() {
|
||||
fn modifier_key_down_uses_the_device_side_bits() {
|
||||
let both_shifts = SHIFT_MASK | DEVICE_LEFT_SHIFT_MASK | DEVICE_RIGHT_SHIFT_MASK;
|
||||
assert_eq!(
|
||||
modifier_key_down_from_flags(LEFT_SHIFT_KEYCODE, SHIFT_MASK),
|
||||
modifier_key_down_from_flags(LEFT_SHIFT_KEYCODE, both_shifts, false),
|
||||
Some(true)
|
||||
);
|
||||
let right_only = SHIFT_MASK | DEVICE_RIGHT_SHIFT_MASK;
|
||||
assert_eq!(
|
||||
modifier_key_down_from_flags(RIGHT_SHIFT_KEYCODE, 0),
|
||||
modifier_key_down_from_flags(LEFT_SHIFT_KEYCODE, right_only, true),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
modifier_key_down_from_flags(LEFT_COMMAND_KEYCODE, COMMAND_MASK),
|
||||
modifier_key_down_from_flags(RIGHT_SHIFT_KEYCODE, right_only, false),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(modifier_key_down_from_flags(0x39, 0), None);
|
||||
assert_eq!(
|
||||
modifier_key_down_from_flags(RIGHT_SHIFT_KEYCODE, 0, true),
|
||||
Some(false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn releasing_one_side_while_the_other_is_held_is_a_keyup_for_every_group() {
|
||||
let cases = [
|
||||
(
|
||||
LEFT_CONTROL_KEYCODE,
|
||||
RIGHT_CONTROL_KEYCODE,
|
||||
CONTROL_MASK,
|
||||
DEVICE_LEFT_CONTROL_MASK,
|
||||
DEVICE_RIGHT_CONTROL_MASK,
|
||||
),
|
||||
(
|
||||
LEFT_OPTION_KEYCODE,
|
||||
RIGHT_OPTION_KEYCODE,
|
||||
ALTERNATE_MASK,
|
||||
DEVICE_LEFT_ALTERNATE_MASK,
|
||||
DEVICE_RIGHT_ALTERNATE_MASK,
|
||||
),
|
||||
(
|
||||
LEFT_COMMAND_KEYCODE,
|
||||
RIGHT_COMMAND_KEYCODE,
|
||||
COMMAND_MASK,
|
||||
DEVICE_LEFT_COMMAND_MASK,
|
||||
DEVICE_RIGHT_COMMAND_MASK,
|
||||
),
|
||||
];
|
||||
for (left, right, group, left_bit, right_bit) in cases {
|
||||
assert_eq!(
|
||||
modifier_key_down_from_flags(left, group | left_bit, false),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
modifier_key_down_from_flags(right, group | left_bit | right_bit, false),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
modifier_key_down_from_flags(left, group | right_bit, true),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(modifier_key_down_from_flags(right, 0, true), Some(false));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_device_bits_fall_back_to_toggling_the_held_state() {
|
||||
assert_eq!(
|
||||
modifier_key_down_from_flags(RIGHT_OPTION_KEYCODE, ALTERNATE_MASK, false),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
modifier_key_down_from_flags(RIGHT_OPTION_KEYCODE, ALTERNATE_MASK, true),
|
||||
Some(false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_modifier_keycodes_are_not_decided() {
|
||||
assert_eq!(modifier_key_down_from_flags(0x39, 0, false), None);
|
||||
assert_eq!(modifier_key_down_from_flags(0x00, SHIFT_MASK, true), None);
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,11 @@ pub enum CgEventType {
|
||||
LeftMouseUp = 2,
|
||||
RightMouseDown = 3,
|
||||
RightMouseUp = 4,
|
||||
MouseMoved = 5,
|
||||
LeftMouseDragged = 6,
|
||||
RightMouseDragged = 7,
|
||||
KeyDown = 10,
|
||||
KeyUp = 11,
|
||||
FlagsChanged = 12,
|
||||
ScrollWheel = 22,
|
||||
OtherMouseDown = 25,
|
||||
OtherMouseUp = 26,
|
||||
OtherMouseDragged = 27,
|
||||
}
|
||||
|
||||
impl CgEventType {
|
||||
@@ -26,16 +21,11 @@ impl CgEventType {
|
||||
2 => Self::LeftMouseUp,
|
||||
3 => Self::RightMouseDown,
|
||||
4 => Self::RightMouseUp,
|
||||
5 => Self::MouseMoved,
|
||||
6 => Self::LeftMouseDragged,
|
||||
7 => Self::RightMouseDragged,
|
||||
10 => Self::KeyDown,
|
||||
11 => Self::KeyUp,
|
||||
12 => Self::FlagsChanged,
|
||||
22 => Self::ScrollWheel,
|
||||
25 => Self::OtherMouseDown,
|
||||
26 => Self::OtherMouseUp,
|
||||
27 => Self::OtherMouseDragged,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -117,13 +107,15 @@ mod tests {
|
||||
assert!(is_down(CgEventType::LeftMouseDown));
|
||||
assert!(!is_down(CgEventType::LeftMouseUp));
|
||||
assert!(is_down(CgEventType::OtherMouseDown));
|
||||
assert!(!is_down(CgEventType::MouseMoved));
|
||||
assert!(!is_down(CgEventType::KeyDown));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_u32_maps_known_event_types() {
|
||||
assert_eq!(Some(CgEventType::LeftMouseDown), CgEventType::from_u32(1));
|
||||
assert_eq!(Some(CgEventType::ScrollWheel), CgEventType::from_u32(22));
|
||||
assert_eq!(Some(CgEventType::OtherMouseUp), CgEventType::from_u32(26));
|
||||
assert_eq!(None, CgEventType::from_u32(5));
|
||||
assert_eq!(None, CgEventType::from_u32(22));
|
||||
assert_eq!(None, CgEventType::from_u32(999));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::sync::{
|
||||
Arc, Mutex,
|
||||
Arc, Mutex, MutexGuard,
|
||||
atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering},
|
||||
mpsc::{Receiver, Sender, channel},
|
||||
};
|
||||
@@ -33,14 +34,12 @@ unsafe extern "C" {
|
||||
fn CGEventTapEnable(tap: CFMachPortRef, enable: bool);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum InputEventKind {
|
||||
KeyDown,
|
||||
KeyUp,
|
||||
MouseDown,
|
||||
MouseUp,
|
||||
MouseMove,
|
||||
Wheel,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -50,8 +49,6 @@ pub struct InputEventPayload {
|
||||
keycode: u32,
|
||||
key_name: String,
|
||||
button: u8,
|
||||
delta_x: i32,
|
||||
delta_y: i32,
|
||||
x: i32,
|
||||
y: i32,
|
||||
has_xy: bool,
|
||||
@@ -66,8 +63,6 @@ impl ToNapiValue for InputEventPayload {
|
||||
InputEventKind::KeyUp => "keyup",
|
||||
InputEventKind::MouseDown => "mousedown",
|
||||
InputEventKind::MouseUp => "mouseup",
|
||||
InputEventKind::MouseMove => "mousemove",
|
||||
InputEventKind::Wheel => "wheel",
|
||||
};
|
||||
obj.set("type", kind_str)?;
|
||||
obj.set("ctrlKey", value.mods.ctrl)?;
|
||||
@@ -87,18 +82,6 @@ impl ToNapiValue for InputEventPayload {
|
||||
obj.set("y", value.y)?;
|
||||
}
|
||||
}
|
||||
InputEventKind::MouseMove => {
|
||||
obj.set("x", value.x)?;
|
||||
obj.set("y", value.y)?;
|
||||
}
|
||||
InputEventKind::Wheel => {
|
||||
obj.set("deltaX", value.delta_x)?;
|
||||
obj.set("deltaY", value.delta_y)?;
|
||||
if value.has_xy {
|
||||
obj.set("x", value.x)?;
|
||||
obj.set("y", value.y)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { <Object<'_> as ToNapiValue>::to_napi_value(raw_env, obj) }
|
||||
}
|
||||
@@ -112,12 +95,9 @@ type EventTsfn = Arc<
|
||||
Status,
|
||||
false,
|
||||
true,
|
||||
EVENT_QUEUE_LIMIT,
|
||||
>,
|
||||
>;
|
||||
|
||||
const EVENT_QUEUE_LIMIT: usize = 1024;
|
||||
|
||||
struct WorkerState {
|
||||
run_loop: CFRunLoop,
|
||||
join: JoinHandle<()>,
|
||||
@@ -138,7 +118,6 @@ impl InputHook {
|
||||
.build_threadsafe_function::<InputEventPayload>()
|
||||
.weak::<true>()
|
||||
.callee_handled::<false>()
|
||||
.max_queue_size::<EVENT_QUEUE_LIMIT>()
|
||||
.build()
|
||||
.map_err(|err| {
|
||||
Error::new(
|
||||
@@ -235,11 +214,6 @@ const EVENTS_OF_INTEREST: &[CGEventType] = &[
|
||||
CGEventType::RightMouseUp,
|
||||
CGEventType::OtherMouseDown,
|
||||
CGEventType::OtherMouseUp,
|
||||
CGEventType::MouseMoved,
|
||||
CGEventType::LeftMouseDragged,
|
||||
CGEventType::RightMouseDragged,
|
||||
CGEventType::OtherMouseDragged,
|
||||
CGEventType::ScrollWheel,
|
||||
];
|
||||
|
||||
const CAPS_LOCK_KEYCODE: u16 = 0x39;
|
||||
@@ -253,6 +227,7 @@ fn worker_main(tsfn: EventTsfn, loop_tx: Sender<std::result::Result<CFRunLoop, S
|
||||
tap_ref: tap_ref.clone(),
|
||||
last_flags: last_flags.clone(),
|
||||
caps_lock_via_hid: caps_lock_via_hid.clone(),
|
||||
held: Mutex::new(HeldInputs::default()),
|
||||
};
|
||||
let tap_result = CGEventTap::new(
|
||||
CGEventTapLocation::Session,
|
||||
@@ -319,8 +294,67 @@ fn caps_lock_payload(pressed: bool, flags: u64) -> InputEventPayload {
|
||||
keycode: u32::from(CAPS_LOCK_KEYCODE),
|
||||
key_name: keymap::keycode_name_or_fallback(CAPS_LOCK_KEYCODE),
|
||||
button: 0,
|
||||
delta_x: 0,
|
||||
delta_y: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
has_xy: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct HeldInputs {
|
||||
keys: BTreeSet<u16>,
|
||||
buttons: BTreeSet<u8>,
|
||||
}
|
||||
|
||||
impl HeldInputs {
|
||||
fn track(&mut self, payload: &InputEventPayload) {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let keycode = payload.keycode as u16;
|
||||
match payload.kind {
|
||||
InputEventKind::KeyDown => {
|
||||
self.keys.insert(keycode);
|
||||
}
|
||||
InputEventKind::KeyUp => {
|
||||
self.keys.remove(&keycode);
|
||||
}
|
||||
InputEventKind::MouseDown => {
|
||||
self.buttons.insert(payload.button);
|
||||
}
|
||||
InputEventKind::MouseUp => {
|
||||
self.buttons.remove(&payload.button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn take_releases(&mut self) -> Vec<InputEventPayload> {
|
||||
let mods = modifiers::from_flags(0);
|
||||
let keys = std::mem::take(&mut self.keys)
|
||||
.into_iter()
|
||||
.map(|keycode| key_payload(InputEventKind::KeyUp, keycode, mods));
|
||||
let buttons =
|
||||
std::mem::take(&mut self.buttons)
|
||||
.into_iter()
|
||||
.map(|button| InputEventPayload {
|
||||
kind: InputEventKind::MouseUp,
|
||||
mods,
|
||||
keycode: 0,
|
||||
key_name: String::new(),
|
||||
button,
|
||||
x: 0,
|
||||
y: 0,
|
||||
has_xy: false,
|
||||
});
|
||||
keys.chain(buttons).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn key_payload(kind: InputEventKind, keycode: u16, mods: Modifiers) -> InputEventPayload {
|
||||
InputEventPayload {
|
||||
kind,
|
||||
mods,
|
||||
keycode: u32::from(keycode),
|
||||
key_name: keymap::keycode_name_or_fallback(keycode),
|
||||
button: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
has_xy: false,
|
||||
@@ -332,6 +366,7 @@ struct EventDispatcher {
|
||||
tap_ref: Arc<AtomicPtr<c_void>>,
|
||||
last_flags: Arc<AtomicU64>,
|
||||
caps_lock_via_hid: Arc<AtomicBool>,
|
||||
held: Mutex<HeldInputs>,
|
||||
}
|
||||
|
||||
impl EventDispatcher {
|
||||
@@ -344,6 +379,7 @@ impl EventDispatcher {
|
||||
match event_type {
|
||||
CGEventType::TapDisabledByTimeout | CGEventType::TapDisabledByUserInput => {
|
||||
self.reenable_tap();
|
||||
self.release_all();
|
||||
return CallbackResult::Keep;
|
||||
}
|
||||
_ => {}
|
||||
@@ -380,7 +416,9 @@ impl EventDispatcher {
|
||||
self.send_key(InputEventKind::KeyUp, keycode, mods);
|
||||
return CallbackResult::Keep;
|
||||
}
|
||||
let Some(is_down) = modifiers::modifier_key_down_from_flags(keycode, flags.bits())
|
||||
let was_held = self.held().keys.contains(&keycode);
|
||||
let Some(is_down) =
|
||||
modifiers::modifier_key_down_from_flags(keycode, flags.bits(), was_held)
|
||||
else {
|
||||
return CallbackResult::Keep;
|
||||
};
|
||||
@@ -414,8 +452,6 @@ impl EventDispatcher {
|
||||
keycode: 0,
|
||||
key_name: String::new(),
|
||||
button: b,
|
||||
delta_x: 0,
|
||||
delta_y: 0,
|
||||
x: point.x as i32,
|
||||
y: point.y as i32,
|
||||
has_xy: true,
|
||||
@@ -423,74 +459,37 @@ impl EventDispatcher {
|
||||
self.send(payload);
|
||||
}
|
||||
}
|
||||
CgEventType::MouseMoved
|
||||
| CgEventType::LeftMouseDragged
|
||||
| CgEventType::RightMouseDragged
|
||||
| CgEventType::OtherMouseDragged => {
|
||||
let point = event.location();
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let payload = InputEventPayload {
|
||||
kind: InputEventKind::MouseMove,
|
||||
mods,
|
||||
keycode: 0,
|
||||
key_name: String::new(),
|
||||
button: 0,
|
||||
delta_x: 0,
|
||||
delta_y: 0,
|
||||
x: point.x as i32,
|
||||
y: point.y as i32,
|
||||
has_xy: true,
|
||||
};
|
||||
self.send(payload);
|
||||
}
|
||||
CgEventType::ScrollWheel => {
|
||||
let dy = event.get_integer_value_field(EventField::SCROLL_WHEEL_EVENT_DELTA_AXIS_1)
|
||||
as i32;
|
||||
let dx = event.get_integer_value_field(EventField::SCROLL_WHEEL_EVENT_DELTA_AXIS_2)
|
||||
as i32;
|
||||
let point = event.location();
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let payload = InputEventPayload {
|
||||
kind: InputEventKind::Wheel,
|
||||
mods,
|
||||
keycode: 0,
|
||||
key_name: String::new(),
|
||||
button: 0,
|
||||
delta_x: dx,
|
||||
delta_y: dy,
|
||||
x: point.x as i32,
|
||||
y: point.y as i32,
|
||||
has_xy: true,
|
||||
};
|
||||
self.send(payload);
|
||||
}
|
||||
}
|
||||
|
||||
CallbackResult::Keep
|
||||
}
|
||||
|
||||
fn send_key(&self, kind: InputEventKind, keycode: u16, mods: Modifiers) {
|
||||
let payload = InputEventPayload {
|
||||
kind,
|
||||
mods,
|
||||
keycode: u32::from(keycode),
|
||||
key_name: keymap::keycode_name_or_fallback(keycode),
|
||||
button: 0,
|
||||
delta_x: 0,
|
||||
delta_y: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
has_xy: false,
|
||||
};
|
||||
self.send(payload);
|
||||
self.send(key_payload(kind, keycode, mods));
|
||||
}
|
||||
|
||||
fn held(&self) -> MutexGuard<'_, HeldInputs> {
|
||||
self.held
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
}
|
||||
|
||||
fn send(&self, payload: InputEventPayload) {
|
||||
self.held().track(&payload);
|
||||
let _ = self
|
||||
.tsfn
|
||||
.call(payload, ThreadsafeFunctionCallMode::NonBlocking);
|
||||
}
|
||||
|
||||
fn release_all(&self) {
|
||||
let releases = self.held().take_releases();
|
||||
for payload in releases {
|
||||
let _ = self
|
||||
.tsfn
|
||||
.call(payload, ThreadsafeFunctionCallMode::NonBlocking);
|
||||
}
|
||||
}
|
||||
|
||||
fn reenable_tap(&self) {
|
||||
let tap: CFMachPortRef = self.tap_ref.load(Ordering::Acquire).cast();
|
||||
if !tap.is_null() {
|
||||
@@ -502,3 +501,43 @@ impl EventDispatcher {
|
||||
pub fn has_accessibility_permission() -> bool {
|
||||
objc2_core_graphics::CGPreflightListenEventAccess()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn held_inputs_release_every_pressed_key_and_button_once() {
|
||||
let mods = modifiers::from_flags(0);
|
||||
let mut held = HeldInputs::default();
|
||||
held.track(&key_payload(InputEventKind::KeyDown, 0x31, mods));
|
||||
held.track(&key_payload(InputEventKind::KeyDown, 0x3b, mods));
|
||||
held.track(&key_payload(InputEventKind::KeyDown, 0x00, mods));
|
||||
held.track(&key_payload(InputEventKind::KeyUp, 0x00, mods));
|
||||
held.track(&InputEventPayload {
|
||||
kind: InputEventKind::MouseDown,
|
||||
mods,
|
||||
keycode: 0,
|
||||
key_name: String::new(),
|
||||
button: 3,
|
||||
x: 0,
|
||||
y: 0,
|
||||
has_xy: true,
|
||||
});
|
||||
let releases = held.take_releases();
|
||||
let summary: Vec<(InputEventKind, u32, u8)> = releases
|
||||
.iter()
|
||||
.map(|payload| (payload.kind, payload.keycode, payload.button))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
summary,
|
||||
vec![
|
||||
(InputEventKind::KeyUp, 0x31, 0),
|
||||
(InputEventKind::KeyUp, 0x3b, 0),
|
||||
(InputEventKind::MouseUp, 0, 3),
|
||||
]
|
||||
);
|
||||
assert!(releases.iter().all(|payload| !payload.mods.ctrl));
|
||||
assert!(held.take_releases().is_empty());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user