|
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,137 @@ 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 | +/// ```no_run |
| 445 | +///extern crate core_foundation; |
| 446 | +///use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop}; |
| 447 | +///use core_graphics::event::{CGEventTap, CGEventTapLocation, CGEventTapPlacement, CGEventTapOptions, CGEventType}; |
| 448 | +///let current = CFRunLoop::get_current(); |
| 449 | +///match CGEventTap::new( |
| 450 | +/// CGEventTapLocation::HID, |
| 451 | +/// CGEventTapPlacement::HeadInsertEventTap, |
| 452 | +/// CGEventTapOptions::Default, |
| 453 | +/// vec![CGEventType::MouseMoved], |
| 454 | +/// |_a, _b, d| { |
| 455 | +/// println!("{:?}", d.location()); |
| 456 | +/// None |
| 457 | +/// }, |
| 458 | +/// ) { |
| 459 | +/// Ok(tap) => unsafe { |
| 460 | +/// let loop_source = tap |
| 461 | +/// .mach_port |
| 462 | +/// .create_runloop_source(0) |
| 463 | +/// .expect("Somethings is bad "); |
| 464 | +/// current.add_source(&loop_source, kCFRunLoopCommonModes); |
| 465 | +/// tap.enable(); |
| 466 | +/// CFRunLoop::run_current(); |
| 467 | +/// }, |
| 468 | +/// Err(_) => (assert!(false)), |
| 469 | +/// } |
| 470 | +/// ``` |
| 471 | +pub struct CGEventTap<'tap_life> { |
| 472 | + pub mach_port: CFMachPort, |
| 473 | + pub callback_ref: |
| 474 | + Box<dyn Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>, |
| 475 | +} |
| 476 | + |
| 477 | +impl<'tap_life> CGEventTap<'tap_life> { |
| 478 | + pub fn new<F: Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>( |
| 479 | + tap: CGEventTapLocation, |
| 480 | + place: CGEventTapPlacement, |
| 481 | + options: CGEventTapOptions, |
| 482 | + events_of_interest: std::vec::Vec<CGEventType>, |
| 483 | + callback: F, |
| 484 | + ) -> Result<CGEventTap<'tap_life>, ()> { |
| 485 | + let event_mask: CGEventMask = events_of_interest |
| 486 | + .iter() |
| 487 | + .fold(CGEventType::Null as CGEventMask, |mask, &etype| { |
| 488 | + mask | CGEventMaskBit!(etype) |
| 489 | + }); |
| 490 | + let cb = Box::new(Box::new(callback) as CGEventTapCallBackFn); |
| 491 | + let cbr = Box::into_raw(cb); |
| 492 | + unsafe { |
| 493 | + let event_tap_ref = CGEventTapCreate( |
| 494 | + tap, |
| 495 | + place, |
| 496 | + options, |
| 497 | + event_mask, |
| 498 | + cg_event_tap_callback_internal, |
| 499 | + cbr as *const c_void, |
| 500 | + ); |
| 501 | + |
| 502 | + if !event_tap_ref.is_null() { |
| 503 | + Ok(Self { |
| 504 | + mach_port: (CFMachPort::wrap_under_create_rule(event_tap_ref)), |
| 505 | + callback_ref: Box::from_raw(cbr), |
| 506 | + }) |
| 507 | + } else { |
| 508 | + Box::from_raw(cbr); |
| 509 | + Err(()) |
| 510 | + } |
| 511 | + } |
| 512 | + } |
| 513 | + |
| 514 | + pub fn enable(&self) { |
| 515 | + unsafe { CGEventTapEnable(self.mach_port.as_concrete_TypeRef(), true) } |
| 516 | + } |
| 517 | +} |
| 518 | + |
387 | 519 | foreign_type! {
|
388 | 520 | #[doc(hidden)]
|
389 | 521 | type CType = ::sys::CGEvent;
|
@@ -666,4 +798,17 @@ extern {
|
666 | 798 | /// fixed point number or integer, the value parameter is scaled as needed
|
667 | 799 | /// and converted to the appropriate type.
|
668 | 800 | fn CGEventSetDoubleValueField(event: ::sys::CGEventRef, field: CGEventField, value: f64);
|
| 801 | + |
| 802 | + // ::sys::CGEventTapRef is actually an CFMachPortRef |
| 803 | + fn CGEventTapCreate( |
| 804 | + tap: CGEventTapLocation, |
| 805 | + place: CGEventTapPlacement, |
| 806 | + options: CGEventTapOptions, |
| 807 | + eventsOfInterest: CGEventMask, |
| 808 | + callback: CGEventTapCallBackInternal, |
| 809 | + userInfo: *const c_void, |
| 810 | + ) -> CFMachPortRef; |
| 811 | + |
| 812 | + fn CGEventTapEnable(tap: CFMachPortRef, enable: bool); |
| 813 | + |
669 | 814 | }
|
0 commit comments