@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,1116 @@
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 <inttypes.h>
20
+ #include <limits.h>
21
+
22
+ #ifdef USE_XRECORD_ASYNC
23
+ #include <pthread.h>
24
+ #endif
25
+
26
+ #include <stdint.h>
27
+ #include <uiohook.h>
28
+
29
+ #ifdef USE_XKB_COMMON
30
+ #include <xcb/xkb.h>
31
+ #else
32
+ #include <X11/XKBlib.h>
33
+ #endif
34
+
35
+ #include <X11/keysym.h>
36
+ #include <X11/Xlibint.h>
37
+ #include <X11/Xlib.h>
38
+ #include <X11/extensions/record.h>
39
+
40
+ #if defined(USE_XINERAMA) && !defined(USE_XRANDR)
41
+ #include <X11/extensions/Xinerama.h>
42
+ #elif defined(USE_XRANDR)
43
+ #include <X11/extensions/Xrandr.h>
44
+ #else
45
+ // TODO We may need to fallback to the xf86vm extension for things like TwinView.
46
+ #pragma message("*** Warning: Xinerama or XRandR support is required to produce cross-platform mouse coordinates for multi-head configurations!")
47
+ #pragma message("... Assuming single-head display.")
48
+ #endif
49
+
50
+ #include "logger.h"
51
+ #include "input_helper.h"
52
+
53
+ // Thread and hook handles.
54
+ #ifdef USE_XRECORD_ASYNC
55
+ static bool running;
56
+
57
+ static pthread_cond_t hook_xrecord_cond = PTHREAD_COND_INITIALIZER;
58
+ static pthread_mutex_t hook_xrecord_mutex = PTHREAD_MUTEX_INITIALIZER;
59
+ #endif
60
+
61
+ typedef struct _hook_info {
62
+ struct _data {
63
+ Display *display;
64
+ XRecordRange *range;
65
+ } data;
66
+ struct _ctrl {
67
+ Display *display;
68
+ XRecordContext context;
69
+ } ctrl;
70
+ struct _input {
71
+ #ifdef USE_XKB_COMMON
72
+ xcb_connection_t *connection;
73
+ struct xkb_context *context;
74
+ #endif
75
+ uint16_t mask;
76
+ struct _mouse {
77
+ bool is_dragged;
78
+ struct _click {
79
+ unsigned short int count;
80
+ long int time;
81
+ unsigned short int button;
82
+ } click;
83
+ } mouse;
84
+ } input;
85
+ } hook_info;
86
+ static hook_info *hook;
87
+
88
+ // For this struct, refer to libxnee, requires Xlibint.h
89
+ typedef union {
90
+ unsigned char type;
91
+ xEvent event;
92
+ xResourceReq req;
93
+ xGenericReply reply;
94
+ xError error;
95
+ xConnSetupPrefix setup;
96
+ } XRecordDatum;
97
+
98
+ #if defined(USE_XKB_COMMON)
99
+ static struct xkb_state *state = NULL;
100
+ #endif
101
+
102
+ // Virtual event pointer.
103
+ static uiohook_event event;
104
+
105
+ // Event dispatch callback.
106
+ static dispatcher_t dispatcher = NULL;
107
+
108
+ UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) {
109
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n",
110
+ __FUNCTION__, __LINE__, dispatch_proc);
111
+
112
+ dispatcher = dispatch_proc;
113
+ }
114
+
115
+ // Send out an event if a dispatcher was set.
116
+ static inline void dispatch_event(uiohook_event *const event) {
117
+ if (dispatcher != NULL) {
118
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n",
119
+ __FUNCTION__, __LINE__, event->type);
120
+
121
+ dispatcher(event);
122
+ } else {
123
+ logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n",
124
+ __FUNCTION__, __LINE__);
125
+ }
126
+ }
127
+
128
+ // Set the native modifier mask for future events.
129
+ static inline void set_modifier_mask(uint16_t mask) {
130
+ hook->input.mask |= mask;
131
+ }
132
+
133
+ // Unset the native modifier mask for future events.
134
+ static inline void unset_modifier_mask(uint16_t mask) {
135
+ hook->input.mask &= ~mask;
136
+ }
137
+
138
+ // Get the current native modifier mask state.
139
+ static inline uint16_t get_modifiers() {
140
+ return hook->input.mask;
141
+ }
142
+
143
+ // Initialize the modifier lock masks.
144
+ static void initialize_locks() {
145
+ #ifdef USE_XKB_COMMON
146
+ if (xkb_state_led_name_is_active(state, XKB_LED_NAME_CAPS)) {
147
+ set_modifier_mask(MASK_CAPS_LOCK);
148
+ } else {
149
+ unset_modifier_mask(MASK_CAPS_LOCK);
150
+ }
151
+
152
+ if (xkb_state_led_name_is_active(state, XKB_LED_NAME_NUM)) {
153
+ set_modifier_mask(MASK_NUM_LOCK);
154
+ } else {
155
+ unset_modifier_mask(MASK_NUM_LOCK);
156
+ }
157
+
158
+ if (xkb_state_led_name_is_active(state, XKB_LED_NAME_SCROLL)) {
159
+ set_modifier_mask(MASK_SCROLL_LOCK);
160
+ } else {
161
+ unset_modifier_mask(MASK_SCROLL_LOCK);
162
+ }
163
+ #else
164
+ unsigned int led_mask = 0x00;
165
+ if (XkbGetIndicatorState(hook->ctrl.display, XkbUseCoreKbd, &led_mask) == Success) {
166
+ if (led_mask & 0x01) {
167
+ set_modifier_mask(MASK_CAPS_LOCK);
168
+ } else {
169
+ unset_modifier_mask(MASK_CAPS_LOCK);
170
+ }
171
+
172
+ if (led_mask & 0x02) {
173
+ set_modifier_mask(MASK_NUM_LOCK);
174
+ } else {
175
+ unset_modifier_mask(MASK_NUM_LOCK);
176
+ }
177
+
178
+ if (led_mask & 0x04) {
179
+ set_modifier_mask(MASK_SCROLL_LOCK);
180
+ } else {
181
+ unset_modifier_mask(MASK_SCROLL_LOCK);
182
+ }
183
+ } else {
184
+ logger(LOG_LEVEL_WARN, "%s [%u]: XkbGetIndicatorState failed to get current led mask!\n",
185
+ __FUNCTION__, __LINE__);
186
+ }
187
+ #endif
188
+ }
189
+
190
+ // Initialize the modifier mask to the current modifiers.
191
+ static void initialize_modifiers() {
192
+ hook->input.mask = 0x0000;
193
+
194
+ KeyCode keycode;
195
+ char keymap[32];
196
+ XQueryKeymap(hook->ctrl.display, keymap);
197
+
198
+ Window unused_win;
199
+ int unused_int;
200
+ unsigned int mask;
201
+ if (XQueryPointer(hook->ctrl.display, DefaultRootWindow(hook->ctrl.display), &unused_win, &unused_win, &unused_int, &unused_int, &unused_int, &unused_int, &mask)) {
202
+ if (mask & ShiftMask) {
203
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L);
204
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); }
205
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R);
206
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); }
207
+ }
208
+ if (mask & ControlMask) {
209
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L);
210
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); }
211
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R);
212
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); }
213
+ }
214
+ if (mask & Mod1Mask) {
215
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L);
216
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); }
217
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R);
218
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); }
219
+ }
220
+ if (mask & Mod4Mask) {
221
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L);
222
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); }
223
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R);
224
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); }
225
+ }
226
+
227
+ if (mask & Button1Mask) { set_modifier_mask(MASK_BUTTON1); }
228
+ if (mask & Button2Mask) { set_modifier_mask(MASK_BUTTON2); }
229
+ if (mask & Button3Mask) { set_modifier_mask(MASK_BUTTON3); }
230
+ if (mask & Button4Mask) { set_modifier_mask(MASK_BUTTON4); }
231
+ if (mask & Button5Mask) { set_modifier_mask(MASK_BUTTON5); }
232
+ } else {
233
+ logger(LOG_LEVEL_WARN, "%s [%u]: XQueryPointer failed to get current modifiers!\n",
234
+ __FUNCTION__, __LINE__);
235
+
236
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L);
237
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); }
238
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R);
239
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); }
240
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L);
241
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); }
242
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R);
243
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); }
244
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L);
245
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); }
246
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R);
247
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); }
248
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L);
249
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); }
250
+ keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R);
251
+ if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); }
252
+ }
253
+
254
+ initialize_locks();
255
+ }
256
+
257
+ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) {
258
+ uint64_t timestamp = (uint64_t) recorded_data->server_time;
259
+
260
+ if (recorded_data->category == XRecordStartOfData) {
261
+ // Initialize native input helper functions.
262
+ load_input_helper();
263
+
264
+ // Populate the hook start event.
265
+ event.time = timestamp;
266
+ event.reserved = 0x00;
267
+
268
+ event.type = EVENT_HOOK_ENABLED;
269
+ event.mask = 0x00;
270
+
271
+ // Fire the hook start event.
272
+ dispatch_event(&event);
273
+ } else if (recorded_data->category == XRecordEndOfData) {
274
+ // Populate the hook stop event.
275
+ event.time = timestamp;
276
+ event.reserved = 0x00;
277
+
278
+ event.type = EVENT_HOOK_DISABLED;
279
+ event.mask = 0x00;
280
+
281
+ // Fire the hook stop event.
282
+ dispatch_event(&event);
283
+
284
+ // Deinitialize native input helper functions.
285
+ unload_input_helper();
286
+ } else if (recorded_data->category == XRecordFromServer || recorded_data->category == XRecordFromClient) {
287
+ // Get XRecord data.
288
+ XRecordDatum *data = (XRecordDatum *) recorded_data->data;
289
+
290
+ if (data->type == KeyPress) {
291
+ // The X11 KeyCode associated with this event.
292
+ KeyCode keycode = (KeyCode) data->event.u.u.detail;
293
+ KeySym keysym = 0x00;
294
+ #if defined(USE_XKB_COMMON)
295
+ if (state != NULL) {
296
+ keysym = xkb_state_key_get_one_sym(state, keycode);
297
+ }
298
+ #else
299
+ keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state);
300
+ #endif
301
+
302
+ // Check to make sure the key is printable.
303
+ uint16_t buffer[2];
304
+ size_t count = 0;
305
+ #ifdef USE_XKB_COMMON
306
+ if (state != NULL) {
307
+ count = keycode_to_unicode(state, keycode, buffer, sizeof(buffer) / sizeof(uint16_t));
308
+ }
309
+ #else
310
+ count = keysym_to_unicode(keysym, buffer, sizeof(buffer) / sizeof(uint16_t));
311
+ #endif
312
+
313
+
314
+ unsigned short int scancode = keycode_to_scancode(keycode);
315
+
316
+ // TODO If you have a better suggestion for this ugly, let me know.
317
+ if (scancode == VC_SHIFT_L) { set_modifier_mask(MASK_SHIFT_L); }
318
+ else if (scancode == VC_SHIFT_R) { set_modifier_mask(MASK_SHIFT_R); }
319
+ else if (scancode == VC_CONTROL_L) { set_modifier_mask(MASK_CTRL_L); }
320
+ else if (scancode == VC_CONTROL_R) { set_modifier_mask(MASK_CTRL_R); }
321
+ else if (scancode == VC_ALT_L) { set_modifier_mask(MASK_ALT_L); }
322
+ else if (scancode == VC_ALT_R) { set_modifier_mask(MASK_ALT_R); }
323
+ else if (scancode == VC_META_L) { set_modifier_mask(MASK_META_L); }
324
+ else if (scancode == VC_META_R) { set_modifier_mask(MASK_META_R); }
325
+ #ifdef USE_XKB_COMMON
326
+ xkb_state_update_key(state, keycode, XKB_KEY_DOWN);
327
+ #endif
328
+ initialize_locks();
329
+
330
+
331
+ if ((get_modifiers() & MASK_NUM_LOCK) == 0) {
332
+ switch (scancode) {
333
+ case VC_KP_SEPARATOR:
334
+ case VC_KP_1:
335
+ case VC_KP_2:
336
+ case VC_KP_3:
337
+ case VC_KP_4:
338
+ case VC_KP_5:
339
+ case VC_KP_6:
340
+ case VC_KP_7:
341
+ case VC_KP_8:
342
+ case VC_KP_0:
343
+ case VC_KP_9:
344
+ scancode |= 0xEE00;
345
+ break;
346
+ }
347
+ }
348
+
349
+ // Populate key pressed event.
350
+ event.time = timestamp;
351
+ event.reserved = 0x00;
352
+
353
+ event.type = EVENT_KEY_PRESSED;
354
+ event.mask = get_modifiers();
355
+
356
+ event.data.keyboard.keycode = scancode;
357
+ event.data.keyboard.rawcode = keysym;
358
+ event.data.keyboard.keychar = CHAR_UNDEFINED;
359
+
360
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X pressed. (%#X)\n",
361
+ __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
362
+
363
+ // Fire key pressed event.
364
+ dispatch_event(&event);
365
+
366
+ // If the pressed event was not consumed...
367
+ if (event.reserved ^ 0x01) {
368
+ for (unsigned int i = 0; i < count; i++) {
369
+ // Populate key typed event.
370
+ event.time = timestamp;
371
+ event.reserved = 0x00;
372
+
373
+ event.type = EVENT_KEY_TYPED;
374
+ event.mask = get_modifiers();
375
+
376
+ event.data.keyboard.keycode = VC_UNDEFINED;
377
+ event.data.keyboard.rawcode = keysym;
378
+ event.data.keyboard.keychar = buffer[i];
379
+
380
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X typed. (%lc)\n",
381
+ __FUNCTION__, __LINE__, event.data.keyboard.keycode, (uint16_t) event.data.keyboard.keychar);
382
+
383
+ // Fire key typed event.
384
+ dispatch_event(&event);
385
+ }
386
+ }
387
+ } else if (data->type == KeyRelease) {
388
+ // The X11 KeyCode associated with this event.
389
+ KeyCode keycode = (KeyCode) data->event.u.u.detail;
390
+ KeySym keysym = 0x00;
391
+ #ifdef USE_XKB_COMMON
392
+ if (state != NULL) {
393
+ keysym = xkb_state_key_get_one_sym(state, keycode);
394
+ }
395
+ #else
396
+ keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state);
397
+ #endif
398
+
399
+ // Check to make sure the key is printable.
400
+ uint16_t buffer[2];
401
+ #ifdef USE_XKB_COMMON
402
+ if (state != NULL) {
403
+ keycode_to_unicode(state, keycode, buffer, sizeof(buffer) / sizeof(uint16_t));
404
+ }
405
+ #else
406
+ keysym_to_unicode(keysym, buffer, sizeof(buffer) / sizeof(uint16_t));
407
+ #endif
408
+
409
+ unsigned short int scancode = keycode_to_scancode(keycode);
410
+
411
+ // TODO If you have a better suggestion for this ugly, let me know.
412
+ if (scancode == VC_SHIFT_L) { unset_modifier_mask(MASK_SHIFT_L); }
413
+ else if (scancode == VC_SHIFT_R) { unset_modifier_mask(MASK_SHIFT_R); }
414
+ else if (scancode == VC_CONTROL_L) { unset_modifier_mask(MASK_CTRL_L); }
415
+ else if (scancode == VC_CONTROL_R) { unset_modifier_mask(MASK_CTRL_R); }
416
+ else if (scancode == VC_ALT_L) { unset_modifier_mask(MASK_ALT_L); }
417
+ else if (scancode == VC_ALT_R) { unset_modifier_mask(MASK_ALT_R); }
418
+ else if (scancode == VC_META_L) { unset_modifier_mask(MASK_META_L); }
419
+ else if (scancode == VC_META_R) { unset_modifier_mask(MASK_META_R); }
420
+ #ifdef USE_XKB_COMMON
421
+ xkb_state_update_key(state, keycode, XKB_KEY_UP);
422
+ #endif
423
+ initialize_locks();
424
+
425
+ if ((get_modifiers() & MASK_NUM_LOCK) == 0) {
426
+ switch (scancode) {
427
+ case VC_KP_SEPARATOR:
428
+ case VC_KP_1:
429
+ case VC_KP_2:
430
+ case VC_KP_3:
431
+ case VC_KP_4:
432
+ case VC_KP_5:
433
+ case VC_KP_6:
434
+ case VC_KP_7:
435
+ case VC_KP_8:
436
+ case VC_KP_0:
437
+ case VC_KP_9:
438
+ scancode |= 0xEE00;
439
+ break;
440
+ }
441
+ }
442
+
443
+ // Populate key released event.
444
+ event.time = timestamp;
445
+ event.reserved = 0x00;
446
+
447
+ event.type = EVENT_KEY_RELEASED;
448
+ event.mask = get_modifiers();
449
+
450
+ event.data.keyboard.keycode = scancode;
451
+ event.data.keyboard.rawcode = keysym;
452
+ event.data.keyboard.keychar = CHAR_UNDEFINED;
453
+
454
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X released. (%#X)\n",
455
+ __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
456
+
457
+ // Fire key released event.
458
+ dispatch_event(&event);
459
+ } else if (data->type == ButtonPress) {
460
+ unsigned int map_button = button_map_lookup(data->event.u.u.detail);
461
+
462
+ // X11 handles wheel events as button events.
463
+ if (map_button == WheelUp || map_button == WheelDown
464
+ || map_button == WheelLeft || map_button == WheelRight) {
465
+
466
+ // Reset the click count and previous button.
467
+ hook->input.mouse.click.count = 1;
468
+ hook->input.mouse.click.button = MOUSE_NOBUTTON;
469
+
470
+ /* Scroll wheel release events.
471
+ * Scroll type: WHEEL_UNIT_SCROLL
472
+ * Scroll amount: 3 unit increments per notch
473
+ * Units to scroll: 3 unit increments
474
+ * Vertical unit increment: 15 pixels
475
+ */
476
+
477
+ // Populate mouse wheel event.
478
+ event.time = timestamp;
479
+ event.reserved = 0x00;
480
+
481
+ event.type = EVENT_MOUSE_WHEEL;
482
+ event.mask = get_modifiers();
483
+
484
+ event.data.wheel.clicks = hook->input.mouse.click.count;
485
+ event.data.wheel.x = data->event.u.keyButtonPointer.rootX;
486
+ event.data.wheel.y = data->event.u.keyButtonPointer.rootY;
487
+
488
+ #if defined(USE_XINERAMA) || defined(USE_XRANDR)
489
+ uint8_t count;
490
+ screen_data *screens = hook_create_screen_info(&count);
491
+ if (count > 1) {
492
+ event.data.wheel.x -= screens[0].x;
493
+ event.data.wheel.y -= screens[0].y;
494
+ }
495
+
496
+ if (screens != NULL) {
497
+ free(screens);
498
+ }
499
+ #endif
500
+
501
+ /* X11 does not have an API call for acquiring the mouse scroll type. This
502
+ * maybe part of the XInput2 (XI2) extention but I will wont know until it
503
+ * is available on my platform. For the time being we will just use the
504
+ * unit scroll value.
505
+ */
506
+ event.data.wheel.type = WHEEL_UNIT_SCROLL;
507
+
508
+ /* Some scroll wheel properties are available via the new XInput2 (XI2)
509
+ * extension. Unfortunately the extension is not available on my
510
+ * development platform at this time. For the time being we will just
511
+ * use the Windows default value of 3.
512
+ */
513
+ event.data.wheel.amount = 3;
514
+
515
+ if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelLeft) {
516
+ // Wheel Rotated Up and Away.
517
+ event.data.wheel.rotation = -1;
518
+ } else { // data->event.u.u.detail == WheelDown
519
+ // Wheel Rotated Down and Towards.
520
+ event.data.wheel.rotation = 1;
521
+ }
522
+
523
+ if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown) {
524
+ // Wheel Rotated Up or Down.
525
+ event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION;
526
+ } else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight
527
+ // Wheel Rotated Left or Right.
528
+ event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION;
529
+ }
530
+
531
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n",
532
+ __FUNCTION__, __LINE__, event.data.wheel.type,
533
+ event.data.wheel.amount * event.data.wheel.rotation,
534
+ event.data.wheel.direction,
535
+ event.data.wheel.x, event.data.wheel.y);
536
+
537
+ // Fire mouse wheel event.
538
+ dispatch_event(&event);
539
+ } else {
540
+ /* This information is all static for X11, its up to the WM to
541
+ * decide how to interpret the wheel events.
542
+ */
543
+ uint16_t button = MOUSE_NOBUTTON;
544
+ switch (map_button) {
545
+ case Button1:
546
+ button = MOUSE_BUTTON1;
547
+ set_modifier_mask(MASK_BUTTON1);
548
+ break;
549
+
550
+ case Button2:
551
+ button = MOUSE_BUTTON2;
552
+ set_modifier_mask(MASK_BUTTON2);
553
+ break;
554
+
555
+ case Button3:
556
+ button = MOUSE_BUTTON3;
557
+ set_modifier_mask(MASK_BUTTON3);
558
+ break;
559
+
560
+ case XButton1:
561
+ button = MOUSE_BUTTON4;
562
+ set_modifier_mask(MASK_BUTTON5);
563
+ break;
564
+
565
+ case XButton2:
566
+ button = MOUSE_BUTTON5;
567
+ set_modifier_mask(MASK_BUTTON5);
568
+ break;
569
+
570
+ default:
571
+ // Do not set modifier masks past button MASK_BUTTON5.
572
+ break;
573
+ }
574
+
575
+
576
+ // Track the number of clicks, the button must match the previous button.
577
+ if (button == hook->input.mouse.click.button && (long int) (timestamp - hook->input.mouse.click.time) <= hook_get_multi_click_time()) {
578
+ if (hook->input.mouse.click.count < USHRT_MAX) {
579
+ hook->input.mouse.click.count++;
580
+ } else {
581
+ logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n",
582
+ __FUNCTION__, __LINE__);
583
+ }
584
+ } else {
585
+ // Reset the click count.
586
+ hook->input.mouse.click.count = 1;
587
+
588
+ // Set the previous button.
589
+ hook->input.mouse.click.button = button;
590
+ }
591
+
592
+ // Save this events time to calculate the hook->input.mouse.click.count.
593
+ hook->input.mouse.click.time = timestamp;
594
+
595
+
596
+ // Populate mouse pressed event.
597
+ event.time = timestamp;
598
+ event.reserved = 0x00;
599
+
600
+ event.type = EVENT_MOUSE_PRESSED;
601
+ event.mask = get_modifiers();
602
+
603
+ event.data.mouse.button = button;
604
+ event.data.mouse.clicks = hook->input.mouse.click.count;
605
+ event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
606
+ event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
607
+
608
+ #if defined(USE_XINERAMA) || defined(USE_XRANDR)
609
+ uint8_t count;
610
+ screen_data *screens = hook_create_screen_info(&count);
611
+ if (count > 1) {
612
+ event.data.mouse.x -= screens[0].x;
613
+ event.data.mouse.y -= screens[0].y;
614
+ }
615
+
616
+ if (screens != NULL) {
617
+ free(screens);
618
+ }
619
+ #endif
620
+
621
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n",
622
+ __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
623
+ event.data.mouse.x, event.data.mouse.y);
624
+
625
+ // Fire mouse pressed event.
626
+ dispatch_event(&event);
627
+ }
628
+ } else if (data->type == ButtonRelease) {
629
+ unsigned int map_button = button_map_lookup(data->event.u.u.detail);
630
+
631
+ // X11 handles wheel events as button events.
632
+ if (map_button != WheelUp && map_button != WheelDown
633
+ && map_button != WheelLeft && map_button != WheelRight) {
634
+
635
+ /* This information is all static for X11, its up to the WM to
636
+ * decide how to interpret the wheel events.
637
+ */
638
+ uint16_t button = MOUSE_NOBUTTON;
639
+ switch (map_button) {
640
+ // FIXME This should use a lookup table to handle button remapping.
641
+ case Button1:
642
+ button = MOUSE_BUTTON1;
643
+ unset_modifier_mask(MASK_BUTTON1);
644
+ break;
645
+
646
+ case Button2:
647
+ button = MOUSE_BUTTON2;
648
+ unset_modifier_mask(MASK_BUTTON2);
649
+ break;
650
+
651
+ case Button3:
652
+ button = MOUSE_BUTTON3;
653
+ unset_modifier_mask(MASK_BUTTON3);
654
+ break;
655
+
656
+ case XButton1:
657
+ button = MOUSE_BUTTON4;
658
+ unset_modifier_mask(MASK_BUTTON5);
659
+ break;
660
+
661
+ case XButton2:
662
+ button = MOUSE_BUTTON5;
663
+ unset_modifier_mask(MASK_BUTTON5);
664
+ break;
665
+
666
+ default:
667
+ // Do not set modifier masks past button MASK_BUTTON5.
668
+ break;
669
+ }
670
+
671
+ // Populate mouse released event.
672
+ event.time = timestamp;
673
+ event.reserved = 0x00;
674
+
675
+ event.type = EVENT_MOUSE_RELEASED;
676
+ event.mask = get_modifiers();
677
+
678
+ event.data.mouse.button = button;
679
+ event.data.mouse.clicks = hook->input.mouse.click.count;
680
+ event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
681
+ event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
682
+
683
+ #if defined(USE_XINERAMA) || defined(USE_XRANDR)
684
+ uint8_t count;
685
+ screen_data *screens = hook_create_screen_info(&count);
686
+ if (count > 1) {
687
+ event.data.mouse.x -= screens[0].x;
688
+ event.data.mouse.y -= screens[0].y;
689
+ }
690
+
691
+ if (screens != NULL) {
692
+ free(screens);
693
+ }
694
+ #endif
695
+
696
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u released %u time(s). (%u, %u)\n",
697
+ __FUNCTION__, __LINE__, event.data.mouse.button,
698
+ event.data.mouse.clicks,
699
+ event.data.mouse.x, event.data.mouse.y);
700
+
701
+ // Fire mouse released event.
702
+ dispatch_event(&event);
703
+
704
+ // If the pressed event was not consumed...
705
+ if (event.reserved ^ 0x01 && hook->input.mouse.is_dragged != true) {
706
+ // Populate mouse clicked event.
707
+ event.time = timestamp;
708
+ event.reserved = 0x00;
709
+
710
+ event.type = EVENT_MOUSE_CLICKED;
711
+ event.mask = get_modifiers();
712
+
713
+ event.data.mouse.button = button;
714
+ event.data.mouse.clicks = hook->input.mouse.click.count;
715
+ event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
716
+ event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
717
+
718
+ #if defined(USE_XINERAMA) || defined(USE_XRANDR)
719
+ uint8_t count;
720
+ screen_data *screens = hook_create_screen_info(&count);
721
+ if (count > 1) {
722
+ event.data.mouse.x -= screens[0].x;
723
+ event.data.mouse.y -= screens[0].y;
724
+ }
725
+
726
+ if (screens != NULL) {
727
+ free(screens);
728
+ }
729
+ #endif
730
+
731
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n",
732
+ __FUNCTION__, __LINE__, event.data.mouse.button,
733
+ event.data.mouse.clicks,
734
+ event.data.mouse.x, event.data.mouse.y);
735
+
736
+ // Fire mouse clicked event.
737
+ dispatch_event(&event);
738
+ }
739
+
740
+ // Reset the number of clicks.
741
+ if (button == hook->input.mouse.click.button && (long int) (event.time - hook->input.mouse.click.time) > hook_get_multi_click_time()) {
742
+ // Reset the click count.
743
+ hook->input.mouse.click.count = 0;
744
+ }
745
+ }
746
+ } else if (data->type == MotionNotify) {
747
+ // Reset the click count.
748
+ if (hook->input.mouse.click.count != 0 && (long int) (timestamp - hook->input.mouse.click.time) > hook_get_multi_click_time()) {
749
+ hook->input.mouse.click.count = 0;
750
+ }
751
+
752
+ // Populate mouse move event.
753
+ event.time = timestamp;
754
+ event.reserved = 0x00;
755
+
756
+ event.mask = get_modifiers();
757
+
758
+ // Check the upper half of virtual modifiers for non-zero values and set the mouse
759
+ // dragged flag. The last 3 bits are reserved for lock masks.
760
+ hook->input.mouse.is_dragged = ((event.mask & 0x1F00) > 0);
761
+ if (hook->input.mouse.is_dragged) {
762
+ // Create Mouse Dragged event.
763
+ event.type = EVENT_MOUSE_DRAGGED;
764
+ } else {
765
+ // Create a Mouse Moved event.
766
+ event.type = EVENT_MOUSE_MOVED;
767
+ }
768
+
769
+ event.data.mouse.button = MOUSE_NOBUTTON;
770
+ event.data.mouse.clicks = hook->input.mouse.click.count;
771
+ event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
772
+ event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
773
+
774
+ #if defined(USE_XINERAMA) || defined(USE_XRANDR)
775
+ uint8_t count;
776
+ screen_data *screens = hook_create_screen_info(&count);
777
+ if (count > 1) {
778
+ event.data.mouse.x -= screens[0].x;
779
+ event.data.mouse.y -= screens[0].y;
780
+ }
781
+
782
+ if (screens != NULL) {
783
+ free(screens);
784
+ }
785
+ #endif
786
+
787
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse %s to %i, %i. (%#X)\n",
788
+ __FUNCTION__, __LINE__, hook->input.mouse.is_dragged ? "dragged" : "moved",
789
+ event.data.mouse.x, event.data.mouse.y, event.mask);
790
+
791
+ // Fire mouse move event.
792
+ dispatch_event(&event);
793
+ } else {
794
+ // In theory this *should* never execute.
795
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled X11 event: %#X.\n",
796
+ __FUNCTION__, __LINE__, (unsigned int) data->type);
797
+ }
798
+ } else {
799
+ logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled X11 hook category! (%#X)\n",
800
+ __FUNCTION__, __LINE__, recorded_data->category);
801
+ }
802
+
803
+ // TODO There is no way to consume the XRecord event.
804
+
805
+ XRecordFreeData(recorded_data);
806
+ }
807
+
808
+
809
+ static inline bool enable_key_repeate() {
810
+ // Attempt to setup detectable autorepeat.
811
+ // NOTE: is_auto_repeat is NOT stdbool!
812
+ Bool is_auto_repeat = False;
813
+
814
+ // Enable detectable auto-repeat.
815
+ XkbSetDetectableAutoRepeat(hook->ctrl.display, True, &is_auto_repeat);
816
+
817
+ return is_auto_repeat;
818
+ }
819
+
820
+
821
+ static inline int xrecord_block() {
822
+ int status = UIOHOOK_FAILURE;
823
+
824
+ // Save the data display associated with this hook so it is passed to each event.
825
+ //XPointer closeure = (XPointer) (ctrl_display);
826
+ XPointer closeure = NULL;
827
+
828
+ #ifdef USE_XRECORD_ASYNC
829
+ // Async requires that we loop so that our thread does not return.
830
+ if (XRecordEnableContextAsync(hook->data.display, context, hook_event_proc, closeure) != 0) {
831
+ // Time in MS to sleep the runloop.
832
+ int timesleep = 100;
833
+
834
+ // Allow the thread loop to block.
835
+ pthread_mutex_lock(&hook_xrecord_mutex);
836
+ running = true;
837
+
838
+ do {
839
+ // Unlock the mutex from the previous iteration.
840
+ pthread_mutex_unlock(&hook_xrecord_mutex);
841
+
842
+ XRecordProcessReplies(hook->data.display);
843
+
844
+ // Prevent 100% CPU utilization.
845
+ struct timeval tv;
846
+ gettimeofday(&tv, NULL);
847
+
848
+ struct timespec ts;
849
+ ts.tv_sec = time(NULL) + timesleep / 1000;
850
+ ts.tv_nsec = tv.tv_usec * 1000 + 1000 * 1000 * (timesleep % 1000);
851
+ ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000);
852
+ ts.tv_nsec %= (1000 * 1000 * 1000);
853
+
854
+ pthread_mutex_lock(&hook_xrecord_mutex);
855
+ pthread_cond_timedwait(&hook_xrecord_cond, &hook_xrecord_mutex, &ts);
856
+ } while (running);
857
+
858
+ // Unlock after loop exit.
859
+ pthread_mutex_unlock(&hook_xrecord_mutex);
860
+
861
+ // Set the exit status.
862
+ status = NULL;
863
+ }
864
+ #else
865
+ // Sync blocks until XRecordDisableContext() is called.
866
+ if (XRecordEnableContext(hook->data.display, hook->ctrl.context, hook_event_proc, closeure) != 0) {
867
+ status = UIOHOOK_SUCCESS;
868
+ }
869
+ #endif
870
+ else {
871
+ logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordEnableContext failure!\n",
872
+ __FUNCTION__, __LINE__);
873
+
874
+ #ifdef USE_XRECORD_ASYNC
875
+ // Reset the running state.
876
+ pthread_mutex_lock(&hook_xrecord_mutex);
877
+ running = false;
878
+ pthread_mutex_unlock(&hook_xrecord_mutex);
879
+ #endif
880
+
881
+ // Set the exit status.
882
+ status = UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT;
883
+ }
884
+
885
+ return status;
886
+ }
887
+
888
+ static int xrecord_alloc() {
889
+ int status = UIOHOOK_FAILURE;
890
+
891
+ // Make sure the data display is synchronized to prevent late event delivery!
892
+ // See Bug 42356 for more information.
893
+ // https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4
894
+ XSynchronize(hook->data.display, True);
895
+
896
+ // Setup XRecord range.
897
+ XRecordClientSpec clients = XRecordAllClients;
898
+
899
+ hook->data.range = XRecordAllocRange();
900
+ if (hook->data.range != NULL) {
901
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordAllocRange successful.\n",
902
+ __FUNCTION__, __LINE__);
903
+
904
+ hook->data.range->device_events.first = KeyPress;
905
+ hook->data.range->device_events.last = MotionNotify;
906
+
907
+ // Note that the documentation for this function is incorrect,
908
+ // hook->data.display should be used!
909
+ // See: http://www.x.org/releases/X11R7.6/doc/libXtst/recordlib.txt
910
+ hook->ctrl.context = XRecordCreateContext(hook->data.display, XRecordFromServerTime, &clients, 1, &hook->data.range, 1);
911
+ if (hook->ctrl.context != 0) {
912
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordCreateContext successful.\n",
913
+ __FUNCTION__, __LINE__);
914
+
915
+ // Block until hook_stop() is called.
916
+ status = xrecord_block();
917
+
918
+ // Free up the context if it was set.
919
+ XRecordFreeContext(hook->data.display, hook->ctrl.context);
920
+ } else {
921
+ logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordCreateContext failure!\n",
922
+ __FUNCTION__, __LINE__);
923
+
924
+ // Set the exit status.
925
+ status = UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT;
926
+ }
927
+
928
+ // Free the XRecord range.
929
+ XFree(hook->data.range);
930
+ } else {
931
+ logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordAllocRange failure!\n",
932
+ __FUNCTION__, __LINE__);
933
+
934
+ // Set the exit status.
935
+ status = UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE;
936
+ }
937
+
938
+ return status;
939
+ }
940
+
941
+ static int xrecord_query() {
942
+ int status = UIOHOOK_FAILURE;
943
+
944
+ // Check to make sure XRecord is installed and enabled.
945
+ int major, minor;
946
+ if (XRecordQueryVersion(hook->ctrl.display, &major, &minor) != 0) {
947
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecord version: %i.%i.\n",
948
+ __FUNCTION__, __LINE__, major, minor);
949
+
950
+ status = xrecord_alloc();
951
+ } else {
952
+ logger(LOG_LEVEL_ERROR, "%s [%u]: XRecord is not currently available!\n",
953
+ __FUNCTION__, __LINE__);
954
+
955
+ status = UIOHOOK_ERROR_X_RECORD_NOT_FOUND;
956
+ }
957
+
958
+ return status;
959
+ }
960
+
961
+ static int xrecord_start() {
962
+ int status = UIOHOOK_FAILURE;
963
+
964
+ // Open the control display for XRecord.
965
+ hook->ctrl.display = XOpenDisplay(NULL);
966
+
967
+ // Open a data display for XRecord.
968
+ // NOTE This display must be opened on the same thread as XRecord.
969
+ hook->data.display = XOpenDisplay(NULL);
970
+ if (hook->ctrl.display != NULL && hook->data.display != NULL) {
971
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: XOpenDisplay successful.\n",
972
+ __FUNCTION__, __LINE__);
973
+
974
+ bool is_auto_repeat = enable_key_repeate();
975
+ if (is_auto_repeat) {
976
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Successfully enabled detectable auto-repeat.\n",
977
+ __FUNCTION__, __LINE__);
978
+ } else {
979
+ logger(LOG_LEVEL_WARN, "%s [%u]: Could not enable detectable auto-repeat!\n",
980
+ __FUNCTION__, __LINE__);
981
+ }
982
+
983
+ #if defined(USE_XKB_COMMON)
984
+ // Open XCB Connection
985
+ hook->input.connection = XGetXCBConnection(hook->ctrl.display);
986
+ int xcb_status = xcb_connection_has_error(hook->input.connection);
987
+ if (xcb_status <= 0) {
988
+ // Initialize xkbcommon context.
989
+ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
990
+
991
+ if (context != NULL) {
992
+ hook->input.context = xkb_context_ref(context);
993
+ } else {
994
+ logger(LOG_LEVEL_ERROR, "%s [%u]: xkb_context_new failure!\n",
995
+ __FUNCTION__, __LINE__);
996
+ }
997
+ } else {
998
+ logger(LOG_LEVEL_ERROR, "%s [%u]: xcb_connect failure! (%d)\n",
999
+ __FUNCTION__, __LINE__, xcb_status);
1000
+ }
1001
+ #endif
1002
+
1003
+ #ifdef USE_XKB_COMMON
1004
+ state = create_xkb_state(hook->input.context, hook->input.connection);
1005
+ #endif
1006
+
1007
+ // Initialize starting modifiers.
1008
+ initialize_modifiers();
1009
+
1010
+ status = xrecord_query();
1011
+
1012
+ #ifdef USE_XKB_COMMON
1013
+ if (state != NULL) {
1014
+ destroy_xkb_state(state);
1015
+ }
1016
+
1017
+ if (hook->input.context != NULL) {
1018
+ xkb_context_unref(hook->input.context);
1019
+ hook->input.context = NULL;
1020
+ }
1021
+ #endif
1022
+ } else {
1023
+ logger(LOG_LEVEL_ERROR, "%s [%u]: XOpenDisplay failure!\n",
1024
+ __FUNCTION__, __LINE__);
1025
+
1026
+ status = UIOHOOK_ERROR_X_OPEN_DISPLAY;
1027
+ }
1028
+
1029
+ // Close down the XRecord data display.
1030
+ if (hook->data.display != NULL) {
1031
+ XCloseDisplay(hook->data.display);
1032
+ hook->data.display = NULL;
1033
+ }
1034
+
1035
+ // Close down the XRecord control display.
1036
+ if (hook->ctrl.display) {
1037
+ XCloseDisplay(hook->ctrl.display);
1038
+ hook->ctrl.display = NULL;
1039
+ }
1040
+
1041
+ return status;
1042
+ }
1043
+
1044
+ UIOHOOK_API int hook_run() {
1045
+ // Hook data for future cleanup.
1046
+ hook = malloc(sizeof(hook_info));
1047
+ if (hook == NULL) {
1048
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for hook structure!\n",
1049
+ __FUNCTION__, __LINE__);
1050
+
1051
+ return UIOHOOK_ERROR_OUT_OF_MEMORY;
1052
+ }
1053
+
1054
+ hook->input.mask = 0x0000;
1055
+ hook->input.mouse.is_dragged = false;
1056
+ hook->input.mouse.click.count = 0;
1057
+ hook->input.mouse.click.time = 0;
1058
+ hook->input.mouse.click.button = MOUSE_NOBUTTON;
1059
+
1060
+ int status = xrecord_start();
1061
+
1062
+ // Free data associated with this hook.
1063
+ free(hook);
1064
+ hook = NULL;
1065
+
1066
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Something, something, something, complete.\n",
1067
+ __FUNCTION__, __LINE__);
1068
+
1069
+ return status;
1070
+ }
1071
+
1072
+ UIOHOOK_API int hook_stop() {
1073
+ int status = UIOHOOK_FAILURE;
1074
+
1075
+ if (hook != NULL && hook->ctrl.display != NULL && hook->ctrl.context != 0) {
1076
+ // We need to make sure the context is still valid.
1077
+ XRecordState *state = malloc(sizeof(XRecordState));
1078
+ if (state != NULL) {
1079
+ if (XRecordGetContext(hook->ctrl.display, hook->ctrl.context, &state) != 0) {
1080
+ // Try to exit the thread naturally.
1081
+ if (state->enabled && XRecordDisableContext(hook->ctrl.display, hook->ctrl.context) != 0) {
1082
+ #ifdef USE_XRECORD_ASYNC
1083
+ pthread_mutex_lock(&hook_xrecord_mutex);
1084
+ running = false;
1085
+ pthread_cond_signal(&hook_xrecord_cond);
1086
+ pthread_mutex_unlock(&hook_xrecord_mutex);
1087
+ #endif
1088
+
1089
+ // See Bug 42356 for more information.
1090
+ // https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4
1091
+ //XFlush(hook->ctrl.display);
1092
+ XSync(hook->ctrl.display, False);
1093
+
1094
+ status = UIOHOOK_SUCCESS;
1095
+ }
1096
+ } else {
1097
+ logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordGetContext failure!\n",
1098
+ __FUNCTION__, __LINE__);
1099
+
1100
+ status = UIOHOOK_ERROR_X_RECORD_GET_CONTEXT;
1101
+ }
1102
+
1103
+ free(state);
1104
+ } else {
1105
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for XRecordState!\n",
1106
+ __FUNCTION__, __LINE__);
1107
+
1108
+ status = UIOHOOK_ERROR_OUT_OF_MEMORY;
1109
+ }
1110
+ }
1111
+
1112
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n",
1113
+ __FUNCTION__, __LINE__, status);
1114
+
1115
+ return status;
1116
+ }