@mukea/uiohook-napi 1.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/binding.gyp +85 -0
  4. package/dist/index.d.ts +194 -0
  5. package/dist/index.js +206 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/prebuild-test-noop.d.ts +0 -0
  8. package/dist/prebuild-test-noop.js +3 -0
  9. package/dist/prebuild-test-noop.js.map +1 -0
  10. package/libuiohook/include/uiohook.h +457 -0
  11. package/libuiohook/src/darwin/input_helper.c +535 -0
  12. package/libuiohook/src/darwin/input_helper.h +203 -0
  13. package/libuiohook/src/darwin/input_hook.c +1436 -0
  14. package/libuiohook/src/darwin/post_event.c +303 -0
  15. package/libuiohook/src/darwin/system_properties.c +479 -0
  16. package/libuiohook/src/logger.c +40 -0
  17. package/libuiohook/src/logger.h +32 -0
  18. package/libuiohook/src/windows/input_helper.c +913 -0
  19. package/libuiohook/src/windows/input_helper.h +146 -0
  20. package/libuiohook/src/windows/input_hook.c +722 -0
  21. package/libuiohook/src/windows/post_event.c +248 -0
  22. package/libuiohook/src/windows/system_properties.c +231 -0
  23. package/libuiohook/src/x11/input_helper.c +1846 -0
  24. package/libuiohook/src/x11/input_helper.h +108 -0
  25. package/libuiohook/src/x11/input_hook.c +1116 -0
  26. package/libuiohook/src/x11/post_event.c +427 -0
  27. package/libuiohook/src/x11/system_properties.c +494 -0
  28. package/package.json +60 -0
  29. package/prebuilds/darwin/darwin-arm64/@mukea+uiohook-napi.node +0 -0
  30. package/prebuilds/darwin/darwin-x64/@mukea+uiohook-napi.node +0 -0
  31. package/prebuilds/linux/linux-arm64/@mukea+uiohook-napi.node +0 -0
  32. package/prebuilds/linux/linux-x64/@mukea+uiohook-napi.node +0 -0
  33. package/prebuilds/windows/win32-x64/@mukea+uiohook-napi.node +0 -0
  34. package/src/lib/addon.c +337 -0
  35. package/src/lib/napi_helpers.c +51 -0
  36. package/src/lib/napi_helpers.h +53 -0
  37. package/src/lib/uiohook_worker.c +200 -0
  38. package/src/lib/uiohook_worker.h +12 -0
