@@ -7,6 +7,7 @@ use core::ffi::{c_ulong, c_void};
77use core_foundation:: {
88 base:: { CFRelease , CFRetain , CFTypeID , TCFType } ,
99 mach_port:: { CFMachPort , CFMachPortInvalidate , CFMachPortRef } ,
10+ runloop:: { kCFRunLoopCommonModes, CFRunLoop } ,
1011} ;
1112use foreign_types:: { foreign_type, ForeignType } ;
1213use std:: { mem:: ManuallyDrop , ptr} ;
@@ -547,7 +548,7 @@ unsafe extern "C" fn cg_event_tap_callback_internal(
547548/// use core_graphics::event::{CGEventTap, CGEventTapLocation, CGEventTapPlacement, CGEventTapOptions, CGEventType, CallbackResult};
548549/// let current = CFRunLoop::get_current();
549550///
550- /// CGEventTap::with (
551+ /// CGEventTap::with_enabled (
551552/// CGEventTapLocation::HID,
552553/// CGEventTapPlacement::HeadInsertEventTap,
553554/// CGEventTapOptions::Default,
@@ -556,55 +557,63 @@ unsafe extern "C" fn cg_event_tap_callback_internal(
556557/// println!("{:?}", event.location());
557558/// CallbackResult::Keep
558559/// },
559- /// |tap| {
560- /// let loop_source = tap
561- /// .mach_port()
562- /// .create_runloop_source(0)
563- /// .expect("Runloop source creation failed");
564- /// current.add_source(&loop_source, unsafe { kCFRunLoopCommonModes });
565- /// tap.enable();
566- /// CFRunLoop::run_current();
567- /// },
560+ /// || CFRunLoop::run_current(),
568561/// ).expect("Failed to install event tap");
569562/// ```
563+ #[ must_use = "CGEventTap is disabled when dropped" ]
570564pub struct CGEventTap < ' tap_life > {
571565 mach_port : CFMachPort ,
572566 _callback : Box < CGEventTapCallbackFn < ' tap_life > > ,
573567}
574568
575569impl CGEventTap < ' static > {
576- pub fn new < F : Fn ( CGEventTapProxy , CGEventType , & CGEvent ) -> CallbackResult + ' static > (
570+ pub fn new < F : Fn ( CGEventTapProxy , CGEventType , & CGEvent ) -> CallbackResult + Send + ' static > (
577571 tap : CGEventTapLocation ,
578572 place : CGEventTapPlacement ,
579573 options : CGEventTapOptions ,
580574 events_of_interest : std:: vec:: Vec < CGEventType > ,
581575 callback : F ,
582576 ) -> Result < Self , ( ) > {
583577 // SAFETY: callback is 'static so even if this object is forgotten it
584- // will be valid to call.
578+ // will be valid to call. F is safe to send across threads.
585579 unsafe { Self :: new_unchecked ( tap, place, options, events_of_interest, callback) }
586580 }
587581}
588582
589583impl < ' tap_life > CGEventTap < ' tap_life > {
590- pub fn with < R > (
584+ /// Configures an event tap with the supplied options and callback, then
585+ /// calls `with_fn`.
586+ ///
587+ /// Note that the current thread run loop must run within `with_fn` for the
588+ /// tap to process events. The tap is destroyed when `with_fn` returns.
589+ pub fn with_enabled < R > (
591590 tap : CGEventTapLocation ,
592591 place : CGEventTapPlacement ,
593592 options : CGEventTapOptions ,
594593 events_of_interest : std:: vec:: Vec < CGEventType > ,
595594 callback : impl Fn ( CGEventTapProxy , CGEventType , & CGEvent ) -> CallbackResult + ' tap_life ,
596- with_fn : impl FnOnce ( & Self ) -> R ,
595+ with_fn : impl FnOnce ( ) -> R ,
597596 ) -> Result < R , ( ) > {
598597 // SAFETY: We are okay to bypass the 'static restriction because the
599598 // event tap is dropped before returning. The callback therefore cannot
600- // be called after its lifetime expires.
599+ // be called after its lifetime expires. Since we only enable the tap
600+ // on the current thread run loop and don't hand it to user code, we
601+ // know that the callback will only be called from the current thread.
601602 let event_tap: Self =
602603 unsafe { Self :: new_unchecked ( tap, place, options, events_of_interest, callback) ? } ;
603- Ok ( with_fn ( & event_tap) )
604+ let loop_source = event_tap
605+ . mach_port ( )
606+ . create_runloop_source ( 0 )
607+ . expect ( "Runloop source creation failed" ) ;
608+ CFRunLoop :: get_current ( ) . add_source ( & loop_source, unsafe { kCFRunLoopCommonModes } ) ;
609+ event_tap. enable ( ) ;
610+ Ok ( with_fn ( ) )
604611 }
605612
606613 /// Caller is responsible for ensuring that this object is dropped before
607- /// `'tap_life` expires.
614+ /// `'tap_life` expires. Either state captured by `callback` must be safe to
615+ /// send across threads, or the tap must only be installed on the current
616+ /// thread's run loop.
608617 pub unsafe fn new_unchecked (
609618 tap : CGEventTapLocation ,
610619 place : CGEventTapPlacement ,
0 commit comments