这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ wayland = ["input", "input-linux", "xkbcommon"]
x11 = ["dep:x11"]

[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.26"
core-graphics = {version = "0.24.0", features = ["highsierra"]}
# cocoa = "0.26"
# core-graphics = {version = "0.24.0", features = ["highsierra"]}
core-foundation = {version = "0.10"}
core-foundation-sys = {version = "0.8"}
dispatch = "0.2"
objc2-foundation = "0.3.1"
objc2-core-graphics = "0.3.1"
objc2-core-foundation = "0.3.1"
objc2 = "0.6.1"


[target.'cfg(all(target_family = "unix", not(target_os = "macos")))'.dependencies]
Expand Down
160 changes: 50 additions & 110 deletions src/macos/common.rs
Original file line number Diff line number Diff line change
@@ -1,103 +1,27 @@
#![allow(clippy::upper_case_acronyms)]
use crate::macos::keyboard::Keyboard;
use crate::rdev::{Button, Event, EventType};
use cocoa::base::id;
use core_graphics::event::{CGEvent, CGEventFlags, CGEventTapLocation, CGEventType, EventField};
use core::ptr::NonNull;
use lazy_static::lazy_static;
use objc2_core_graphics::{CGEvent, CGEventField, CGEventFlags, CGEventType};
use std::convert::TryInto;
use std::os::raw::c_void;
use std::sync::Mutex;
use std::time::SystemTime;

use crate::macos::keycodes::key_from_code;

pub type CFMachPortRef = *const c_void;
pub type CFIndex = u64;
pub type CFAllocatorRef = id;
pub type CFRunLoopSourceRef = id;
pub type CFRunLoopRef = id;
pub type CFRunLoopMode = id;
pub type CGEventTapProxy = id;
pub type CGEventRef = CGEvent;

// https://developer.apple.com/documentation/coregraphics/cgeventtapplacement?language=objc
pub type CGEventTapPlacement = u32;
#[allow(non_upper_case_globals)]
pub const kCGHeadInsertEventTap: u32 = 0;

// https://developer.apple.com/documentation/coregraphics/cgeventtapoptions?language=objc
#[allow(non_upper_case_globals)]
#[repr(u32)]
pub enum CGEventTapOption {
#[cfg(feature = "unstable_grab")]
Default = 0,
ListenOnly = 1,
}

lazy_static! {
pub static ref LAST_FLAGS: Mutex<CGEventFlags> = Mutex::new(CGEventFlags::CGEventFlagNull);
pub static ref LAST_FLAGS: Mutex<CGEventFlags> = Mutex::new(CGEventFlags(0));
pub static ref KEYBOARD_STATE: Mutex<Keyboard> = Mutex::new(Keyboard::new().unwrap());
}

// https://developer.apple.com/documentation/coregraphics/cgeventmask?language=objc
pub type CGEventMask = u64;
#[allow(non_upper_case_globals)]
pub const kCGEventMaskForAllEvents: u64 = (1 << CGEventType::LeftMouseDown as u64)
+ (1 << CGEventType::LeftMouseUp as u64)
+ (1 << CGEventType::RightMouseDown as u64)
+ (1 << CGEventType::RightMouseUp as u64)
+ (1 << CGEventType::MouseMoved as u64)
+ (1 << CGEventType::LeftMouseDragged as u64)
+ (1 << CGEventType::RightMouseDragged as u64)
+ (1 << CGEventType::KeyDown as u64)
+ (1 << CGEventType::KeyUp as u64)
+ (1 << CGEventType::FlagsChanged as u64)
+ (1 << CGEventType::ScrollWheel as u64);

#[cfg(target_os = "macos")]
#[link(name = "Cocoa", kind = "framework")]
unsafe extern "C" {
#[allow(improper_ctypes)]
pub fn CGEventTapCreate(
tap: CGEventTapLocation,
place: CGEventTapPlacement,
options: CGEventTapOption,
eventsOfInterest: CGEventMask,
callback: QCallback,
user_info: id,
) -> CFMachPortRef;
pub fn CFMachPortCreateRunLoopSource(
allocator: CFAllocatorRef,
tap: CFMachPortRef,
order: CFIndex,
) -> CFRunLoopSourceRef;
pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
pub fn CFRunLoopGetCurrent() -> CFRunLoopRef;
pub fn CGEventTapEnable(tap: CFMachPortRef, enable: bool);
pub fn CFRunLoopRun();

pub static kCFRunLoopCommonModes: CFRunLoopMode;

}

// TODO Remove this, this was added as the coded
// existed and worked, but clippy is complaining.
// There's probably a better fix.
#[allow(improper_ctypes_definitions)]
pub type QCallback = unsafe extern "C" fn(
proxy: CGEventTapProxy,
_type: CGEventType,
cg_event: CGEventRef,
user_info: *mut c_void,
) -> CGEventRef;

pub fn set_is_main_thread(b: bool) {
KEYBOARD_STATE.lock().unwrap().set_is_main_thread(b);
}

pub unsafe fn convert(
_type: CGEventType,
cg_event: &CGEvent,
cg_event: NonNull<CGEvent>,
keyboard_state: &mut Keyboard,
) -> Option<Event> {
unsafe {
Expand All @@ -107,81 +31,91 @@ pub unsafe fn convert(
CGEventType::RightMouseDown => Some(EventType::ButtonPress(Button::Right)),
CGEventType::RightMouseUp => Some(EventType::ButtonRelease(Button::Right)),
CGEventType::MouseMoved => {
let point = cg_event.location();
let point = CGEvent::location(Some(cg_event.as_ref()));
// let point = cg_event.location();
Some(EventType::MouseMove {
x: point.x,
y: point.y,
})
}
CGEventType::LeftMouseDragged => {
let point = cg_event.location();
let point = CGEvent::location(Some(cg_event.as_ref()));
Some(EventType::MouseMove {
x: point.x,
y: point.y,
})
}
CGEventType::RightMouseDragged => {
let point = cg_event.location();
let point = CGEvent::location(Some(cg_event.as_ref()));
Some(EventType::MouseMove {
x: point.x,
y: point.y,
})
}
CGEventType::KeyDown => {
let code = cg_event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE);
let code = CGEvent::integer_value_field(
Some(cg_event.as_ref()),
CGEventField::KeyboardEventKeycode,
);
let key = key_from_code(code.try_into().ok()?);
Some(EventType::KeyPress(key))
}
CGEventType::KeyUp => {
let code = cg_event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE);
let code = CGEvent::integer_value_field(
Some(cg_event.as_ref()),
CGEventField::KeyboardEventKeycode,
);
let key = key_from_code(code.try_into().ok()?);
Some(EventType::KeyRelease(key))
}
CGEventType::FlagsChanged => {
let code = cg_event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE);
let code = CGEvent::integer_value_field(
Some(cg_event.as_ref()),
CGEventField::KeyboardEventKeycode,
);
let code = code.try_into().ok()?;
let flags = cg_event.get_flags();
let flags = CGEvent::flags(Some(cg_event.as_ref()));
let key = key_from_code(code);

// Determine if this is a press or release based on flag changes
let mut global_flags = LAST_FLAGS.lock().unwrap();
if flags.contains(CGEventFlags::CGEventFlagShift)
&& !global_flags.contains(CGEventFlags::CGEventFlagShift)
if flags.contains(CGEventFlags::MaskShift)
&& !global_flags.contains(CGEventFlags::MaskShift)
{
*global_flags = flags;
Some(EventType::KeyPress(key))
} else if !flags.contains(CGEventFlags::CGEventFlagShift)
&& global_flags.contains(CGEventFlags::CGEventFlagShift)
} else if !flags.contains(CGEventFlags::MaskShift)
&& global_flags.contains(CGEventFlags::MaskShift)
{
*global_flags = flags;
Some(EventType::KeyRelease(key))
} else if flags.contains(CGEventFlags::CGEventFlagControl)
&& !global_flags.contains(CGEventFlags::CGEventFlagControl)
} else if flags.contains(CGEventFlags::MaskControl)
&& !global_flags.contains(CGEventFlags::MaskControl)
{
*global_flags = flags;
Some(EventType::KeyPress(key))
} else if !flags.contains(CGEventFlags::CGEventFlagControl)
&& global_flags.contains(CGEventFlags::CGEventFlagControl)
} else if !flags.contains(CGEventFlags::MaskControl)
&& global_flags.contains(CGEventFlags::MaskControl)
{
*global_flags = flags;
Some(EventType::KeyRelease(key))
} else if flags.contains(CGEventFlags::CGEventFlagAlternate)
&& !global_flags.contains(CGEventFlags::CGEventFlagAlternate)
} else if flags.contains(CGEventFlags::MaskAlternate)
&& !global_flags.contains(CGEventFlags::MaskAlternate)
{
*global_flags = flags;
Some(EventType::KeyPress(key))
} else if !flags.contains(CGEventFlags::CGEventFlagAlternate)
&& global_flags.contains(CGEventFlags::CGEventFlagAlternate)
} else if !flags.contains(CGEventFlags::MaskAlternate)
&& global_flags.contains(CGEventFlags::MaskAlternate)
{
*global_flags = flags;
Some(EventType::KeyRelease(key))
} else if flags.contains(CGEventFlags::CGEventFlagCommand)
&& !global_flags.contains(CGEventFlags::CGEventFlagCommand)
} else if flags.contains(CGEventFlags::MaskCommand)
&& !global_flags.contains(CGEventFlags::MaskCommand)
{
*global_flags = flags;
Some(EventType::KeyPress(key))
} else if !flags.contains(CGEventFlags::CGEventFlagCommand)
&& global_flags.contains(CGEventFlags::CGEventFlagCommand)
} else if !flags.contains(CGEventFlags::MaskCommand)
&& global_flags.contains(CGEventFlags::MaskCommand)
{
*global_flags = flags;
Some(EventType::KeyRelease(key))
Expand All @@ -190,20 +124,26 @@ pub unsafe fn convert(
}
}
CGEventType::ScrollWheel => {
let delta_y = cg_event
.get_integer_value_field(EventField::SCROLL_WHEEL_EVENT_POINT_DELTA_AXIS_1);
let delta_x = cg_event
.get_integer_value_field(EventField::SCROLL_WHEEL_EVENT_POINT_DELTA_AXIS_2);
let delta_y = CGEvent::integer_value_field(
Some(cg_event.as_ref()),
CGEventField::ScrollWheelEventDeltaAxis1,
);
let delta_x = CGEvent::integer_value_field(
Some(cg_event.as_ref()),
CGEventField::ScrollWheelEventDeltaAxis2,
);
Some(EventType::Wheel { delta_x, delta_y })
}
_ => None,
};
if let Some(event_type) = option_type {
let name = match event_type {
EventType::KeyPress(_) => {
let code =
cg_event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE) as u32;
let flags = cg_event.get_flags();
let code = CGEvent::integer_value_field(
Some(cg_event.as_ref()),
CGEventField::KeyboardEventKeycode,
);
let flags = CGEvent::flags(Some(cg_event.as_ref()));
keyboard_state.create_string_for_key(code, flags)
}
_ => None,
Expand Down
16 changes: 13 additions & 3 deletions src/macos/display.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
use objc2_core_graphics::{CGDisplayPixelsHigh, CGDisplayPixelsWide, CGMainDisplayID};

use crate::rdev::DisplayError;
use core_graphics::display::CGDisplay;

pub fn display_size() -> Result<(u64, u64), DisplayError> {
let main = CGDisplay::main();
Ok((main.pixels_wide(), main.pixels_high()))
unsafe {
let main = CGMainDisplayID();
Ok((
CGDisplayPixelsWide(main)
.try_into()
.map_err(|_| DisplayError::ConversionError)?,
CGDisplayPixelsHigh(main)
.try_into()
.map_err(|_| DisplayError::ConversionError)?,
))
}
}
59 changes: 30 additions & 29 deletions src/macos/grab.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,42 @@
#![allow(improper_ctypes_definitions)]
use crate::macos::common::*;
use crate::rdev::{Event, GrabError};
use cocoa::base::nil;
use cocoa::foundation::NSAutoreleasePool;
use core_graphics::event::{CGEventTapLocation, CGEventType};
use objc2_core_foundation::{CFMachPort, CFRunLoop, kCFRunLoopCommonModes};
use objc2_core_graphics::{
CGEvent, CGEventTapCallBack, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement,
CGEventTapProxy, CGEventType, kCGEventMaskForAllEvents,
};
use objc2_foundation::NSAutoreleasePool;
use std::os::raw::c_void;
use std::ptr::{NonNull, null_mut};

static mut GLOBAL_CALLBACK: Option<Box<dyn FnMut(Event) -> Option<Event>>> = None;

#[link(name = "Cocoa", kind = "framework")]
unsafe extern "C" {}

unsafe extern "C" fn raw_callback(
unsafe extern "C-unwind" fn raw_callback(
_proxy: CGEventTapProxy,
_type: CGEventType,
cg_event: CGEventRef,
cg_event: NonNull<CGEvent>,
_user_info: *mut c_void,
) -> CGEventRef {
) -> *mut CGEvent {
let opt = KEYBOARD_STATE.lock();
if let Ok(mut keyboard) = opt {
unsafe {
if let Some(event) = convert(_type, &cg_event, &mut keyboard) {
if let Some(event) = convert(_type, cg_event, &mut keyboard) {
// Reborrowing the global callback pointer.
// Totally UB. but not sure there's a great alternative.
let ptr = &raw mut GLOBAL_CALLBACK;
if let Some(callback) = &mut *ptr {
if callback(event).is_none() {
cg_event.set_type(CGEventType::Null);
CGEvent::set_type(Some(cg_event.as_ref()), CGEventType::Null)
}
}
}
}
}
cg_event
cg_event.as_ptr()
}

pub fn grab<T>(callback: T) -> Result<(), GrabError>
Expand All @@ -41,28 +45,25 @@ where
{
unsafe {
GLOBAL_CALLBACK = Some(Box::new(callback));
let _pool = NSAutoreleasePool::new(nil);
let tap = CGEventTapCreate(
CGEventTapLocation::HID, // HID, Session, AnnotatedSession,
kCGHeadInsertEventTap,
CGEventTapOption::Default,
kCGEventMaskForAllEvents,
raw_callback,
nil,
);
if tap.is_null() {
return Err(GrabError::EventTapError);
}
let _loop = CFMachPortCreateRunLoopSource(nil, tap, 0);
if _loop.is_null() {
return Err(GrabError::LoopSourceError);
}
let _pool = NSAutoreleasePool::new();
let callback: CGEventTapCallBack = Some(raw_callback);
let tap = CGEvent::tap_create(
CGEventTapLocation::HIDEventTap, // HID, Session, AnnotatedSession,
CGEventTapPlacement::HeadInsertEventTap,
CGEventTapOptions::Default,
kCGEventMaskForAllEvents.into(),
callback,
null_mut(),
)
.ok_or(GrabError::EventTapError)?;
let loop_ = CFMachPort::new_run_loop_source(None, Some(&tap), 0)
.ok_or(GrabError::LoopSourceError)?;

let current_loop = CFRunLoopGetCurrent();
CFRunLoopAddSource(current_loop, _loop, kCFRunLoopCommonModes);
let current_loop = CFRunLoop::current().unwrap();
current_loop.add_source(Some(&loop_), kCFRunLoopCommonModes);

CGEventTapEnable(tap, true);
CFRunLoopRun();
CGEvent::tap_enable(&tap, true);
CFRunLoop::run();
}
Ok(())
}
Loading