Skip to main content

core_graphics2/
event.rs

1use std::mem::ManuallyDrop;
2
3use core_foundation::{
4    base::{kCFAllocatorDefault, CFAllocatorRef, CFTypeID, TCFType},
5    data::{CFData, CFDataRef},
6    declare_TCFType, impl_CFTypeDescription, impl_TCFType,
7    mach_port::{CFMachPort, CFMachPortRef},
8};
9use libc::{c_double, c_ulong, c_void, pid_t};
10#[cfg(feature = "objc")]
11use objc2::encode::{Encoding, RefEncode};
12
13use crate::{error::CGError, geometry::CGPoint};
14pub use crate::{event_source::CGEventSource, event_types::*, remote_operation::CGKeyCode};
15
16extern "C" {
17    pub fn CGEventGetTypeID() -> CFTypeID;
18    pub fn CGEventCreate(source: CGEventSourceRef) -> CGEventRef;
19    pub fn CGEventCreateData(allocator: CFAllocatorRef, event: CGEventRef) -> CFDataRef;
20    pub fn CGEventCreateFromData(allocator: CFAllocatorRef, data: CFDataRef) -> CGEventRef;
21    pub fn CGEventCreateMouseEvent(
22        source: CGEventSourceRef,
23        mouseType: CGEventType,
24        mouseCursorPosition: CGPoint,
25        mouseButton: CGMouseButton,
26    ) -> CGEventRef;
27    pub fn CGEventCreateKeyboardEvent(source: CGEventSourceRef, virtualKey: CGKeyCode, keydown: bool) -> CGEventRef;
28    pub fn CGEventCreateScrollWheelEvent2(
29        source: CGEventSourceRef,
30        units: CGScrollEventUnit,
31        wheelCount: u32,
32        wheel1: i32,
33        wheel2: i32,
34        wheel3: i32,
35    ) -> CGEventRef;
36    pub fn CGEventCreateCopy(event: CGEventRef) -> CGEventRef;
37    pub fn CGEventCreateSourceFromEvent(event: CGEventRef) -> CGEventSourceRef;
38    pub fn CGEventSetSource(event: CGEventRef, source: CGEventSourceRef);
39    pub fn CGEventGetType(event: CGEventRef) -> CGEventType;
40    pub fn CGEventSetType(event: CGEventRef, type_: CGEventType);
41    pub fn CGEventGetTimestamp(event: CGEventRef) -> CGEventTimestamp;
42    pub fn CGEventSetTimestamp(event: CGEventRef, timestamp: CGEventTimestamp);
43    pub fn CGEventGetLocation(event: CGEventRef) -> CGPoint;
44    pub fn CGEventGetUnflippedLocation(event: CGEventRef) -> CGPoint;
45    pub fn CGEventSetLocation(event: CGEventRef, location: CGPoint);
46    pub fn CGEventGetFlags(event: CGEventRef) -> CGEventFlags;
47    pub fn CGEventSetFlags(event: CGEventRef, flags: CGEventFlags);
48    pub fn CGEventKeyboardGetUnicodeString(
49        event: CGEventRef,
50        maxStringLength: c_ulong,
51        actualStringLength: *mut c_ulong,
52        unicodeString: *mut u16,
53    ) -> bool;
54    pub fn CGEventKeyboardSetUnicodeString(event: CGEventRef, stringLength: c_ulong, unicodeString: *const u16);
55    pub fn CGEventGetIntegerValueField(event: CGEventRef, field: CGEventField) -> i64;
56    pub fn CGEventSetIntegerValueField(event: CGEventRef, field: CGEventField, value: i64);
57    pub fn CGEventGetDoubleValueField(event: CGEventRef, field: CGEventField) -> c_double;
58    pub fn CGEventSetDoubleValueField(event: CGEventRef, field: CGEventField, value: c_double);
59
60    pub fn CGEventTapCreate(
61        tap: CGEventTapLocation,
62        place: CGEventTapPlacement,
63        options: CGEventTapOptions,
64        eventsOfInterest: CGEventMask,
65        callback: CGEventTapCallBack,
66        userInfo: *mut c_void,
67    ) -> CFMachPortRef;
68    pub fn CGEventTapCreateForPSN(
69        processSerialNumber: *const c_void,
70        place: CGEventTapPlacement,
71        options: CGEventTapOptions,
72        eventsOfInterest: CGEventMask,
73        callback: CGEventTapCallBack,
74        userInfo: *mut c_void,
75    ) -> CFMachPortRef;
76    pub fn CGEventTapCreateForPid(
77        pid: pid_t,
78        place: CGEventTapPlacement,
79        options: CGEventTapOptions,
80        eventsOfInterest: CGEventMask,
81        callback: CGEventTapCallBack,
82        userInfo: *mut c_void,
83    ) -> CFMachPortRef;
84    pub fn CGEventTapEnable(tap: CFMachPortRef, enable: bool);
85    pub fn CGEventTapIsEnabled(tap: CFMachPortRef) -> bool;
86    pub fn CGEventTapPostEvent(proxy: CGEventTapProxy, event: CGEventRef);
87
88    pub fn CGEventPost(tap: CGEventTapLocation, event: CGEventRef);
89    pub fn CGEventPostToPSN(processSerialNumber: *mut c_void, event: CGEventRef);
90    pub fn CGEventPostToPid(pid: pid_t, event: CGEventRef);
91    pub fn CGGetEventTapList(maxNumberOfTaps: u32, tapList: *mut CGEventTapInformation, eventTapCount: *mut u32) -> CGError;
92    pub fn CGPreflightListenEventAccess() -> bool;
93    pub fn CGRequestListenEventAccess() -> bool;
94    pub fn CGPreflightPostEventAccess() -> bool;
95    pub fn CGRequestPostEventAccess() -> bool;
96}
97
98#[macro_export]
99macro_rules! CGEventMaskBit {
100    ($event_type:expr) => {
101        1 << $event_type as CGEventMask
102    };
103}
104
105pub type CGEventTapCallbackBox<'a> = Box<dyn FnMut(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'a>;
106
107pub struct CGEventTap<'a> {
108    pub mach_port: CFMachPort,
109    pub callback: Box<dyn FnMut(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'a>,
110}
111
112impl<'a> CGEventTap<'a> {
113    pub fn new<F>(
114        tap: CGEventTapLocation,
115        place: CGEventTapPlacement,
116        options: CGEventTapOptions,
117        events_of_interest: Vec<CGEventType>,
118        closure: F,
119    ) -> Result<CGEventTap<'a>, ()>
120    where
121        F: FnMut(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'a,
122    {
123        let event_mask: CGEventMask = events_of_interest.iter().fold(CGEventType::Null as CGEventMask, |mask, &etype| mask | CGEventMaskBit!(etype));
124        let cb = Box::into_raw(Box::new(Box::new(closure) as CGEventTapCallbackBox));
125        unsafe {
126            let event_tap = CGEventTapCreate(tap, place, options, event_mask, callback, cb as *mut c_void);
127            if event_tap.is_null() {
128                let _ = Box::from_raw(cb);
129                return Err(());
130            } else {
131                return Ok(Self {
132                    mach_port: CFMachPort::wrap_under_create_rule(event_tap),
133                    callback: Box::from_raw(cb),
134                });
135            }
136        }
137
138        extern "C" fn callback(proxy: CGEventTapProxy, etype: CGEventType, event: CGEventRef, user_info: *mut c_void) -> CGEventRef {
139            unsafe {
140                let callback = user_info as *mut CGEventTapCallbackBox;
141                let event = CGEvent::wrap_under_get_rule(event);
142                let new_event = (*callback)(proxy, etype, &event);
143                let event = new_event.unwrap_or(event);
144                ManuallyDrop::new(event).as_concrete_TypeRef()
145            }
146        }
147    }
148
149    pub fn enable(&self, enable: bool) {
150        unsafe { CGEventTapEnable(self.mach_port.as_concrete_TypeRef(), enable) }
151    }
152
153    pub fn is_enabled(&self) -> bool {
154        unsafe { CGEventTapIsEnabled(self.mach_port.as_concrete_TypeRef()) }
155    }
156}
157
158declare_TCFType!(CGEvent, CGEventRef);
159impl_TCFType!(CGEvent, CGEventRef, CGEventGetTypeID);
160impl_CFTypeDescription!(CGEvent);
161
162impl CGEvent {
163    pub fn new(source: CGEventSource) -> Option<CGEvent> {
164        unsafe {
165            let event = CGEventCreate(source.as_concrete_TypeRef());
166            if event.is_null() {
167                None
168            } else {
169                Some(TCFType::wrap_under_create_rule(event))
170            }
171        }
172    }
173
174    pub fn new_data(&self) -> Option<CFData> {
175        unsafe {
176            let data = CGEventCreateData(kCFAllocatorDefault, self.as_concrete_TypeRef());
177            if data.is_null() {
178                None
179            } else {
180                Some(TCFType::wrap_under_create_rule(data))
181            }
182        }
183    }
184
185    pub fn new_mouse_event(source: CGEventSource, mouse_type: CGEventType, cursor_position: CGPoint, button: CGMouseButton) -> Option<CGEvent> {
186        unsafe {
187            let event = CGEventCreateMouseEvent(source.as_concrete_TypeRef(), mouse_type, cursor_position, button);
188            if event.is_null() {
189                None
190            } else {
191                Some(TCFType::wrap_under_create_rule(event))
192            }
193        }
194    }
195
196    pub fn new_keyboard_event(source: CGEventSource, keycode: CGKeyCode, keydown: bool) -> Option<CGEvent> {
197        unsafe {
198            let event = CGEventCreateKeyboardEvent(source.as_concrete_TypeRef(), keycode, keydown);
199            if event.is_null() {
200                None
201            } else {
202                Some(TCFType::wrap_under_create_rule(event))
203            }
204        }
205    }
206
207    pub fn new_scroll_event(
208        source: CGEventSource,
209        units: CGScrollEventUnit,
210        wheel_count: u32,
211        wheel1: i32,
212        wheel2: i32,
213        wheel3: i32,
214    ) -> Option<CGEvent> {
215        unsafe {
216            let event = CGEventCreateScrollWheelEvent2(source.as_concrete_TypeRef(), units, wheel_count, wheel1, wheel2, wheel3);
217            if event.is_null() {
218                None
219            } else {
220                Some(TCFType::wrap_under_create_rule(event))
221            }
222        }
223    }
224
225    pub fn new_copy(&self) -> Option<CGEvent> {
226        unsafe {
227            let event = CGEventCreateCopy(self.as_concrete_TypeRef());
228            if event.is_null() {
229                None
230            } else {
231                Some(TCFType::wrap_under_create_rule(event))
232            }
233        }
234    }
235
236    pub fn new_source(&self) -> Option<CGEventSource> {
237        unsafe {
238            let source = CGEventCreateSourceFromEvent(self.as_concrete_TypeRef());
239            if source.is_null() {
240                None
241            } else {
242                Some(TCFType::wrap_under_create_rule(source))
243            }
244        }
245    }
246
247    pub fn from_data(data: CFData) -> Option<CGEvent> {
248        unsafe {
249            let event = CGEventCreateFromData(kCFAllocatorDefault, data.as_concrete_TypeRef());
250            if event.is_null() {
251                None
252            } else {
253                Some(TCFType::wrap_under_create_rule(event))
254            }
255        }
256    }
257
258    pub fn set_source(&self, source: CGEventSource) {
259        unsafe {
260            CGEventSetSource(self.as_concrete_TypeRef(), source.as_concrete_TypeRef());
261        }
262    }
263
264    pub fn get_type(&self) -> CGEventType {
265        unsafe { CGEventGetType(self.as_concrete_TypeRef()) }
266    }
267
268    pub fn set_type(&self, event_type: CGEventType) {
269        unsafe {
270            CGEventSetType(self.as_concrete_TypeRef(), event_type);
271        }
272    }
273
274    pub fn get_timestamp(&self) -> CGEventTimestamp {
275        unsafe { CGEventGetTimestamp(self.as_concrete_TypeRef()) }
276    }
277
278    pub fn set_timestamp(&self, timestamp: CGEventTimestamp) {
279        unsafe {
280            CGEventSetTimestamp(self.as_concrete_TypeRef(), timestamp);
281        }
282    }
283
284    pub fn get_location(&self) -> CGPoint {
285        unsafe { CGEventGetLocation(self.as_concrete_TypeRef()) }
286    }
287
288    pub fn get_unflipped_location(&self) -> CGPoint {
289        unsafe { CGEventGetUnflippedLocation(self.as_concrete_TypeRef()) }
290    }
291
292    pub fn set_location(&self, location: CGPoint) {
293        unsafe {
294            CGEventSetLocation(self.as_concrete_TypeRef(), location);
295        }
296    }
297
298    pub fn get_flags(&self) -> CGEventFlags {
299        unsafe { CGEventGetFlags(self.as_concrete_TypeRef()) }
300    }
301
302    pub fn set_flags(&self, flags: CGEventFlags) {
303        unsafe {
304            CGEventSetFlags(self.as_concrete_TypeRef(), flags);
305        }
306    }
307
308    pub fn get_integer_value_field(&self, field: CGEventField) -> i64 {
309        unsafe { CGEventGetIntegerValueField(self.as_concrete_TypeRef(), field) }
310    }
311
312    pub fn set_integer_value_field(&self, field: CGEventField, value: i64) {
313        unsafe { CGEventSetIntegerValueField(self.as_concrete_TypeRef(), field, value) }
314    }
315
316    pub fn get_double_value_field(&self, field: CGEventField) -> f64 {
317        unsafe { CGEventGetDoubleValueField(self.as_concrete_TypeRef(), field) }
318    }
319
320    pub fn set_double_value_field(&self, field: CGEventField, value: f64) {
321        unsafe { CGEventSetDoubleValueField(self.as_concrete_TypeRef(), field, value) }
322    }
323
324    pub fn set_string(&self, string: &str) {
325        let utf16: Vec<u16> = string.encode_utf16().collect();
326        unsafe {
327            CGEventKeyboardSetUnicodeString(self.as_concrete_TypeRef(), utf16.len() as c_ulong, utf16.as_ptr());
328        }
329    }
330
331    pub fn post(&self, tap: CGEventTapLocation) {
332        unsafe {
333            CGEventPost(tap, self.as_concrete_TypeRef());
334        }
335    }
336
337    pub fn post_to_pid(&self, pid: pid_t) {
338        unsafe {
339            CGEventPostToPid(pid, self.as_concrete_TypeRef());
340        }
341    }
342
343    pub fn post_from_tap(&self, proxy: CGEventTapProxy) {
344        unsafe {
345            CGEventTapPostEvent(proxy, self.as_concrete_TypeRef());
346        }
347    }
348}
349
350#[cfg(feature = "objc")]
351unsafe impl RefEncode for __CGEvent {
352    const ENCODING_REF: Encoding = Encoding::Pointer(&Encoding::Struct("CGEvent", &[]));
353}