@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.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/binding.gyp +85 -0
- package/dist/index.d.ts +194 -0
- package/dist/index.js +206 -0
- package/dist/index.js.map +1 -0
- package/dist/prebuild-test-noop.d.ts +0 -0
- package/dist/prebuild-test-noop.js +3 -0
- package/dist/prebuild-test-noop.js.map +1 -0
- package/libuiohook/include/uiohook.h +457 -0
- package/libuiohook/src/darwin/input_helper.c +535 -0
- package/libuiohook/src/darwin/input_helper.h +203 -0
- package/libuiohook/src/darwin/input_hook.c +1436 -0
- package/libuiohook/src/darwin/post_event.c +303 -0
- package/libuiohook/src/darwin/system_properties.c +479 -0
- package/libuiohook/src/logger.c +40 -0
- package/libuiohook/src/logger.h +32 -0
- package/libuiohook/src/windows/input_helper.c +913 -0
- package/libuiohook/src/windows/input_helper.h +146 -0
- package/libuiohook/src/windows/input_hook.c +722 -0
- package/libuiohook/src/windows/post_event.c +248 -0
- package/libuiohook/src/windows/system_properties.c +231 -0
- package/libuiohook/src/x11/input_helper.c +1846 -0
- package/libuiohook/src/x11/input_helper.h +108 -0
- package/libuiohook/src/x11/input_hook.c +1116 -0
- package/libuiohook/src/x11/post_event.c +427 -0
- package/libuiohook/src/x11/system_properties.c +494 -0
- package/package.json +60 -0
- package/prebuilds/darwin/darwin-arm64/@mukea+uiohook-napi.node +0 -0
- package/prebuilds/darwin/darwin-x64/@mukea+uiohook-napi.node +0 -0
- package/prebuilds/linux/linux-arm64/@mukea+uiohook-napi.node +0 -0
- package/prebuilds/linux/linux-x64/@mukea+uiohook-napi.node +0 -0
- package/prebuilds/windows/win32-x64/@mukea+uiohook-napi.node +0 -0
- package/src/lib/addon.c +337 -0
- package/src/lib/napi_helpers.c +51 -0
- package/src/lib/napi_helpers.h +53 -0
- package/src/lib/uiohook_worker.c +200 -0
- 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
|
+
}
|