Skip to content

Commit 20bd8dd

Browse files
committed
Fix lifetime unsoundness in CGEventTap, add ::with
1 parent f0b65c2 commit 20bd8dd

File tree

1 file changed

+45
-14
lines changed

1 file changed

+45
-14
lines changed

core-graphics/src/event.rs

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -440,44 +440,75 @@ unsafe extern "C" fn cg_event_tap_callback_internal(
440440
}
441441

442442
/// ```no_run
443-
///use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
444-
///use core_graphics::event::{CGEventTap, CGEventTapLocation, CGEventTapPlacement, CGEventTapOptions, CGEventType};
445-
///let current = CFRunLoop::get_current();
446-
///match CGEventTap::new(
443+
/// use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
444+
/// use core_graphics::event::{CGEventTap, CGEventTapLocation, CGEventTapPlacement, CGEventTapOptions, CGEventType};
445+
/// let current = CFRunLoop::get_current();
446+
///
447+
/// CGEventTap::with(
447448
/// CGEventTapLocation::HID,
448449
/// CGEventTapPlacement::HeadInsertEventTap,
449450
/// CGEventTapOptions::Default,
450451
/// vec![CGEventType::MouseMoved],
451-
/// |_a, _b, d| {
452-
/// println!("{:?}", d.location());
452+
/// |_proxy, _type, event| {
453+
/// println!("{:?}", event.location());
453454
/// None
454455
/// },
455-
/// ) {
456-
/// Ok(tap) => unsafe {
456+
/// |tap| {
457457
/// let loop_source = tap
458458
/// .mach_port()
459459
/// .create_runloop_source(0)
460460
/// .expect("Runloop source creation failed");
461-
/// current.add_source(&loop_source, kCFRunLoopCommonModes);
461+
/// current.add_source(&loop_source, unsafe { kCFRunLoopCommonModes });
462462
/// tap.enable();
463463
/// CFRunLoop::run_current();
464464
/// },
465-
/// Err(_) => (assert!(false)),
466-
/// }
465+
/// ).expect("Failed to install event tap");
467466
/// ```
468467
pub struct CGEventTap<'tap_life> {
469468
mach_port: CFMachPort,
470469
_callback: Box<CGEventTapCallBackFn<'tap_life>>,
471470
}
472471

473-
impl<'tap_life> CGEventTap<'tap_life> {
474-
pub fn new<F: Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>(
472+
impl CGEventTap<'static> {
473+
pub fn new<F: Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'static>(
475474
tap: CGEventTapLocation,
476475
place: CGEventTapPlacement,
477476
options: CGEventTapOptions,
478477
events_of_interest: std::vec::Vec<CGEventType>,
479478
callback: F,
480-
) -> Result<CGEventTap<'tap_life>, ()> {
479+
) -> Result<Self, ()> {
480+
// SAFETY: callback is 'static so even if this object is forgotten it
481+
// will be valid to call.
482+
unsafe { Self::new_unchecked(tap, place, options, events_of_interest, callback) }
483+
}
484+
}
485+
486+
impl<'tap_life> CGEventTap<'tap_life> {
487+
pub fn with<R>(
488+
tap: CGEventTapLocation,
489+
place: CGEventTapPlacement,
490+
options: CGEventTapOptions,
491+
events_of_interest: std::vec::Vec<CGEventType>,
492+
callback: impl Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life,
493+
with_fn: impl FnOnce(&Self) -> R,
494+
) -> Result<R, ()> {
495+
// SAFETY: We are okay to bypass the 'static restriction because the
496+
// event tap is dropped before returning. The callback therefore cannot
497+
// be called after its lifetime expires.
498+
let event_tap: Self =
499+
unsafe { Self::new_unchecked(tap, place, options, events_of_interest, callback)? };
500+
Ok(with_fn(&event_tap))
501+
}
502+
503+
/// Caller is responsible for ensuring that this object is dropped before
504+
/// `'tap_life` expires.
505+
pub unsafe fn new_unchecked(
506+
tap: CGEventTapLocation,
507+
place: CGEventTapPlacement,
508+
options: CGEventTapOptions,
509+
events_of_interest: std::vec::Vec<CGEventType>,
510+
callback: impl Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life,
511+
) -> Result<Self, ()> {
481512
let event_mask: CGEventMask = events_of_interest
482513
.iter()
483514
.fold(CGEventType::Null as CGEventMask, |mask, &etype| {

0 commit comments

Comments
 (0)