@@ -0,0 +1,1436 @@
1
+ /* libUIOHook: Cross-platform keyboard and mouse hooking from userland.
2
+ * Copyright (C) 2006-2023 Alexander Barker. All Rights Reserved.
3
+ * https://github.com/kwhat/libuiohook/
4
+ *
5
+ * libUIOHook is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Lesser General Public License as published
7
+ * by the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * libUIOHook is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public License
16
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ #include <dlfcn.h>
20
+ #include <mach/mach_time.h>
21
+
22
+ #ifdef USE_OBJC
23
+ #include <objc/objc.h>
24
+ #include <objc/objc-runtime.h>
25
+ #endif
26
+
27
+ #include <pthread.h>
28
+ #include <stdbool.h>
29
+ #include <sys/time.h>
30
+ #include <uiohook.h>
31
+
32
+ #include "input_helper.h"
33
+ #include "logger.h"
34
+
35
+
36
+ typedef struct _event_runloop_info {
37
+ CFMachPortRef port;
38
+ CFRunLoopSourceRef source;
39
+ CFRunLoopObserverRef observer;
40
+ } event_runloop_info;
41
+
42
+ #ifdef USE_OBJC
43
+ static id auto_release_pool;
44
+
45
+ typedef struct {
46
+ CGEventRef event;
47
+ UInt32 subtype;
48
+ UInt32 data1;
49
+ } TISEventMessage;
50
+ TISEventMessage *tis_event_message;
51
+ #endif
52
+
53
+ // Event runloop reference.
54
+ CFRunLoopRef event_loop;
55
+
56
+ // Modifiers for tracking key masks.
57
+ static uint16_t current_modifiers = 0x0000;
58
+
59
+ // Required to transport messages between the main runloop and our thread for
60
+ // Unicode lookups.
61
+ #define KEY_BUFFER_SIZE 4
62
+ typedef struct {
63
+ CGEventRef event;
64
+ UniChar buffer[KEY_BUFFER_SIZE];
65
+ UniCharCount length;
66
+ } TISKeycodeMessage;
67
+ TISKeycodeMessage *tis_keycode_message;
68
+
69
+ #if __MAC_OS_X_VERSION_MAX_ALLOWED <= 1050
70
+ typedef void* dispatch_queue_t;
71
+ #endif
72
+ static struct dispatch_queue_s *dispatch_main_queue_s;
73
+ static void (*dispatch_sync_f_f)(dispatch_queue_t, void *, void (*function)(void *));
74
+
75
+ #if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES)
76
+ typedef struct _main_runloop_info {
77
+ CFRunLoopSourceRef source;
78
+ CFRunLoopObserverRef observer;
79
+ } main_runloop_info;
80
+
81
+ main_runloop_info *main_runloop_keycode = NULL;
82
+
83
+ static pthread_cond_t main_runloop_cond = PTHREAD_COND_INITIALIZER;
84
+ static pthread_mutex_t main_runloop_mutex = PTHREAD_MUTEX_INITIALIZER;
85
+ #endif
86
+
87
+ // Click count globals.
88
+ static unsigned short click_count = 0;
89
+ static CGEventTimestamp click_time = 0;
90
+ static unsigned short int click_button = MOUSE_NOBUTTON;
91
+ static bool mouse_dragged = false;
92
+
93
+ // Structure for the current Unix epoch in milliseconds.
94
+ static struct timeval system_time;
95
+
96
+ // Virtual event pointer.
97
+ static uiohook_event event;
98
+
99
+ // Event dispatch callback.
100
+ static dispatcher_t dispatcher = NULL;
101
+
102
+ // We define the event_runloop_info as a static so that hook_event_proc can
103
+ // re-enable the tap when it gets disabled by a timeout
104
+ static event_runloop_info *hook = NULL;
105
+
106
+ UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) {
107
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n",
108
+ __FUNCTION__, __LINE__, dispatch_proc);
109
+
110
+ dispatcher = dispatch_proc;
111
+ }
112
+
113
+ // Send out an event if a dispatcher was set.
114
+ static inline void dispatch_event(uiohook_event *const event) {
115
+ if (dispatcher != NULL) {
116
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n",
117
+ __FUNCTION__, __LINE__, event->type);
118
+
119
+ dispatcher(event);
120
+ } else {
121
+ logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n",
122
+ __FUNCTION__, __LINE__);
123
+ }
124
+ }
125
+
126
+
127
+ // Set the native modifier mask for future events.
128
+ static inline void set_modifier_mask(uint16_t mask) {
129
+ current_modifiers |= mask;
130
+ }
131
+
132
+ // Unset the native modifier mask for future events.
133
+ static inline void unset_modifier_mask(uint16_t mask) {
134
+ current_modifiers &= ~mask;
135
+ }
136
+
137
+ // Get the current native modifier mask state.
138
+ static inline uint16_t get_modifiers() {
139
+ return current_modifiers;
140
+ }
141
+
142
+ // Initialize the modifier mask to the current modifiers.
143
+ static void initialize_modifiers() {
144
+ current_modifiers = 0x0000;
145
+
146
+ if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Shift)) {
147
+ set_modifier_mask(MASK_SHIFT_L);
148
+ }
149
+ if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightShift)) {
150
+ set_modifier_mask(MASK_SHIFT_R);
151
+ }
152
+
153
+ if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Control)) {
154
+ set_modifier_mask(MASK_CTRL_L);
155
+ }
156
+ if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightControl)) {
157
+ set_modifier_mask(MASK_CTRL_R);
158
+ }
159
+
160
+ if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Option)) {
161
+ set_modifier_mask(MASK_ALT_L);
162
+ }
163
+ if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightOption)) {
164
+ set_modifier_mask(MASK_ALT_R);
165
+ }
166
+
167
+ if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Command)) {
168
+ set_modifier_mask(MASK_META_L);
169
+ }
170
+ if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightCommand)) {
171
+ set_modifier_mask(MASK_META_R);
172
+ }
173
+
174
+ if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_LBUTTON)) {
175
+ set_modifier_mask(MASK_BUTTON1);
176
+ }
177
+ if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_RBUTTON)) {
178
+ set_modifier_mask(MASK_BUTTON2);
179
+ }
180
+
181
+ if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_MBUTTON)) {
182
+ set_modifier_mask(MASK_BUTTON3);
183
+ }
184
+ if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_XBUTTON1)) {
185
+ set_modifier_mask(MASK_BUTTON4);
186
+ }
187
+ if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_XBUTTON2)) {
188
+ set_modifier_mask(MASK_BUTTON5);
189
+ }
190
+
191
+ if (CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState) & kCGEventFlagMaskAlphaShift) {
192
+ set_modifier_mask(MASK_CAPS_LOCK);
193
+ }
194
+ // Best I can tell, OS X does not support Num or Scroll lock.
195
+ unset_modifier_mask(MASK_NUM_LOCK);
196
+ unset_modifier_mask(MASK_SCROLL_LOCK);
197
+ }
198
+
199
+
200
+ // Wrap keycode_to_unicode with some null checks.
201
+ static void keycode_to_lookup(void *info) {
202
+ TISKeycodeMessage *data = (TISKeycodeMessage *) info;
203
+
204
+ if (data != NULL && data->event != NULL) {
205
+ // Preform Unicode lookup.
206
+ data->length = keycode_to_unicode(data->event, data->buffer, KEY_BUFFER_SIZE);
207
+ }
208
+ }
209
+
210
+ static void hook_status_proc(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
211
+ uint64_t timestamp = mach_absolute_time();
212
+
213
+ switch (activity) {
214
+ case kCFRunLoopEntry:
215
+ // Initialize Native Input Functions.
216
+ load_input_helper();
217
+
218
+ // Populate the hook start event.
219
+ event.time = timestamp;
220
+ event.reserved = 0x00;
221
+
222
+ event.type = EVENT_HOOK_ENABLED;
223
+ event.mask = 0x00;
224
+
225
+ // Fire the hook start event.
226
+ dispatch_event(&event);
227
+ break;
228
+
229
+ case kCFRunLoopExit:
230
+ // Populate the hook stop event.
231
+ event.time = timestamp;
232
+ event.reserved = 0x00;
233
+
234
+ event.type = EVENT_HOOK_DISABLED;
235
+ event.mask = 0x00;
236
+
237
+ // Fire the hook stop event.
238
+ dispatch_event(&event);
239
+
240
+ // Deinitialize native input helper functions.
241
+ unload_input_helper();
242
+ break;
243
+
244
+ default:
245
+ logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled RunLoop activity! (%#X)\n",
246
+ __FUNCTION__, __LINE__, (unsigned int) activity);
247
+ break;
248
+ }
249
+ }
250
+
251
+ static inline void process_key_pressed(uint64_t timestamp, CGEventRef event_ref) {
252
+ UInt64 keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode);
253
+
254
+ // Populate key pressed event.
255
+ event.time = timestamp;
256
+ event.reserved = 0x00;
257
+
258
+ event.type = EVENT_KEY_PRESSED;
259
+ event.mask = get_modifiers();
260
+
261
+ event.data.keyboard.keycode = keycode_to_scancode(keycode);
262
+ event.data.keyboard.rawcode = keycode;
263
+ event.data.keyboard.keychar = CHAR_UNDEFINED;
264
+
265
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X pressed. (%#X)\n",
266
+ __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
267
+
268
+ // Fire key pressed event.
269
+ dispatch_event(&event);
270
+
271
+ // If the pressed event was not consumed...
272
+ if (event.reserved ^ 0x01) {
273
+ tis_keycode_message->event = event_ref;
274
+ tis_keycode_message->length = 0;
275
+ bool is_runloop_main = CFEqual(event_loop, CFRunLoopGetMain());
276
+
277
+ if (dispatch_sync_f_f != NULL && dispatch_main_queue_s != NULL && !is_runloop_main) {
278
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Using dispatch_sync_f for key typed events.\n",
279
+ __FUNCTION__, __LINE__);
280
+ (*dispatch_sync_f_f)(dispatch_main_queue_s, tis_keycode_message, &keycode_to_lookup);
281
+ }
282
+ #if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES)
283
+ else if (!is_runloop_main) {
284
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Using CFRunLoopWakeUp for key typed events.\n",
285
+ __FUNCTION__, __LINE__);
286
+
287
+ // Lock for code dealing with the main runloop.
288
+ pthread_mutex_lock(&main_runloop_mutex);
289
+
290
+ // Check to see if the main runloop is still running.
291
+ // TODO I would rather this be a check on hook_enable(),
292
+ // but it makes the usage complicated by requiring a separate
293
+ // thread for the main runloop and hook registration.
294
+ CFStringRef mode = CFRunLoopCopyCurrentMode(CFRunLoopGetMain());
295
+ if (mode != NULL) {
296
+ CFRelease(mode);
297
+
298
+ // Lookup the Unicode representation for this event.
299
+ //CFRunLoopSourceContext context = { .version = 0 };
300
+ //CFRunLoopSourceGetContext(main_runloop_keycode->source, &context);
301
+
302
+ // Get the run loop context info pointer.
303
+ //TISKeycodeMessage *info = (TISKeycodeMessage *) context.info;
304
+
305
+ // Set the event pointer.
306
+ //info->event = event_ref;
307
+
308
+
309
+ // Signal the custom source and wakeup the main runloop.
310
+ CFRunLoopSourceSignal(main_runloop_keycode->source);
311
+ CFRunLoopWakeUp(CFRunLoopGetMain());
312
+
313
+ // Wait for a lock while the main runloop processes they key typed event.
314
+ pthread_cond_wait(&main_runloop_cond, &main_runloop_mutex);
315
+ }
316
+ else {
317
+ logger(LOG_LEVEL_WARN, "%s [%u]: Failed to signal RunLoop main!\n",
318
+ __FUNCTION__, __LINE__);
319
+ }
320
+
321
+ // Unlock for code dealing with the main runloop.
322
+ pthread_mutex_unlock(&main_runloop_mutex);
323
+ }
324
+ #endif
325
+ else {
326
+ keycode_to_lookup(tis_keycode_message);
327
+ }
328
+
329
+ for (unsigned int i = 0; i < tis_keycode_message->length; i++) {
330
+ // Populate key typed event.
331
+ event.time = timestamp;
332
+ event.reserved = 0x00;
333
+
334
+ event.type = EVENT_KEY_TYPED;
335
+ event.mask = get_modifiers();
336
+
337
+ event.data.keyboard.keycode = VC_UNDEFINED;
338
+ event.data.keyboard.rawcode = keycode;
339
+ event.data.keyboard.keychar = tis_keycode_message->buffer[i];
340
+
341
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X typed. (%lc)\n",
342
+ __FUNCTION__, __LINE__, event.data.keyboard.keycode,
343
+ (wint_t) event.data.keyboard.keychar);
344
+
345
+ // Populate key typed event.
346
+ dispatch_event(&event);
347
+ }
348
+ }
349
+ }
350
+
351
+ static inline void process_key_released(uint64_t timestamp, CGEventRef event_ref) {
352
+ UInt64 keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode);
353
+
354
+ // Populate key released event.
355
+ event.time = timestamp;
356
+ event.reserved = 0x00;
357
+
358
+ event.type = EVENT_KEY_RELEASED;
359
+ event.mask = get_modifiers();
360
+
361
+ event.data.keyboard.keycode = keycode_to_scancode(keycode);
362
+ event.data.keyboard.rawcode = keycode;
363
+ event.data.keyboard.keychar = CHAR_UNDEFINED;
364
+
365
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X released. (%#X)\n",
366
+ __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
367
+
368
+ // Fire key released event.
369
+ dispatch_event(&event);
370
+ }
371
+
372
+ static inline void process_modifier_changed(uint64_t timestamp, CGEventRef event_ref) {
373
+ CGEventFlags event_mask = CGEventGetFlags(event_ref);
374
+ UInt64 keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode);
375
+
376
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Modifiers Changed for key %#X. (%#X)\n",
377
+ __FUNCTION__, __LINE__, (unsigned long) keycode, (unsigned int) event_mask);
378
+
379
+ /* Because Apple treats modifier keys differently than normal key
380
+ * events, any changes to the modifier keys will require a key state
381
+ * change to be fired manually.
382
+ *
383
+ * NOTE Left and right keyboard masks like NX_NEXTLSHIFTKEYMASK exist and
384
+ * appear to be in use on Darwin, however they are removed by comment or
385
+ * preprocessor with a note that reads "device-dependent (really?)." To
386
+ * ensure compatability, we will do this the verbose way.
387
+ *
388
+ * NOTE The masks for scroll and number lock are set in the key event.
389
+ */
390
+ if (keycode == kVK_Shift) {
391
+ if (event_mask & kCGEventFlagMaskShift) {
392
+ // Process as a key pressed event.
393
+ set_modifier_mask(MASK_SHIFT_L);
394
+ process_key_pressed(timestamp, event_ref);
395
+ } else {
396
+ // Process as a key released event.
397
+ unset_modifier_mask(MASK_SHIFT_L);
398
+ process_key_released(timestamp, event_ref);
399
+ }
400
+ } else if (keycode == kVK_Control) {
401
+ if (event_mask & kCGEventFlagMaskControl) {
402
+ // Process as a key pressed event.
403
+ set_modifier_mask(MASK_CTRL_L);
404
+ process_key_pressed(timestamp, event_ref);
405
+ } else {
406
+ // Process as a key released event.
407
+ unset_modifier_mask(MASK_CTRL_L);
408
+ process_key_released(timestamp, event_ref);
409
+ }
410
+ } else if (keycode == kVK_Command) {
411
+ if (event_mask & kCGEventFlagMaskCommand) {
412
+ // Process as a key pressed event.
413
+ set_modifier_mask(MASK_META_L);
414
+ process_key_pressed(timestamp, event_ref);
415
+ } else {
416
+ // Process as a key released event.
417
+ unset_modifier_mask(MASK_META_L);
418
+ process_key_released(timestamp, event_ref);
419
+ }
420
+ } else if (keycode == kVK_Option) {
421
+ if (event_mask & kCGEventFlagMaskAlternate) {
422
+ // Process as a key pressed event.
423
+ set_modifier_mask(MASK_ALT_L);
424
+ process_key_pressed(timestamp, event_ref);
425
+ } else {
426
+ // Process as a key released event.
427
+ unset_modifier_mask(MASK_ALT_L);
428
+ process_key_released(timestamp, event_ref);
429
+ }
430
+ } else if (keycode == kVK_RightShift) {
431
+ if (event_mask & kCGEventFlagMaskShift) {
432
+ // Process as a key pressed event.
433
+ set_modifier_mask(MASK_SHIFT_R);
434
+ process_key_pressed(timestamp, event_ref);
435
+ } else {
436
+ // Process as a key released event.
437
+ unset_modifier_mask(MASK_SHIFT_R);
438
+ process_key_released(timestamp, event_ref);
439
+ }
440
+ } else if (keycode == kVK_RightControl) {
441
+ if (event_mask & kCGEventFlagMaskControl) {
442
+ // Process as a key pressed event.
443
+ set_modifier_mask(MASK_CTRL_R);
444
+ process_key_pressed(timestamp, event_ref);
445
+ } else {
446
+ // Process as a key released event.
447
+ unset_modifier_mask(MASK_CTRL_R);
448
+ process_key_released(timestamp, event_ref);
449
+ }
450
+ } else if (keycode == kVK_RightCommand) {
451
+ if (event_mask & kCGEventFlagMaskCommand) {
452
+ // Process as a key pressed event.
453
+ set_modifier_mask(MASK_META_R);
454
+ process_key_pressed(timestamp, event_ref);
455
+ } else {
456
+ // Process as a key released event.
457
+ unset_modifier_mask(MASK_META_R);
458
+ process_key_released(timestamp, event_ref);
459
+ }
460
+ } else if (keycode == kVK_RightOption) {
461
+ if (event_mask & kCGEventFlagMaskAlternate) {
462
+ // Process as a key pressed event.
463
+ set_modifier_mask(MASK_ALT_R);
464
+ process_key_pressed(timestamp, event_ref);
465
+ } else {
466
+ // Process as a key released event.
467
+ unset_modifier_mask(MASK_ALT_R);
468
+ process_key_released(timestamp, event_ref);
469
+ }
470
+ } else if (keycode == kVK_CapsLock) {
471
+ if (current_modifiers & MASK_CAPS_LOCK) {
472
+ // Process as a key pressed event.
473
+ unset_modifier_mask(MASK_CAPS_LOCK);
474
+ // Key released handled by process_system_key
475
+ } else {
476
+ // Process as a key released event.
477
+ set_modifier_mask(MASK_CAPS_LOCK);
478
+ // Key pressed handled by process_system_key
479
+ }
480
+ }
481
+ }
482
+
483
+ #ifdef USE_OBJC
484
+ static void obcj_message(void *info) {
485
+ TISEventMessage *data = (TISEventMessage *) info;
486
+
487
+ if (data != NULL && data->event != NULL) {
488
+ // Contributed by Iván Munsuri Ibáñez <munsuri@gmail.com> and Alex <universailp@web.de>
489
+ id (*eventWithCGEvent)(id, SEL, CGEventRef) = (id (*)(id, SEL, CGEventRef)) objc_msgSend;
490
+ id event_data = eventWithCGEvent((id) objc_getClass("NSEvent"), sel_registerName("eventWithCGEvent:"), data->event);
491
+
492
+ UInt32 (*eventWithoutCGEvent)(id, SEL) = (UInt32 (*)(id, SEL)) objc_msgSend;
493
+ data->subtype = eventWithoutCGEvent(event_data, sel_registerName("subtype"));
494
+ data->data1 = eventWithoutCGEvent(event_data, sel_registerName("data1"));
495
+ }
496
+ }
497
+ #endif
498
+
499
+ /* These events are totally undocumented for the CGEvent type, but are required to grab media and caps-lock keys.
500
+ */
501
+ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) {
502
+ if (CGEventGetType(event_ref) == NX_SYSDEFINED) {
503
+ UInt32 subtype = 0;
504
+ UInt32 data1 = 0;
505
+
506
+ #ifdef USE_OBJC
507
+ bool is_runloop_main = CFEqual(event_loop, CFRunLoopGetMain());
508
+ tis_event_message->event = event_ref;
509
+ tis_event_message->subtype = 0;
510
+ tis_event_message->data1 = 0;
511
+
512
+ if (dispatch_sync_f_f != NULL && dispatch_main_queue_s != NULL && !is_runloop_main) {
513
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Using dispatch_sync_f for system key events.\n",
514
+ __FUNCTION__, __LINE__);
515
+
516
+ (*dispatch_sync_f_f)(dispatch_main_queue_s, tis_event_message, &obcj_message);
517
+ subtype = tis_event_message->subtype;
518
+ data1 = tis_event_message->data1;
519
+ } else if (is_runloop_main) {
520
+ obcj_message(tis_event_message);
521
+ subtype = tis_event_message->subtype;
522
+ data1 = tis_event_message->data1;
523
+ } else {
524
+ #endif
525
+ // If we are not using ObjC, the only way I've found to access CGEvent->subtype and CGEvent>data1 is to
526
+ // serialize the event and read the byte offsets. I am not sure why, but CGEventCreateData appears to use
527
+ // big-endian byte ordering even though all current apple architectures are little-endian.
528
+ CFDataRef data_ref = CGEventCreateData(kCFAllocatorDefault, event_ref);
529
+ if (data_ref == NULL) {
530
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for CGEventRef copy!\n",
531
+ __FUNCTION__, __LINE__);
532
+ return;
533
+ }
534
+
535
+ if (CFDataGetLength(data_ref) < 132)
536
+ {
537
+ CFRelease(data_ref);
538
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Insufficient CFData range size!\n",
539
+ __FUNCTION__, __LINE__);
540
+ return;
541
+ }
542
+
543
+ UInt8 *buffer = malloc(4);
544
+ if (buffer == NULL) {
545
+ CFRelease(data_ref);
546
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for CFData range buffer!\n",
547
+ __FUNCTION__, __LINE__);
548
+ return;
549
+ }
550
+
551
+ CFDataGetBytes(data_ref, CFRangeMake(120, 4), buffer);
552
+ subtype = CFSwapInt32BigToHost(*((UInt32 *) buffer));
553
+
554
+ CFDataGetBytes(data_ref, CFRangeMake(128, 4), buffer);
555
+ data1 = CFSwapInt32BigToHost(*((UInt32 *) buffer));
556
+
557
+ free(buffer);
558
+ CFRelease(data_ref);
559
+ #ifdef USE_OBJC
560
+ }
561
+ #endif
562
+
563
+ if (subtype == 8) {
564
+ int key_code = (data1 & 0xFFFF0000) >> 16;
565
+ int key_flags = (data1 & 0xFFFF);
566
+ int key_state = (key_flags & 0xFF00) >> 8;
567
+ bool key_down = (key_state & 0x1) == 0;
568
+
569
+ if (key_code == NX_KEYTYPE_CAPS_LOCK) {
570
+ // It doesn't appear like we can modify the event coming in, so we will fabricate a new event.
571
+ CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
572
+ CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_CapsLock, key_down);
573
+ CGEventSetFlags(ns_event, CGEventGetFlags(event_ref));
574
+
575
+ if (key_down) {
576
+ process_key_pressed(timestamp, ns_event);
577
+ } else {
578
+ process_key_released(timestamp, ns_event);
579
+ }
580
+
581
+ CFRelease(ns_event);
582
+ CFRelease(src);
583
+ } else if (key_code == NX_KEYTYPE_SOUND_UP) {
584
+ // It doesn't appear like we can modify the event coming in, so we will fabricate a new event.
585
+ CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
586
+ CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_VolumeUp, key_down);
587
+ CGEventSetFlags(ns_event, CGEventGetFlags(event_ref));
588
+
589
+ if (key_down) {
590
+ process_key_pressed(timestamp, ns_event);
591
+ } else {
592
+ process_key_released(timestamp, ns_event);
593
+ }
594
+
595
+ CFRelease(ns_event);
596
+ CFRelease(src);
597
+ } else if (key_code == NX_KEYTYPE_SOUND_DOWN) {
598
+ // It doesn't appear like we can modify the event coming in, so we will fabricate a new event.
599
+ CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
600
+ CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_VolumeDown, key_down);
601
+ CGEventSetFlags(ns_event, CGEventGetFlags(event_ref));
602
+
603
+ if (key_down) {
604
+ process_key_pressed(timestamp, ns_event);
605
+ } else {
606
+ process_key_released(timestamp, ns_event);
607
+ }
608
+
609
+ CFRelease(ns_event);
610
+ CFRelease(src);
611
+ } else if (key_code == NX_KEYTYPE_MUTE) {
612
+ // It doesn't appear like we can modify the event coming in, so we will fabricate a new event.
613
+ CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
614
+ CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_Mute, key_down);
615
+ CGEventSetFlags(ns_event, CGEventGetFlags(event_ref));
616
+
617
+ if (key_down) {
618
+ process_key_pressed(timestamp, ns_event);
619
+ } else {
620
+ process_key_released(timestamp, ns_event);
621
+ }
622
+
623
+ CFRelease(ns_event);
624
+ CFRelease(src);
625
+ } else if (key_code == NX_KEYTYPE_EJECT) {
626
+ // It doesn't appear like we can modify the event coming in, so we will fabricate a new event.
627
+ CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
628
+ CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_NX_Eject, key_down);
629
+ CGEventSetFlags(ns_event, CGEventGetFlags(event_ref));
630
+
631
+ if (key_down) {
632
+ process_key_pressed(timestamp, ns_event);
633
+ } else {
634
+ process_key_released(timestamp, ns_event);
635
+ }
636
+
637
+ CFRelease(ns_event);
638
+ CFRelease(src);
639
+ } else if (key_code == NX_KEYTYPE_PLAY) {
640
+ // It doesn't appear like we can modify the event coming in, so we will fabricate a new event.
641
+ CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
642
+ CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Play, key_down);
643
+ CGEventSetFlags(ns_event, CGEventGetFlags(event_ref));
644
+
645
+ if (key_down) {
646
+ process_key_pressed(timestamp, ns_event);
647
+ } else {
648
+ process_key_released(timestamp, ns_event);
649
+ }
650
+
651
+ CFRelease(ns_event);
652
+ CFRelease(src);
653
+ } else if (key_code == NX_KEYTYPE_FAST) {
654
+ // It doesn't appear like we can modify the event coming in, so we will fabricate a new event.
655
+ CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
656
+ CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Next, key_down);
657
+ CGEventSetFlags(ns_event, CGEventGetFlags(event_ref));
658
+
659
+ if (key_down) {
660
+ process_key_pressed(timestamp, ns_event);
661
+ } else {
662
+ process_key_released(timestamp, ns_event);
663
+ }
664
+
665
+ CFRelease(ns_event);
666
+ CFRelease(src);
667
+ } else if (key_code == NX_KEYTYPE_REWIND) {
668
+ // It doesn't appear like we can modify the event coming in, so we will fabricate a new event.
669
+ CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
670
+ CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Previous, key_down);
671
+ CGEventSetFlags(ns_event, CGEventGetFlags(event_ref));
672
+
673
+ if (key_down) {
674
+ process_key_pressed(timestamp, ns_event);
675
+ } else {
676
+ process_key_released(timestamp, ns_event);
677
+ }
678
+
679
+ CFRelease(ns_event);
680
+ CFRelease(src);
681
+ }
682
+ }
683
+ }
684
+ }
685
+
686
+
687
+ static inline void process_button_pressed(uint64_t timestamp, CGEventRef event_ref, uint16_t button) {
688
+ // Track the number of clicks.
689
+ if (button == click_button && (long int) (timestamp - click_time) / 1000000 <= hook_get_multi_click_time()) {
690
+ if (click_count < USHRT_MAX) {
691
+ click_count++;
692
+ }
693
+ else {
694
+ logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n",
695
+ __FUNCTION__, __LINE__);
696
+ }
697
+ } else {
698
+ // Reset the click count.
699
+ click_count = 1;
700
+
701
+ // Set the previous button.
702
+ click_button = button;
703
+ }
704
+
705
+ // Save this events time to calculate the click_count.
706
+ click_time = timestamp;
707
+
708
+ CGPoint event_point = CGEventGetLocation(event_ref);
709
+
710
+ // Populate mouse pressed event.
711
+ event.time = timestamp;
712
+ event.reserved = 0x00;
713
+
714
+ event.type = EVENT_MOUSE_PRESSED;
715
+ event.mask = get_modifiers();
716
+
717
+ event.data.mouse.button = button;
718
+ event.data.mouse.clicks = click_count;
719
+ event.data.mouse.x = event_point.x;
720
+ event.data.mouse.y = event_point.y;
721
+
722
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n",
723
+ __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
724
+ event.data.mouse.x, event.data.mouse.y);
725
+
726
+ // Fire mouse pressed event.
727
+ dispatch_event(&event);
728
+ }
729
+
730
+ static inline void process_button_released(uint64_t timestamp, CGEventRef event_ref, uint16_t button) {
731
+ CGPoint event_point = CGEventGetLocation(event_ref);
732
+
733
+ // Populate mouse released event.
734
+ event.time = timestamp;
735
+ event.reserved = 0x00;
736
+
737
+ event.type = EVENT_MOUSE_RELEASED;
738
+ event.mask = get_modifiers();
739
+
740
+ event.data.mouse.button = button;
741
+ event.data.mouse.clicks = click_count;
742
+ event.data.mouse.x = event_point.x;
743
+ event.data.mouse.y = event_point.y;
744
+
745
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u released %u time(s). (%u, %u)\n",
746
+ __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
747
+ event.data.mouse.x, event.data.mouse.y);
748
+
749
+ // Fire mouse released event.
750
+ dispatch_event(&event);
751
+
752
+ // If the pressed event was not consumed...
753
+ if (event.reserved ^ 0x01 && mouse_dragged != true) {
754
+ // Populate mouse clicked event.
755
+ event.time = timestamp;
756
+ event.reserved = 0x00;
757
+
758
+ event.type = EVENT_MOUSE_CLICKED;
759
+ event.mask = get_modifiers();
760
+
761
+ event.data.mouse.button = button;
762
+ event.data.mouse.clicks = click_count;
763
+ event.data.mouse.x = event_point.x;
764
+ event.data.mouse.y = event_point.y;
765
+
766
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n",
767
+ __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
768
+ event.data.mouse.x, event.data.mouse.y);
769
+
770
+ // Fire mouse clicked event.
771
+ dispatch_event(&event);
772
+ }
773
+
774
+ // Reset the number of clicks.
775
+ if ((long int) (timestamp - click_time) / 1000000 > hook_get_multi_click_time()) {
776
+ // Reset the click count.
777
+ click_count = 0;
778
+ }
779
+ }
780
+
781
+ static inline void process_mouse_moved(uint64_t timestamp, CGEventRef event_ref) {
782
+ // Reset the click count.
783
+ if (click_count != 0 && (long int) (timestamp - click_time) / 1000000 > hook_get_multi_click_time()) {
784
+ click_count = 0;
785
+ }
786
+
787
+ CGPoint event_point = CGEventGetLocation(event_ref);
788
+
789
+ // Populate mouse motion event.
790
+ event.time = timestamp;
791
+ event.reserved = 0x00;
792
+
793
+ if (mouse_dragged) {
794
+ event.type = EVENT_MOUSE_DRAGGED;
795
+ }
796
+ else {
797
+ event.type = EVENT_MOUSE_MOVED;
798
+ }
799
+ event.mask = get_modifiers();
800
+
801
+ event.data.mouse.button = MOUSE_NOBUTTON;
802
+ event.data.mouse.clicks = click_count;
803
+ event.data.mouse.x = event_point.x;
804
+ event.data.mouse.y = event_point.y;
805
+
806
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse %s to %u, %u.\n",
807
+ __FUNCTION__, __LINE__, mouse_dragged ? "dragged" : "moved",
808
+ event.data.mouse.x, event.data.mouse.y);
809
+
810
+ // Fire mouse motion event.
811
+ dispatch_event(&event);
812
+ }
813
+
814
+ static inline void process_mouse_wheel(uint64_t timestamp, CGEventRef event_ref) {
815
+ // Reset the click count and previous button.
816
+ click_count = 1;
817
+ click_button = MOUSE_NOBUTTON;
818
+
819
+ // Check to see what axis was rotated, we only care about axis 1 for vertical rotation.
820
+ // TODO Implement horizontal scrolling by examining axis 2.
821
+ // NOTE kCGScrollWheelEventDeltaAxis3 is currently unused.
822
+ if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0
823
+ || CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) {
824
+ CGPoint event_point = CGEventGetLocation(event_ref);
825
+
826
+ // Populate mouse wheel event.
827
+ event.time = timestamp;
828
+ event.reserved = 0x00;
829
+
830
+ event.type = EVENT_MOUSE_WHEEL;
831
+ event.mask = get_modifiers();
832
+
833
+ event.data.wheel.clicks = click_count;
834
+ event.data.wheel.x = event_point.x;
835
+ event.data.wheel.y = event_point.y;
836
+
837
+ // TODO Figure out if kCGScrollWheelEventDeltaAxis2 causes mouse events with zero rotation.
838
+ if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventIsContinuous) == 0) {
839
+ // Scrolling data is line-based.
840
+ event.data.wheel.type = WHEEL_BLOCK_SCROLL;
841
+ } else {
842
+ // Scrolling data is pixel-based.
843
+ event.data.wheel.type = WHEEL_UNIT_SCROLL;
844
+ }
845
+
846
+ // TODO The result of kCGScrollWheelEventIsContinuous may effect this value.
847
+ // Calculate the amount based on the Point Delta / Event Delta. Integer sign should always be homogeneous resulting in a positive result.
848
+ // NOTE kCGScrollWheelEventFixedPtDeltaAxis1 a floating point value (+0.1/-0.1) that takes acceleration into account.
849
+ // NOTE kCGScrollWheelEventPointDeltaAxis1 will not build on OS X < 10.5
850
+
851
+ if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) {
852
+ event.data.wheel.amount = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis1) / CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1);
853
+
854
+ // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000).
855
+ event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) * -1;
856
+
857
+ } else if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) {
858
+ event.data.wheel.amount = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis2) / CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2);
859
+
860
+ // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000).
861
+ event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) * -1;
862
+ } else {
863
+ //Fail Silently if a 3rd axis gets added without changing this section of code.
864
+ event.data.wheel.amount = 0;
865
+ event.data.wheel.rotation = 0;
866
+ }
867
+
868
+
869
+ if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) {
870
+ // Wheel Rotated Up or Down.
871
+ event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION;
872
+ } else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight
873
+ // Wheel Rotated Left or Right.
874
+ event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION;
875
+ }
876
+
877
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n",
878
+ __FUNCTION__, __LINE__, event.data.wheel.type,
879
+ event.data.wheel.amount * event.data.wheel.rotation,
880
+ event.data.wheel.direction,
881
+ event.data.wheel.x, event.data.wheel.y);
882
+
883
+ // Fire mouse wheel event.
884
+ dispatch_event(&event);
885
+ }
886
+ }
887
+
888
+ CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventRef event_ref, void *refcon) {
889
+ // Get the local system time in UTC.
890
+ gettimeofday(&system_time, NULL);
891
+
892
+ // Grab the native event timestamp for use later..
893
+ uint64_t timestamp = (uint64_t) CGEventGetTimestamp(event_ref);
894
+
895
+ // Get the event class.
896
+ switch (type) {
897
+ case kCGEventKeyDown:
898
+ process_key_pressed(timestamp, event_ref);
899
+ break;
900
+
901
+ case kCGEventKeyUp:
902
+ process_key_released(timestamp, event_ref);
903
+ break;
904
+
905
+ case kCGEventFlagsChanged:
906
+ process_modifier_changed(timestamp, event_ref);
907
+ break;
908
+
909
+ case NX_SYSDEFINED:
910
+ process_system_key(timestamp, event_ref);
911
+ break;
912
+
913
+ case kCGEventLeftMouseDown:
914
+ set_modifier_mask(MASK_BUTTON1);
915
+ process_button_pressed(timestamp, event_ref, MOUSE_BUTTON1);
916
+ break;
917
+
918
+ case kCGEventRightMouseDown:
919
+ set_modifier_mask(MASK_BUTTON2);
920
+ process_button_pressed(timestamp, event_ref, MOUSE_BUTTON2);
921
+ break;
922
+
923
+ case kCGEventOtherMouseDown:
924
+ // Extra mouse buttons.
925
+ if (CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) < UINT16_MAX) {
926
+ uint16_t button = (uint16_t) CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) + 1;
927
+
928
+ // Add support for mouse 4 & 5.
929
+ if (button == 4) {
930
+ set_modifier_mask(MOUSE_BUTTON4);
931
+ } else if (button == 5) {
932
+ set_modifier_mask(MOUSE_BUTTON5);
933
+ }
934
+
935
+ process_button_pressed(timestamp, event_ref, button);
936
+ }
937
+ break;
938
+
939
+ case kCGEventLeftMouseUp:
940
+ unset_modifier_mask(MASK_BUTTON1);
941
+ process_button_released(timestamp, event_ref, MOUSE_BUTTON1);
942
+ break;
943
+
944
+ case kCGEventRightMouseUp:
945
+ unset_modifier_mask(MASK_BUTTON2);
946
+ process_button_released(timestamp, event_ref, MOUSE_BUTTON2);
947
+ break;
948
+
949
+ case kCGEventOtherMouseUp:
950
+ // Extra mouse buttons.
951
+ if (CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) < UINT16_MAX) {
952
+ uint16_t button = (uint16_t) CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) + 1;
953
+
954
+ // Add support for mouse 4 & 5.
955
+ if (button == 4) {
956
+ unset_modifier_mask(MOUSE_BUTTON4);
957
+ } else if (button == 5) {
958
+ unset_modifier_mask(MOUSE_BUTTON5);
959
+ }
960
+
961
+ process_button_pressed(timestamp, event_ref, button);
962
+ }
963
+ break;
964
+
965
+
966
+ case kCGEventLeftMouseDragged:
967
+ case kCGEventRightMouseDragged:
968
+ case kCGEventOtherMouseDragged:
969
+ // FIXME The drag flag is confusing. Use prev x,y to determine click.
970
+ // Set the mouse dragged flag.
971
+ mouse_dragged = true;
972
+ process_mouse_moved(timestamp, event_ref);
973
+ break;
974
+
975
+ case kCGEventMouseMoved:
976
+ // Set the mouse dragged flag.
977
+ mouse_dragged = false;
978
+ process_mouse_moved(timestamp, event_ref);
979
+ break;
980
+
981
+
982
+ case kCGEventScrollWheel:
983
+ process_mouse_wheel(timestamp, event_ref);
984
+ break;
985
+
986
+ default:
987
+ // Check for an old OS X bug where the tap seems to timeout for no reason.
988
+ // See: http://stackoverflow.com/questions/2969110/cgeventtapcreate-breaks-down-mysteriously-with-key-down-events#2971217
989
+ if (type == (CGEventType) kCGEventTapDisabledByTimeout) {
990
+ logger(LOG_LEVEL_WARN, "%s [%u]: CGEventTap timeout!\n",
991
+ __FUNCTION__, __LINE__);
992
+
993
+ // We need to re-enable the tap
994
+ if (hook->port) {
995
+ CGEventTapEnable(hook->port, true);
996
+ }
997
+ } else {
998
+ // In theory this *should* never execute.
999
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Darwin event: %#X.\n",
1000
+ __FUNCTION__, __LINE__, (unsigned int) type);
1001
+ }
1002
+ break;
1003
+ }
1004
+
1005
+ CGEventRef result_ref = NULL;
1006
+ if (event.reserved ^ 0x01) {
1007
+ result_ref = event_ref;
1008
+ } else {
1009
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%#X) (%#p)\n",
1010
+ __FUNCTION__, __LINE__, type, event_ref);
1011
+ }
1012
+
1013
+ return result_ref;
1014
+ }
1015
+
1016
+
1017
+ #if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES)
1018
+ void main_runloop_status_proc(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
1019
+ switch (activity) {
1020
+ case kCFRunLoopExit:
1021
+ // Acquire a lock on the msg_port and signal that anyone waiting should continue.
1022
+ pthread_mutex_lock(&main_runloop_mutex);
1023
+ pthread_cond_broadcast(&main_runloop_cond);
1024
+ pthread_mutex_unlock(&main_runloop_mutex);
1025
+ break;
1026
+ }
1027
+ }
1028
+
1029
+ // Runloop to execute KeyCodeToString on the "Main" runloop due to an undocumented thread safety requirement.
1030
+ static void main_runloop_keycode_proc(void *info) {
1031
+ // Lock the msg_port mutex as we enter the main runloop.
1032
+ pthread_mutex_lock(&main_runloop_mutex);
1033
+
1034
+ keycode_to_lookup(info);
1035
+
1036
+ // Unlock the msg_port mutex to signal to the hook_thread that we have
1037
+ // finished on the main runloop.
1038
+ pthread_cond_broadcast(&main_runloop_cond);
1039
+ pthread_mutex_unlock(&main_runloop_mutex);
1040
+ }
1041
+
1042
+ static int create_main_runloop_info(main_runloop_info **main, CFRunLoopSourceContext *context) {
1043
+ if (*main != NULL) {
1044
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Expected unallocated main_runloop_info pointer!\n",
1045
+ __FUNCTION__, __LINE__);
1046
+
1047
+ return UIOHOOK_FAILURE;
1048
+ }
1049
+
1050
+ // Try and allocate memory for event_runloop_info.
1051
+ *main = malloc(sizeof(main_runloop_info));
1052
+ if (*main == NULL) {
1053
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for main_runloop_info structure!\n",
1054
+ __FUNCTION__, __LINE__);
1055
+
1056
+ return UIOHOOK_ERROR_OUT_OF_MEMORY;
1057
+ }
1058
+
1059
+ // Create a runloop observer for the main runloop.
1060
+ (*main)->observer = CFRunLoopObserverCreate(
1061
+ kCFAllocatorDefault,
1062
+ kCFRunLoopExit, //kCFRunLoopEntry | kCFRunLoopExit, //kCFRunLoopAllActivities,
1063
+ true,
1064
+ 0,
1065
+ main_runloop_status_proc,
1066
+ NULL
1067
+ );
1068
+ if ((*main)->observer == NULL) {
1069
+ logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n",
1070
+ __FUNCTION__, __LINE__);
1071
+
1072
+ return UIOHOOK_ERROR_CREATE_OBSERVER;
1073
+ } else {
1074
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopObserverCreate success!\n",
1075
+ __FUNCTION__, __LINE__);
1076
+ }
1077
+
1078
+ (*main)->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, context);
1079
+
1080
+ if ((*main)->source == NULL) {
1081
+ logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopSourceCreate failure!\n",
1082
+ __FUNCTION__, __LINE__);
1083
+
1084
+ return UIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE;
1085
+ } else {
1086
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopSourceCreate success!\n",
1087
+ __FUNCTION__, __LINE__);
1088
+ }
1089
+
1090
+ // FIXME Check for null ?
1091
+ CFRunLoopRef main_loop = CFRunLoopGetMain();
1092
+
1093
+ pthread_mutex_lock(&main_runloop_mutex);
1094
+
1095
+ CFRunLoopAddSource(main_loop, (*main)->source, kCFRunLoopDefaultMode);
1096
+ CFRunLoopAddObserver(main_loop, (*main)->observer, kCFRunLoopDefaultMode);
1097
+
1098
+ pthread_mutex_unlock(&main_runloop_mutex);
1099
+
1100
+ return UIOHOOK_SUCCESS;
1101
+ }
1102
+
1103
+ static void destroy_main_runloop_info(main_runloop_info **main) {
1104
+ if (*main != NULL) {
1105
+ CFRunLoopRef main_loop = CFRunLoopGetMain();
1106
+
1107
+ if ((*main)->observer != NULL) {
1108
+ if (CFRunLoopContainsObserver(main_loop, (*main)->observer, kCFRunLoopDefaultMode)) {
1109
+ CFRunLoopRemoveObserver(main_loop, (*main)->observer, kCFRunLoopDefaultMode);
1110
+ }
1111
+
1112
+ CFRunLoopObserverInvalidate((*main)->observer);
1113
+ CFRelease((*main)->observer);
1114
+ (*main)->observer = NULL;
1115
+ }
1116
+
1117
+ if ((*main)->source != NULL) {
1118
+ if (CFRunLoopContainsSource(main_loop, (*main)->source, kCFRunLoopDefaultMode)) {
1119
+ CFRunLoopRemoveSource(main_loop, (*main)->source, kCFRunLoopDefaultMode);
1120
+ }
1121
+
1122
+ CFRelease((*main)->source);
1123
+ (*main)->source = NULL;
1124
+ }
1125
+
1126
+ // Free the main structure.
1127
+ free(*main);
1128
+ *main = NULL;
1129
+ }
1130
+ }
1131
+ #endif
1132
+
1133
+
1134
+ static int create_event_runloop_info(event_runloop_info **hook) {
1135
+ if (*hook != NULL) {
1136
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Expected unallocated event_runloop_info pointer!\n",
1137
+ __FUNCTION__, __LINE__);
1138
+
1139
+ return UIOHOOK_FAILURE;
1140
+ }
1141
+
1142
+ // Try and allocate memory for event_runloop_info.
1143
+ *hook = calloc(1, sizeof(event_runloop_info));
1144
+ if (*hook == NULL) {
1145
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for event_runloop_info structure!\n",
1146
+ __FUNCTION__, __LINE__);
1147
+
1148
+ return UIOHOOK_ERROR_OUT_OF_MEMORY;
1149
+ }
1150
+
1151
+ // Setup the event mask to listen for.
1152
+ CGEventMask event_mask = CGEventMaskBit(kCGEventKeyDown) |
1153
+ CGEventMaskBit(kCGEventKeyUp) |
1154
+ CGEventMaskBit(kCGEventFlagsChanged) |
1155
+
1156
+ CGEventMaskBit(kCGEventLeftMouseDown) |
1157
+ CGEventMaskBit(kCGEventLeftMouseUp) |
1158
+ CGEventMaskBit(kCGEventLeftMouseDragged) |
1159
+
1160
+ CGEventMaskBit(kCGEventRightMouseDown) |
1161
+ CGEventMaskBit(kCGEventRightMouseUp) |
1162
+ CGEventMaskBit(kCGEventRightMouseDragged) |
1163
+
1164
+ CGEventMaskBit(kCGEventOtherMouseDown) |
1165
+ CGEventMaskBit(kCGEventOtherMouseUp) |
1166
+ CGEventMaskBit(kCGEventOtherMouseDragged) |
1167
+
1168
+ CGEventMaskBit(kCGEventMouseMoved) |
1169
+ CGEventMaskBit(kCGEventScrollWheel) |
1170
+
1171
+ // NOTE This event is undocumented and used
1172
+ // for caps-lock release and multi-media keys.
1173
+ CGEventMaskBit(NX_SYSDEFINED);
1174
+
1175
+ // Create the event tap.
1176
+ (*hook)->port = CGEventTapCreate(
1177
+ kCGSessionEventTap, // kCGHIDEventTap
1178
+ kCGHeadInsertEventTap, // kCGTailAppendEventTap
1179
+ kCGEventTapOptionDefault, // kCGEventTapOptionListenOnly See https://github.com/kwhat/jnativehook/issues/22
1180
+ event_mask,
1181
+ hook_event_proc,
1182
+ NULL);
1183
+ if ((*hook)->port == NULL) {
1184
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to create event port!\n",
1185
+ __FUNCTION__, __LINE__);
1186
+
1187
+ return UIOHOOK_ERROR_CREATE_EVENT_PORT;
1188
+ } else {
1189
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: CGEventTapCreate Successful.\n",
1190
+ __FUNCTION__, __LINE__);
1191
+ }
1192
+
1193
+ // Create the runloop event source from the event tap.
1194
+ (*hook)->source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, (*hook)->port, 0);
1195
+ if ((*hook)->source == NULL) {
1196
+ logger(LOG_LEVEL_ERROR, "%s [%u]: CFMachPortCreateRunLoopSource failure!\n",
1197
+ __FUNCTION__, __LINE__);
1198
+
1199
+ return UIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE;
1200
+ } else {
1201
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: CFMachPortCreateRunLoopSource successful.\n",
1202
+ __FUNCTION__, __LINE__);
1203
+ }
1204
+
1205
+ // Create run loop observers.
1206
+ (*hook)->observer = CFRunLoopObserverCreate(
1207
+ kCFAllocatorDefault,
1208
+ kCFRunLoopEntry | kCFRunLoopExit, //kCFRunLoopAllActivities,
1209
+ true,
1210
+ 0,
1211
+ hook_status_proc,
1212
+ NULL);
1213
+ if ((*hook)->observer == NULL) {
1214
+ logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n",
1215
+ __FUNCTION__, __LINE__);
1216
+
1217
+ return UIOHOOK_ERROR_CREATE_OBSERVER;
1218
+ } else {
1219
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopObserverCreate successful.\n",
1220
+ __FUNCTION__, __LINE__);
1221
+ }
1222
+
1223
+ // Add the event source and observer to the runloop mode.
1224
+ CFRunLoopAddSource(event_loop, (*hook)->source, kCFRunLoopDefaultMode);
1225
+ CFRunLoopAddObserver(event_loop, (*hook)->observer, kCFRunLoopDefaultMode);
1226
+
1227
+ return UIOHOOK_SUCCESS;
1228
+ }
1229
+
1230
+ static void destroy_event_runloop_info(event_runloop_info **hook) {
1231
+ // FIXME check event_loop for null ?
1232
+
1233
+ if (*hook != NULL) {
1234
+ if ((*hook)->observer != NULL) {
1235
+ if (CFRunLoopContainsObserver(event_loop, (*hook)->observer, kCFRunLoopDefaultMode)) {
1236
+ CFRunLoopRemoveObserver(event_loop, (*hook)->observer, kCFRunLoopDefaultMode);
1237
+ }
1238
+
1239
+ // Invalidate and free hook observer.
1240
+ CFRunLoopObserverInvalidate((*hook)->observer);
1241
+ CFRelease((*hook)->observer);
1242
+ (*hook)->observer = NULL;
1243
+ }
1244
+
1245
+ if ((*hook)->source != NULL) {
1246
+ if (CFRunLoopContainsSource(event_loop, (*hook)->source, kCFRunLoopDefaultMode)) {
1247
+ CFRunLoopRemoveSource(event_loop, (*hook)->source, kCFRunLoopDefaultMode);
1248
+ }
1249
+
1250
+ // Clean up the event source.
1251
+ CFRelease((*hook)->source);
1252
+ (*hook)->source = NULL;
1253
+ }
1254
+
1255
+ if ((*hook)->port != NULL) {
1256
+ // Stop the CFMachPort from receiving any more messages.
1257
+ CFMachPortInvalidate((*hook)->port);
1258
+ CFRelease((*hook)->port);
1259
+ (*hook)->port = NULL;
1260
+ }
1261
+
1262
+ // Free the hook structure.
1263
+ free(*hook);
1264
+ *hook = NULL;
1265
+ }
1266
+ }
1267
+
1268
+ UIOHOOK_API int hook_run() {
1269
+ int status = UIOHOOK_SUCCESS;
1270
+
1271
+ // Check for accessibility before we start the loop.
1272
+ if (is_accessibility_enabled()) {
1273
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Accessibility API is enabled.\n",
1274
+ __FUNCTION__, __LINE__);
1275
+
1276
+ event_loop = CFRunLoopGetCurrent();
1277
+ if (event_loop != NULL) {
1278
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopGetCurrent successful.\n",
1279
+ __FUNCTION__, __LINE__);
1280
+
1281
+ // Initialize starting modifiers.
1282
+ initialize_modifiers();
1283
+
1284
+ // Try and allocate memory for event_runloop_info.
1285
+ int event_runloop_status = create_event_runloop_info(&hook);
1286
+ if (event_runloop_status != UIOHOOK_SUCCESS) {
1287
+ destroy_event_runloop_info(&hook);
1288
+ return event_runloop_status;
1289
+ }
1290
+
1291
+
1292
+ tis_keycode_message = (TISKeycodeMessage *) calloc(1, sizeof(TISKeycodeMessage));
1293
+ if (tis_keycode_message == NULL) {
1294
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for TIS message structure!\n",
1295
+ __FUNCTION__, __LINE__);
1296
+
1297
+ return UIOHOOK_ERROR_OUT_OF_MEMORY;
1298
+ }
1299
+
1300
+ #ifdef USE_OBJC
1301
+ tis_event_message = (TISEventMessage *) calloc(1, sizeof(TISEventMessage));
1302
+ if (tis_event_message == NULL) {
1303
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for TIS event structure!\n",
1304
+ __FUNCTION__, __LINE__);
1305
+
1306
+ return UIOHOOK_ERROR_OUT_OF_MEMORY;
1307
+ }
1308
+ #endif
1309
+
1310
+ // If we are not running on the main runloop, we need to setup a runloop dispatcher.
1311
+ if (!CFEqual(event_loop, CFRunLoopGetMain())) {
1312
+ // Dynamically load dispatch_sync_f to maintain 10.5 compatibility.
1313
+ *(void **) (&dispatch_sync_f_f) = dlsym(RTLD_DEFAULT, "dispatch_sync_f");
1314
+ const char *dlError = dlerror();
1315
+ if (dlError != NULL) {
1316
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n",
1317
+ __FUNCTION__, __LINE__, dlError);
1318
+ }
1319
+
1320
+ // This load is equivalent to calling dispatch_get_main_queue(). We use
1321
+ // _dispatch_main_q because dispatch_get_main_queue is not exported from
1322
+ // libdispatch.dylib and the upstream function only dereferences the pointer.
1323
+ dispatch_main_queue_s = (struct dispatch_queue_s *) dlsym(RTLD_DEFAULT, "_dispatch_main_q");
1324
+ dlError = dlerror();
1325
+ if (dlError != NULL) {
1326
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n",
1327
+ __FUNCTION__, __LINE__, dlError);
1328
+ }
1329
+
1330
+ if (dispatch_sync_f_f == NULL || dispatch_main_queue_s == NULL) {
1331
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Failed to locate dispatch_sync_f() or dispatch_get_main_queue()!\n",
1332
+ __FUNCTION__, __LINE__);
1333
+
1334
+ #if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES)
1335
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Falling back to runloop signaling.\n",
1336
+ __FUNCTION__, __LINE__);
1337
+
1338
+ // TODO The only thing that maybe needed in this struct is the .perform
1339
+ CFRunLoopSourceContext main_runloop_keycode_context = {
1340
+ .version = 0,
1341
+ .info = tis_keycode_message,
1342
+ .retain = NULL,
1343
+ .release = NULL,
1344
+ .copyDescription = NULL,
1345
+ .equal = NULL,
1346
+ .hash = NULL,
1347
+ .schedule = NULL,
1348
+ .cancel = NULL,
1349
+ .perform = main_runloop_keycode_proc
1350
+ };
1351
+
1352
+ int keycode_runloop_status = create_main_runloop_info(&main_runloop_keycode, &main_runloop_keycode_context);
1353
+ if (keycode_runloop_status != UIOHOOK_SUCCESS) {
1354
+ destroy_main_runloop_info(&main_runloop_keycode);
1355
+ return keycode_runloop_status;
1356
+ }
1357
+ #endif
1358
+ }
1359
+ }
1360
+
1361
+ #ifdef USE_OBJC
1362
+ // Contributed by Alex <universailp@web.de>
1363
+ // Create a garbage collector to handle Cocoa events correctly.
1364
+ Class NSAutoreleasePool_class = (Class) objc_getClass("NSAutoreleasePool");
1365
+ id pool = class_createInstance(NSAutoreleasePool_class, 0);
1366
+
1367
+ id (*eventWithoutCGEvent)(id, SEL) = (id (*)(id, SEL)) objc_msgSend;
1368
+ auto_release_pool = eventWithoutCGEvent(pool, sel_registerName("init"));
1369
+ #endif
1370
+
1371
+
1372
+ // Start the hook thread runloop.
1373
+ CFRunLoopRun();
1374
+
1375
+
1376
+ #ifdef USE_OBJC
1377
+ // Contributed by Alex <universailp@web.de>
1378
+ eventWithoutCGEvent(auto_release_pool, sel_registerName("release"));
1379
+ #endif
1380
+
1381
+ #if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES)
1382
+ pthread_mutex_lock(&main_runloop_mutex);
1383
+ if (!CFEqual(event_loop, CFRunLoopGetMain())) {
1384
+ if (dispatch_sync_f_f == NULL || dispatch_main_queue_s == NULL) {
1385
+ destroy_main_runloop_info(&main_runloop_keycode);
1386
+ }
1387
+ }
1388
+ pthread_mutex_unlock(&main_runloop_mutex);
1389
+ #endif
1390
+
1391
+ #ifdef USE_OBJC
1392
+ free(tis_event_message);
1393
+ #endif
1394
+ free(tis_keycode_message);
1395
+
1396
+ destroy_event_runloop_info(&hook);
1397
+ } else {
1398
+ logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopGetCurrent failure!\n",
1399
+ __FUNCTION__, __LINE__);
1400
+
1401
+ status = UIOHOOK_ERROR_GET_RUNLOOP;
1402
+ }
1403
+ } else {
1404
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Accessibility API is disabled!\n",
1405
+ __FUNCTION__, __LINE__);
1406
+
1407
+ status = UIOHOOK_ERROR_AXAPI_DISABLED;
1408
+ }
1409
+
1410
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Something, something, something, complete.\n",
1411
+ __FUNCTION__, __LINE__);
1412
+
1413
+ return status;
1414
+ }
1415
+
1416
+ UIOHOOK_API int hook_stop() {
1417
+ int status = UIOHOOK_FAILURE;
1418
+
1419
+ CFStringRef mode = CFRunLoopCopyCurrentMode(event_loop);
1420
+ if (mode != NULL) {
1421
+ CFRelease(mode);
1422
+
1423
+ // Stop the run loop.
1424
+ CFRunLoopStop(event_loop);
1425
+
1426
+ // Cleanup native input functions.
1427
+ unload_input_helper();
1428
+
1429
+ status = UIOHOOK_SUCCESS;
1430
+ }
1431
+
1432
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n",
1433
+ __FUNCTION__, __LINE__, status);
1434
+
1435
+ return status;
1436
+ }