[breaking][core-graphics] Fix unsoundness in CGEventTap API (#710)
* Fix unsoundness in CGEventTap API * Fix lifetime unsoundness in CGEventTap, add ::with
This commit is contained in:
+63
-23
@@ -6,7 +6,7 @@ use bitflags::bitflags;
|
|||||||
use core::ffi::{c_ulong, c_void};
|
use core::ffi::{c_ulong, c_void};
|
||||||
use core_foundation::{
|
use core_foundation::{
|
||||||
base::{CFRelease, CFRetain, CFTypeID, TCFType},
|
base::{CFRelease, CFRetain, CFTypeID, TCFType},
|
||||||
mach_port::{CFMachPort, CFMachPortRef},
|
mach_port::{CFMachPort, CFMachPortInvalidate, CFMachPortRef},
|
||||||
};
|
};
|
||||||
use foreign_types::{foreign_type, ForeignType};
|
use foreign_types::{foreign_type, ForeignType};
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
@@ -417,7 +417,7 @@ macro_rules! CGEventMaskBit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub type CGEventTapProxy = *const c_void;
|
pub type CGEventTapProxy = *const c_void;
|
||||||
pub type CGEventTapCallBackFn<'tap_life> =
|
type CGEventTapCallBackFn<'tap_life> =
|
||||||
Box<dyn Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>;
|
Box<dyn Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>;
|
||||||
type CGEventTapCallBackInternal = unsafe extern "C" fn(
|
type CGEventTapCallBackInternal = unsafe extern "C" fn(
|
||||||
proxy: CGEventTapProxy,
|
proxy: CGEventTapProxy,
|
||||||
@@ -443,51 +443,81 @@ unsafe extern "C" fn cg_event_tap_callback_internal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
///use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
|
/// use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
|
||||||
///use core_graphics::event::{CGEventTap, CGEventTapLocation, CGEventTapPlacement, CGEventTapOptions, CGEventType};
|
/// use core_graphics::event::{CGEventTap, CGEventTapLocation, CGEventTapPlacement, CGEventTapOptions, CGEventType};
|
||||||
///let current = CFRunLoop::get_current();
|
/// let current = CFRunLoop::get_current();
|
||||||
///match CGEventTap::new(
|
///
|
||||||
|
/// CGEventTap::with(
|
||||||
/// CGEventTapLocation::HID,
|
/// CGEventTapLocation::HID,
|
||||||
/// CGEventTapPlacement::HeadInsertEventTap,
|
/// CGEventTapPlacement::HeadInsertEventTap,
|
||||||
/// CGEventTapOptions::Default,
|
/// CGEventTapOptions::Default,
|
||||||
/// vec![CGEventType::MouseMoved],
|
/// vec![CGEventType::MouseMoved],
|
||||||
/// |_a, _b, d| {
|
/// |_proxy, _type, event| {
|
||||||
/// println!("{:?}", d.location());
|
/// println!("{:?}", event.location());
|
||||||
/// None
|
/// None
|
||||||
/// },
|
/// },
|
||||||
/// ) {
|
/// |tap| {
|
||||||
/// Ok(tap) => unsafe {
|
|
||||||
/// let loop_source = tap
|
/// let loop_source = tap
|
||||||
/// .mach_port
|
/// .mach_port()
|
||||||
/// .create_runloop_source(0)
|
/// .create_runloop_source(0)
|
||||||
/// .expect("Somethings is bad ");
|
/// .expect("Runloop source creation failed");
|
||||||
/// current.add_source(&loop_source, kCFRunLoopCommonModes);
|
/// current.add_source(&loop_source, unsafe { kCFRunLoopCommonModes });
|
||||||
/// tap.enable();
|
/// tap.enable();
|
||||||
/// CFRunLoop::run_current();
|
/// CFRunLoop::run_current();
|
||||||
/// },
|
/// },
|
||||||
/// Err(_) => (assert!(false)),
|
/// ).expect("Failed to install event tap");
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub struct CGEventTap<'tap_life> {
|
pub struct CGEventTap<'tap_life> {
|
||||||
pub mach_port: CFMachPort,
|
mach_port: CFMachPort,
|
||||||
pub callback_ref:
|
_callback: Box<CGEventTapCallBackFn<'tap_life>>,
|
||||||
Box<dyn Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tap_life> CGEventTap<'tap_life> {
|
impl CGEventTap<'static> {
|
||||||
pub fn new<F: Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>(
|
pub fn new<F: Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'static>(
|
||||||
tap: CGEventTapLocation,
|
tap: CGEventTapLocation,
|
||||||
place: CGEventTapPlacement,
|
place: CGEventTapPlacement,
|
||||||
options: CGEventTapOptions,
|
options: CGEventTapOptions,
|
||||||
events_of_interest: std::vec::Vec<CGEventType>,
|
events_of_interest: std::vec::Vec<CGEventType>,
|
||||||
callback: F,
|
callback: F,
|
||||||
) -> Result<CGEventTap<'tap_life>, ()> {
|
) -> Result<Self, ()> {
|
||||||
|
// SAFETY: callback is 'static so even if this object is forgotten it
|
||||||
|
// will be valid to call.
|
||||||
|
unsafe { Self::new_unchecked(tap, place, options, events_of_interest, callback) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tap_life> CGEventTap<'tap_life> {
|
||||||
|
pub fn with<R>(
|
||||||
|
tap: CGEventTapLocation,
|
||||||
|
place: CGEventTapPlacement,
|
||||||
|
options: CGEventTapOptions,
|
||||||
|
events_of_interest: std::vec::Vec<CGEventType>,
|
||||||
|
callback: impl Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life,
|
||||||
|
with_fn: impl FnOnce(&Self) -> R,
|
||||||
|
) -> Result<R, ()> {
|
||||||
|
// SAFETY: We are okay to bypass the 'static restriction because the
|
||||||
|
// event tap is dropped before returning. The callback therefore cannot
|
||||||
|
// be called after its lifetime expires.
|
||||||
|
let event_tap: Self =
|
||||||
|
unsafe { Self::new_unchecked(tap, place, options, events_of_interest, callback)? };
|
||||||
|
Ok(with_fn(&event_tap))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Caller is responsible for ensuring that this object is dropped before
|
||||||
|
/// `'tap_life` expires.
|
||||||
|
pub unsafe fn new_unchecked(
|
||||||
|
tap: CGEventTapLocation,
|
||||||
|
place: CGEventTapPlacement,
|
||||||
|
options: CGEventTapOptions,
|
||||||
|
events_of_interest: std::vec::Vec<CGEventType>,
|
||||||
|
callback: impl Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life,
|
||||||
|
) -> Result<Self, ()> {
|
||||||
let event_mask: CGEventMask = events_of_interest
|
let event_mask: CGEventMask = events_of_interest
|
||||||
.iter()
|
.iter()
|
||||||
.fold(CGEventType::Null as CGEventMask, |mask, &etype| {
|
.fold(CGEventType::Null as CGEventMask, |mask, &etype| {
|
||||||
mask | CGEventMaskBit!(etype)
|
mask | CGEventMaskBit!(etype)
|
||||||
});
|
});
|
||||||
let cb = Box::new(Box::new(callback) as CGEventTapCallBackFn);
|
let cb: Box<CGEventTapCallBackFn> = Box::new(Box::new(callback));
|
||||||
let cbr = Box::into_raw(cb);
|
let cbr = Box::into_raw(cb);
|
||||||
unsafe {
|
unsafe {
|
||||||
let event_tap_ref = CGEventTapCreate(
|
let event_tap_ref = CGEventTapCreate(
|
||||||
@@ -502,7 +532,7 @@ impl<'tap_life> CGEventTap<'tap_life> {
|
|||||||
if !event_tap_ref.is_null() {
|
if !event_tap_ref.is_null() {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
mach_port: (CFMachPort::wrap_under_create_rule(event_tap_ref)),
|
mach_port: (CFMachPort::wrap_under_create_rule(event_tap_ref)),
|
||||||
callback_ref: Box::from_raw(cbr),
|
_callback: Box::from_raw(cbr),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let _ = Box::from_raw(cbr);
|
let _ = Box::from_raw(cbr);
|
||||||
@@ -511,11 +541,21 @@ impl<'tap_life> CGEventTap<'tap_life> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mach_port(&self) -> &CFMachPort {
|
||||||
|
&self.mach_port
|
||||||
|
}
|
||||||
|
|
||||||
pub fn enable(&self) {
|
pub fn enable(&self) {
|
||||||
unsafe { CGEventTapEnable(self.mach_port.as_concrete_TypeRef(), true) }
|
unsafe { CGEventTapEnable(self.mach_port.as_concrete_TypeRef(), true) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for CGEventTap<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { CFMachPortInvalidate(self.mach_port.as_CFTypeRef() as *mut _) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreign_type! {
|
foreign_type! {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub unsafe type CGEvent {
|
pub unsafe type CGEvent {
|
||||||
|
|||||||
Reference in New Issue
Block a user