|
1 | 1 | #![allow(non_upper_case_globals)]
|
2 |
| - |
3 |
| -use core_foundation::base::{CFRelease, CFRetain, CFTypeID}; |
4 |
| -use geometry::CGPoint; |
| 2 | +use core_foundation::{ |
| 3 | + base::{CFRelease, CFRetain, CFTypeID, TCFType}, |
| 4 | + mach_port::{CFMachPort, CFMachPortRef}, |
| 5 | +}; |
5 | 6 | use event_source::CGEventSource;
|
6 |
| - |
7 |
| -use libc; |
8 |
| - |
9 | 7 | use foreign_types::ForeignType;
|
| 8 | +use geometry::CGPoint; |
| 9 | +use libc::c_void; |
| 10 | +use std::mem::ManuallyDrop; |
10 | 11 |
|
11 | 12 | pub type CGEventField = u32;
|
12 | 13 | pub type CGKeyCode = u16;
|
@@ -384,6 +385,110 @@ pub enum CGEventTapLocation {
|
384 | 385 | AnnotatedSession,
|
385 | 386 | }
|
386 | 387 |
|
| 388 | +// The next three enums are taken from: |
| 389 | +// [Ref](https://github.com/phracker/MacOSX-SDKs/blob/ef9fe35d5691b6dd383c8c46d867a499817a01b6/MacOSX10.15.sdk/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Headers/CGEventTypes.h) |
| 390 | +/* Constants that specify where a new event tap is inserted into the list of |
| 391 | +active event taps. */ |
| 392 | +#[repr(u32)] |
| 393 | +#[derive(Clone, Copy, Debug)] |
| 394 | +pub enum CGEventTapPlacement { |
| 395 | + HeadInsertEventTap = 0, |
| 396 | + TailAppendEventTap, |
| 397 | +} |
| 398 | + |
| 399 | +/* Constants that specify whether a new event tap is an active filter or a |
| 400 | +passive listener. */ |
| 401 | +#[repr(u32)] |
| 402 | +#[derive(Clone, Copy, Debug)] |
| 403 | +pub enum CGEventTapOptions { |
| 404 | + Default = 0x00000000, |
| 405 | + ListenOnly = 0x00000001, |
| 406 | +} |
| 407 | + |
| 408 | +pub type CGEventMask = u64; |
| 409 | +/* Generate an event mask for a single type of event. */ |
| 410 | +macro_rules! CGEventMaskBit { |
| 411 | + ($eventType:expr) => { |
| 412 | + 1 << $eventType as CGEventMask |
| 413 | + }; |
| 414 | +} |
| 415 | + |
| 416 | +pub type CGEventTapProxy = *const c_void; |
| 417 | +pub type CGEventTapCallBackFn<'tap_life> = |
| 418 | + Box<dyn Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>; |
| 419 | +type CGEventTapCallBackInternal = unsafe extern "C" fn( |
| 420 | + proxy: CGEventTapProxy, |
| 421 | + etype: CGEventType, |
| 422 | + event: ::sys::CGEventRef, |
| 423 | + user_info: *const c_void, |
| 424 | +) -> ::sys::CGEventRef; |
| 425 | + |
| 426 | +#[no_mangle] |
| 427 | +unsafe extern "C" fn cg_event_tap_callback_internal( |
| 428 | + _proxy: CGEventTapProxy, |
| 429 | + _etype: CGEventType, |
| 430 | + _event: ::sys::CGEventRef, |
| 431 | + _user_info: *const c_void, |
| 432 | +) -> ::sys::CGEventRef { |
| 433 | + let callback = _user_info as *mut CGEventTapCallBackFn; |
| 434 | + let event = CGEvent::from_ptr(_event); |
| 435 | + let new_event = (*callback)(_proxy, _etype, &event); |
| 436 | + let event = match new_event { |
| 437 | + Some(new_event) => new_event, |
| 438 | + None => event, |
| 439 | + }; |
| 440 | + ManuallyDrop::new(event).as_ptr() |
| 441 | +} |
| 442 | + |
| 443 | + |
| 444 | +pub struct CGEventTap<'tap_life> { |
| 445 | + pub mach_port: CFMachPort, |
| 446 | + pub callback_ref: |
| 447 | + Box<dyn Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>, |
| 448 | +} |
| 449 | + |
| 450 | +impl<'tap_life> CGEventTap<'tap_life> { |
| 451 | + pub fn new<F: Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>( |
| 452 | + tap: CGEventTapLocation, |
| 453 | + place: CGEventTapPlacement, |
| 454 | + options: CGEventTapOptions, |
| 455 | + events_of_interest: std::vec::Vec<CGEventType>, |
| 456 | + callback: F, |
| 457 | + ) -> Result<CGEventTap<'tap_life>, ()> { |
| 458 | + let event_mask: CGEventMask = events_of_interest |
| 459 | + .iter() |
| 460 | + .fold(CGEventType::Null as CGEventMask, |mask, &etype| { |
| 461 | + mask | CGEventMaskBit!(etype) |
| 462 | + }); |
| 463 | + let cb = Box::new(Box::new(callback) as CGEventTapCallBackFn); |
| 464 | + let cbr = Box::into_raw(cb); |
| 465 | + unsafe { |
| 466 | + let event_tap_ref = CGEventTapCreate( |
| 467 | + tap, |
| 468 | + place, |
| 469 | + options, |
| 470 | + event_mask, |
| 471 | + cg_event_tap_callback_internal, |
| 472 | + cbr as *const c_void, |
| 473 | + ); |
| 474 | + |
| 475 | + if !event_tap_ref.is_null() { |
| 476 | + Ok(Self { |
| 477 | + mach_port: (CFMachPort::wrap_under_create_rule(event_tap_ref)), |
| 478 | + callback_ref: Box::from_raw(cbr), |
| 479 | + }) |
| 480 | + } else { |
| 481 | + Box::from_raw(cbr); |
| 482 | + Err(()) |
| 483 | + } |
| 484 | + } |
| 485 | + } |
| 486 | + |
| 487 | + pub fn enable(&self) { |
| 488 | + unsafe { CGEventTapEnable(self.mach_port.as_concrete_TypeRef(), true) } |
| 489 | + } |
| 490 | +} |
| 491 | + |
387 | 492 | foreign_type! {
|
388 | 493 | #[doc(hidden)]
|
389 | 494 | type CType = ::sys::CGEvent;
|
@@ -666,4 +771,17 @@ extern {
|
666 | 771 | /// fixed point number or integer, the value parameter is scaled as needed
|
667 | 772 | /// and converted to the appropriate type.
|
668 | 773 | fn CGEventSetDoubleValueField(event: ::sys::CGEventRef, field: CGEventField, value: f64);
|
| 774 | + |
| 775 | + // ::sys::CGEventTapRef is actually an CFMachPortRef |
| 776 | + fn CGEventTapCreate( |
| 777 | + tap: CGEventTapLocation, |
| 778 | + place: CGEventTapPlacement, |
| 779 | + options: CGEventTapOptions, |
| 780 | + eventsOfInterest: CGEventMask, |
| 781 | + callback: CGEventTapCallBackInternal, |
| 782 | + userInfo: *const c_void, |
| 783 | + ) -> CFMachPortRef; |
| 784 | + |
| 785 | + fn CGEventTapEnable(tap: CFMachPortRef, enable: bool); |
| 786 | + |
669 | 787 | }
|
0 commit comments