@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,427 @@
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 <stdbool.h>
20
+ #include <stdio.h>
21
+ #include <stdlib.h>
22
+ #include <uiohook.h>
23
+ #include <X11/Xlib.h>
24
+ #include <X11/Xutil.h>
25
+ #ifdef USE_XTEST
26
+ #include <X11/extensions/XTest.h>
27
+ #endif
28
+
29
+ #include "input_helper.h"
30
+ #include "logger.h"
31
+
32
+ #ifndef USE_XTEST
33
+ static long current_modifier_mask = NoEventMask;
34
+ #endif
35
+
36
+ static int post_key_event(uiohook_event * const event) {
37
+ KeyCode keycode = scancode_to_keycode(event->data.keyboard.keycode);
38
+ if (keycode == 0x0000) {
39
+ logger(LOG_LEVEL_WARN, "%s [%u]: Unable to lookup scancode: %li\n",
40
+ __FUNCTION__, __LINE__, event->data.keyboard.keycode);
41
+ return UIOHOOK_FAILURE;
42
+ }
43
+
44
+ #ifdef USE_XTEST
45
+ Bool is_pressed;
46
+ #else
47
+ long event_mask;
48
+
49
+ XKeyEvent key_event = {
50
+ .serial = 0x00,
51
+ .time = CurrentTime,
52
+ .same_screen = True,
53
+ .send_event = False,
54
+ .display = helper_disp,
55
+
56
+ .root = XDefaultRootWindow(helper_disp),
57
+ .window = None,
58
+ .subwindow = None,
59
+
60
+ .x_root = 0,
61
+ .y_root = 0,
62
+ .x = 0,
63
+ .y = 0,
64
+
65
+ .state = current_modifier_mask,
66
+ .keycode = keycode
67
+ };
68
+
69
+ int revert;
70
+ XGetInputFocus(helper_disp, &(key_event.window), &revert);
71
+ #endif
72
+
73
+ if (event->type == EVENT_KEY_PRESSED) {
74
+ #ifdef USE_XTEST
75
+ is_pressed = True;
76
+ #else
77
+ key_event.type = KeyPress;
78
+ event_mask = KeyPressMask;
79
+
80
+ switch (event->data.keyboard.keycode) {
81
+ case VC_SHIFT_L:
82
+ case VC_SHIFT_R:
83
+ current_modifier_mask |= ShiftMask;
84
+ break;
85
+
86
+ case VC_CONTROL_L:
87
+ case VC_CONTROL_R:
88
+ current_modifier_mask |= ControlMask;
89
+ break;
90
+
91
+ case VC_META_L:
92
+ case VC_META_R:
93
+ current_modifier_mask |= Mod4Mask;
94
+ break;
95
+
96
+ case VC_ALT_L:
97
+ case VC_ALT_R:
98
+ current_modifier_mask |= Mod1Mask;
99
+ break;
100
+ }
101
+ #endif
102
+
103
+ } else if (event->type == EVENT_KEY_RELEASED) {
104
+ #ifdef USE_XTEST
105
+ is_pressed = False;
106
+ #else
107
+ key_event.type = KeyRelease;
108
+ event_mask = KeyReleaseMask;
109
+
110
+ switch (event->data.keyboard.keycode) {
111
+ case VC_SHIFT_L:
112
+ case VC_SHIFT_R:
113
+ current_modifier_mask &= ~ShiftMask;
114
+ break;
115
+
116
+ case VC_CONTROL_L:
117
+ case VC_CONTROL_R:
118
+ current_modifier_mask &= ~ControlMask;
119
+ break;
120
+
121
+ case VC_META_L:
122
+ case VC_META_R:
123
+ current_modifier_mask &= ~Mod4Mask;
124
+ break;
125
+
126
+ case VC_ALT_L:
127
+ case VC_ALT_R:
128
+ current_modifier_mask &= ~Mod1Mask;
129
+ break;
130
+ }
131
+ #endif
132
+ } else {
133
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid event for keyboard post event: %#X.\n",
134
+ __FUNCTION__, __LINE__, event->type);
135
+ return UIOHOOK_FAILURE;
136
+ }
137
+
138
+ #ifdef USE_XTEST
139
+ if (XTestFakeKeyEvent(helper_disp, keycode, is_pressed, 0) != Success) {
140
+ logger(LOG_LEVEL_ERROR, "%s [%u]: XTestFakeKeyEvent() failed!\n",
141
+ __FUNCTION__, __LINE__, event->type);
142
+ return UIOHOOK_FAILURE;
143
+ }
144
+ #else
145
+ XSelectInput(helper_disp, key_event.window, KeyPressMask | KeyReleaseMask);
146
+ if (XSendEvent(helper_disp, key_event.window, False, event_mask, (XEvent *) &key_event) == 0) {
147
+ logger(LOG_LEVEL_ERROR, "%s [%u]: XSendEvent() failed!\n",
148
+ __FUNCTION__, __LINE__, event->type);
149
+ return UIOHOOK_FAILURE;
150
+ }
151
+ #endif
152
+
153
+ return UIOHOOK_SUCCESS;
154
+ }
155
+
156
+ static int post_mouse_button_event(uiohook_event * const event) {
157
+ XButtonEvent btn_event = {
158
+ .serial = 0,
159
+ .send_event = False,
160
+ .display = helper_disp,
161
+
162
+ .window = None, /* “event” window it is reported relative to */
163
+ .root = None, /* root window that the event occurred on */
164
+ .subwindow = XDefaultRootWindow(helper_disp), /* child window */
165
+
166
+ .time = CurrentTime,
167
+
168
+ .x = event->data.mouse.x, /* pointer x, y coordinates in event window */
169
+ .y = event->data.mouse.y,
170
+
171
+ .x_root = 0, /* coordinates relative to root */
172
+ .y_root = 0,
173
+
174
+ .state = 0x00, /* key or button mask */
175
+ .same_screen = True
176
+ };
177
+
178
+ // Move the pointer to the specified position.
179
+ #ifdef USE_XTEST
180
+ XTestFakeMotionEvent(btn_event.display, -1, btn_event.x, btn_event.y, 0);
181
+ #else
182
+ XWarpPointer(btn_event.display, None, btn_event.subwindow, 0, 0, 0, 0, btn_event.x, btn_event.y);
183
+ XFlush(btn_event.display);
184
+ #endif
185
+
186
+ #ifndef USE_XTEST
187
+ // FIXME This is still not working correctly, clicking on other windows does not yield focus.
188
+ while (btn_event.subwindow != None)
189
+ {
190
+ btn_event.window = btn_event.subwindow;
191
+ XQueryPointer (
192
+ btn_event.display,
193
+ btn_event.window,
194
+ &btn_event.root,
195
+ &btn_event.subwindow,
196
+ &btn_event.x_root,
197
+ &btn_event.y_root,
198
+ &btn_event.x,
199
+ &btn_event.y,
200
+ &btn_event.state
201
+ );
202
+ }
203
+ #endif
204
+
205
+ switch (event->type) {
206
+ case EVENT_MOUSE_PRESSED:
207
+ #ifdef USE_XTEST
208
+ if (event->data.mouse.button < MOUSE_BUTTON1 || event->data.mouse.button > MOUSE_BUTTON5) {
209
+ logger(LOG_LEVEL_WARN, "%s [%u]: Invalid button specified for mouse pressed event! (%u)\n",
210
+ __FUNCTION__, __LINE__, event->data.mouse.button);
211
+ return UIOHOOK_FAILURE;
212
+ }
213
+
214
+ XTestFakeButtonEvent(helper_disp, event->data.mouse.button, True, 0);
215
+ #else
216
+ if (event->data.mouse.button == MOUSE_BUTTON1) {
217
+ current_modifier_mask |= Button1MotionMask;
218
+ } else if (event->data.mouse.button == MOUSE_BUTTON2) {
219
+ current_modifier_mask |= Button2MotionMask;
220
+ } else if (event->data.mouse.button == MOUSE_BUTTON3) {
221
+ current_modifier_mask |= Button3MotionMask;
222
+ } else if (event->data.mouse.button == MOUSE_BUTTON4) {
223
+ current_modifier_mask |= Button4MotionMask;
224
+ } else if (event->data.mouse.button == MOUSE_BUTTON5) {
225
+ current_modifier_mask |= Button5MotionMask;
226
+ } else {
227
+ logger(LOG_LEVEL_WARN, "%s [%u]: Invalid button specified for mouse pressed event! (%u)\n",
228
+ __FUNCTION__, __LINE__, event->data.mouse.button);
229
+ return UIOHOOK_FAILURE;
230
+ }
231
+
232
+ btn_event.type = ButtonPress;
233
+ btn_event.button = event->data.mouse.button;
234
+ btn_event.state = current_modifier_mask;
235
+ XSendEvent(helper_disp, btn_event.window, False, ButtonPressMask, (XEvent *) &btn_event);
236
+ #endif
237
+ break;
238
+
239
+ case EVENT_MOUSE_RELEASED:
240
+ #ifdef USE_XTEST
241
+ if (event->data.mouse.button < MOUSE_BUTTON1 || event->data.mouse.button > MOUSE_BUTTON5) {
242
+ logger(LOG_LEVEL_WARN, "%s [%u]: Invalid button specified for mouse released event! (%u)\n",
243
+ __FUNCTION__, __LINE__, event->data.mouse.button);
244
+ return UIOHOOK_FAILURE;
245
+ }
246
+
247
+ XTestFakeButtonEvent(helper_disp, event->data.mouse.button, False, 0);
248
+ #else
249
+ if (event->data.mouse.button == MOUSE_BUTTON1) {
250
+ current_modifier_mask &= ~Button1MotionMask;
251
+ } else if (event->data.mouse.button == MOUSE_BUTTON2) {
252
+ current_modifier_mask &= ~Button2MotionMask;
253
+ } else if (event->data.mouse.button == MOUSE_BUTTON3) {
254
+ current_modifier_mask &= ~Button3MotionMask;
255
+ } else if (event->data.mouse.button == MOUSE_BUTTON4) {
256
+ current_modifier_mask &= ~Button4MotionMask;
257
+ } else if (event->data.mouse.button == MOUSE_BUTTON5) {
258
+ current_modifier_mask &= ~Button5MotionMask;
259
+ } else {
260
+ logger(LOG_LEVEL_WARN, "%s [%u]: Invalid button specified for mouse released event! (%u)\n",
261
+ __FUNCTION__, __LINE__, event->data.mouse.button);
262
+ return UIOHOOK_FAILURE;
263
+ }
264
+
265
+ btn_event.type = ButtonRelease;
266
+ btn_event.button = event->data.mouse.button;
267
+ btn_event.state = current_modifier_mask;
268
+ XSendEvent(helper_disp, btn_event.window, False, ButtonReleaseMask, (XEvent *) &btn_event);
269
+ #endif
270
+ break;
271
+
272
+ default:
273
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid mouse button event: %#X.\n",
274
+ __FUNCTION__, __LINE__, event->type);
275
+ return UIOHOOK_FAILURE;
276
+ }
277
+
278
+ return UIOHOOK_SUCCESS;
279
+ }
280
+
281
+ static int post_mouse_wheel_event(uiohook_event * const event) {
282
+ XButtonEvent btn_event = {
283
+ .serial = 0,
284
+ .send_event = False,
285
+ .display = helper_disp,
286
+
287
+ .window = None, /* “event” window it is reported relative to */
288
+ .root = None, /* root window that the event occurred on */
289
+ .subwindow = XDefaultRootWindow(helper_disp), /* child window */
290
+
291
+ .time = CurrentTime,
292
+
293
+ .x = event->data.wheel.x, /* pointer x, y coordinates in event window */
294
+ .y = event->data.wheel.y,
295
+
296
+ .x_root = 0, /* coordinates relative to root */
297
+ .y_root = 0,
298
+
299
+ .state = 0x00, /* key or button mask */
300
+ .same_screen = True
301
+ };
302
+
303
+ #ifndef USE_XTEST
304
+ // FIXME This is still not working correctly, clicking on other windows does not yield focus.
305
+ while (btn_event.subwindow != None)
306
+ {
307
+ btn_event.window = btn_event.subwindow;
308
+ XQueryPointer (
309
+ btn_event.display,
310
+ btn_event.window,
311
+ &btn_event.root,
312
+ &btn_event.subwindow,
313
+ &btn_event.x_root,
314
+ &btn_event.y_root,
315
+ &btn_event.x,
316
+ &btn_event.y,
317
+ &btn_event.state
318
+ );
319
+ }
320
+ #endif
321
+
322
+ // Wheel events should be the same as click events on X11.
323
+ // type, amount and rotation
324
+ unsigned int button = button_map_lookup(event->data.wheel.rotation < 0 ? WheelUp : WheelDown);
325
+
326
+ #ifdef USE_XTEST
327
+ XTestFakeButtonEvent(helper_disp, button, True, 0);
328
+ #else
329
+ btn_event.type = ButtonPress;
330
+ btn_event.button = button;
331
+ btn_event.state = current_modifier_mask;
332
+ XSendEvent(helper_disp, btn_event.window, False, ButtonPressMask, (XEvent *) &btn_event);
333
+ #endif
334
+
335
+ #ifdef USE_XTEST
336
+ XTestFakeButtonEvent(helper_disp, button, False, 0);
337
+ #else
338
+ btn_event.type = ButtonRelease;
339
+ btn_event.button = button;
340
+ btn_event.state = current_modifier_mask;
341
+ XSendEvent(helper_disp, btn_event.window, False, ButtonReleaseMask, (XEvent *) &btn_event);
342
+ #endif
343
+
344
+ return UIOHOOK_SUCCESS;
345
+ }
346
+
347
+ static void post_mouse_motion_event(uiohook_event * const event) {
348
+ #ifdef USE_XTEST
349
+ XTestFakeMotionEvent(helper_disp, -1, event->data.mouse.x, event->data.mouse.y, 0);
350
+ #else
351
+ XMotionEvent mov_event = {
352
+ .type = MotionNotify,
353
+ .serial = 0,
354
+ .send_event = False,
355
+ .display = helper_disp,
356
+
357
+ .window = None, /* “event” window it is reported relative to */
358
+ .root = XDefaultRootWindow(helper_disp), /* root window that the event occurred on */
359
+ .subwindow = None, /* child window */
360
+
361
+ .time = CurrentTime,
362
+
363
+ .x = event->data.mouse.x, /* pointer x, y coordinates in event window */
364
+ .y = event->data.mouse.y,
365
+
366
+ .x_root = event->data.mouse.x, /* coordinates relative to root */
367
+ .y_root = event->data.mouse.x,
368
+
369
+ .state = current_modifier_mask|MotionNotify, /* key or button mask */
370
+
371
+ .is_hint = NotifyNormal,
372
+ .same_screen = True
373
+ };
374
+
375
+ int revert;
376
+ XGetInputFocus(helper_disp, &(mov_event.window), &revert);
377
+
378
+ XSendEvent(helper_disp, mov_event.window, False, mov_event.state, (XEvent *) &mov_event);
379
+ #endif
380
+ }
381
+
382
+ // TODO This should return a status code, UIOHOOK_SUCCESS or otherwise.
383
+ UIOHOOK_API void hook_post_event(uiohook_event * const event) {
384
+ if (helper_disp == NULL) {
385
+ logger(LOG_LEVEL_ERROR, "%s [%u]: XDisplay helper_disp is unavailable!\n",
386
+ __FUNCTION__, __LINE__);
387
+ return; // UIOHOOK_ERROR_X_OPEN_DISPLAY
388
+ }
389
+
390
+ XLockDisplay(helper_disp);
391
+
392
+ switch (event->type) {
393
+ case EVENT_KEY_PRESSED:
394
+ case EVENT_KEY_RELEASED:
395
+ post_key_event(event);
396
+ break;
397
+
398
+ case EVENT_MOUSE_PRESSED:
399
+ case EVENT_MOUSE_RELEASED:
400
+ post_mouse_button_event(event);
401
+ break;
402
+
403
+ case EVENT_MOUSE_WHEEL:
404
+ post_mouse_wheel_event(event);
405
+ break;
406
+
407
+ case EVENT_MOUSE_MOVED:
408
+ case EVENT_MOUSE_DRAGGED:
409
+ post_mouse_motion_event(event);
410
+ break;
411
+
412
+ case EVENT_KEY_TYPED:
413
+ case EVENT_MOUSE_CLICKED:
414
+
415
+ case EVENT_HOOK_ENABLED:
416
+ case EVENT_HOOK_DISABLED:
417
+
418
+ default:
419
+ logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n",
420
+ __FUNCTION__, __LINE__, event->type);
421
+ break;
422
+ }
423
+
424
+ // Don't forget to flush!
425
+ XSync(helper_disp, True);
426
+ XUnlockDisplay(helper_disp);
427
+ }