@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,303 @@
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 <ApplicationServices/ApplicationServices.h>
20
+ #include <stdbool.h>
21
+ #include <stdio.h>
22
+ #include <stdlib.h>
23
+ #include <uiohook.h>
24
+
25
+ #include "input_helper.h"
26
+ #include "logger.h"
27
+
28
+ static CGEventFlags current_modifier_mask = 0x00;
29
+ static CGEventType current_motion_event = kCGEventMouseMoved;
30
+ static CGMouseButton current_motion_button = 0;
31
+
32
+
33
+ static int post_key_event(uiohook_event * const event, CGEventSourceRef src) {
34
+ bool is_pressed;
35
+
36
+ if (event->type == EVENT_KEY_PRESSED) {
37
+ is_pressed = true;
38
+ switch (event->data.keyboard.keycode) {
39
+ case VC_SHIFT_L:
40
+ case VC_SHIFT_R:
41
+ current_modifier_mask |= kCGEventFlagMaskShift;
42
+ break;
43
+
44
+ case VC_CONTROL_L:
45
+ case VC_CONTROL_R:
46
+ current_modifier_mask |= kCGEventFlagMaskControl;
47
+ break;
48
+
49
+ case VC_META_L:
50
+ case VC_META_R:
51
+ current_modifier_mask |= kCGEventFlagMaskCommand;
52
+ break;
53
+
54
+ case VC_ALT_L:
55
+ case VC_ALT_R:
56
+ current_modifier_mask |= kCGEventFlagMaskAlternate;
57
+ break;
58
+ }
59
+ } else if (event->type == EVENT_KEY_RELEASED) {
60
+ is_pressed = false;
61
+ switch (event->data.keyboard.keycode) {
62
+ case VC_SHIFT_L:
63
+ case VC_SHIFT_R:
64
+ current_modifier_mask &= ~kCGEventFlagMaskShift;
65
+ break;
66
+
67
+ case VC_CONTROL_L:
68
+ case VC_CONTROL_R:
69
+ current_modifier_mask &= ~kCGEventFlagMaskControl;
70
+ break;
71
+
72
+ case VC_META_L:
73
+ case VC_META_R:
74
+ current_modifier_mask &= ~kCGEventFlagMaskCommand;
75
+ break;
76
+
77
+ case VC_ALT_L:
78
+ case VC_ALT_R:
79
+ current_modifier_mask &= ~kCGEventFlagMaskAlternate;
80
+ break;
81
+ }
82
+ } else {
83
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid event for keyboard post event: %#X.\n",
84
+ __FUNCTION__, __LINE__, event->type);
85
+ return UIOHOOK_FAILURE;
86
+ }
87
+
88
+ CGEventFlags event_mask = current_modifier_mask;
89
+ switch (event->data.keyboard.keycode) {
90
+ case VC_KP_0:
91
+ case VC_KP_1:
92
+ case VC_KP_2:
93
+ case VC_KP_3:
94
+ case VC_KP_4:
95
+ case VC_KP_5:
96
+ case VC_KP_6:
97
+ case VC_KP_7:
98
+ case VC_KP_8:
99
+ case VC_KP_9:
100
+
101
+ case VC_NUM_LOCK:
102
+ case VC_KP_ENTER:
103
+ case VC_KP_MULTIPLY:
104
+ case VC_KP_ADD:
105
+ case VC_KP_SEPARATOR:
106
+ case VC_KP_SUBTRACT:
107
+ case VC_KP_DIVIDE:
108
+ case VC_KP_COMMA:
109
+ event_mask |= kCGEventFlagMaskNumericPad;
110
+ break;
111
+ }
112
+
113
+ CGKeyCode keycode = (CGKeyCode) scancode_to_keycode(event->data.keyboard.keycode);
114
+ if (keycode == kVK_Undefined) {
115
+ logger(LOG_LEVEL_WARN, "%s [%u]: Unable to lookup scancode: %li\n",
116
+ __FUNCTION__, __LINE__, event->data.keyboard.keycode);
117
+ return UIOHOOK_FAILURE;
118
+ }
119
+
120
+ CGEventRef cg_event = CGEventCreateKeyboardEvent(
121
+ src,
122
+ keycode,
123
+ is_pressed
124
+ );
125
+
126
+ if (cg_event == NULL) {
127
+ logger(LOG_LEVEL_ERROR, "%s [%u]: CGEventCreateKeyboardEvent failed!\n",
128
+ __FUNCTION__, __LINE__);
129
+
130
+ return UIOHOOK_ERROR_OUT_OF_MEMORY;
131
+ }
132
+
133
+ CGEventSetFlags(cg_event, event_mask);
134
+
135
+ CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works.
136
+ CFRelease(cg_event);
137
+
138
+ return UIOHOOK_SUCCESS;
139
+ }
140
+
141
+ static int post_mouse_event(uiohook_event * const event, CGEventSourceRef src) {
142
+ CGEventType type = kCGEventNull;
143
+ CGMouseButton button = 0;
144
+
145
+ switch (event->type) {
146
+ case EVENT_MOUSE_PRESSED:
147
+ if (event->data.mouse.button == MOUSE_NOBUTTON) {
148
+ // FIXME Warning
149
+ return UIOHOOK_FAILURE;
150
+ } else if (event->data.mouse.button == MOUSE_BUTTON1) {
151
+ type = kCGEventLeftMouseDown;
152
+ button = kCGMouseButtonLeft;
153
+ current_motion_event = kCGEventLeftMouseDragged;
154
+ } else if (event->data.mouse.button == MOUSE_BUTTON2) {
155
+ type = kCGEventRightMouseDown;
156
+ button = kCGMouseButtonRight;
157
+ current_motion_event = kCGEventRightMouseDragged;
158
+ } else {
159
+ type = kCGEventOtherMouseDown;
160
+ button = event->data.mouse.button - 1;
161
+ current_motion_event = kCGEventOtherMouseDragged;
162
+ }
163
+ current_motion_button = button;
164
+ break;
165
+
166
+ case EVENT_MOUSE_RELEASED:
167
+ if (event->data.mouse.button == MOUSE_NOBUTTON) {
168
+ // FIXME Warning
169
+ return UIOHOOK_FAILURE;
170
+ } else if (event->data.mouse.button == MOUSE_BUTTON1) {
171
+ type = kCGEventLeftMouseUp;
172
+ button = kCGMouseButtonLeft;
173
+ if (current_motion_event == kCGEventLeftMouseDragged) {
174
+ current_motion_event = kCGEventMouseMoved;
175
+ }
176
+ } else if (event->data.mouse.button == MOUSE_BUTTON2) {
177
+ type = kCGEventRightMouseUp;
178
+ button = kCGMouseButtonRight;
179
+ if (current_motion_event == kCGEventRightMouseDragged) {
180
+ current_motion_event = kCGEventMouseMoved;
181
+ }
182
+ } else {
183
+ type = kCGEventOtherMouseUp;
184
+ button = event->data.mouse.button - 1;
185
+ if (current_motion_event == kCGEventOtherMouseDragged) {
186
+ current_motion_event = kCGEventMouseMoved;
187
+ }
188
+ }
189
+ current_motion_button = button;
190
+ break;
191
+
192
+ case EVENT_MOUSE_MOVED:
193
+ case EVENT_MOUSE_DRAGGED:
194
+ type = current_motion_event;
195
+ button = current_motion_button;
196
+ break;
197
+
198
+ default:
199
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid mouse event: %#X.\n",
200
+ __FUNCTION__, __LINE__, event->type);
201
+ return UIOHOOK_FAILURE;
202
+ }
203
+
204
+ CGEventRef cg_event = CGEventCreateMouseEvent(
205
+ src,
206
+ type,
207
+ CGPointMake(
208
+ (CGFloat) event->data.mouse.x,
209
+ (CGFloat) event->data.mouse.y
210
+ ),
211
+ button
212
+ );
213
+
214
+ if (cg_event == NULL) {
215
+ logger(LOG_LEVEL_ERROR, "%s [%u]: CGEventCreateMouseEvent failed!\n",
216
+ __FUNCTION__, __LINE__);
217
+ return UIOHOOK_ERROR_OUT_OF_MEMORY;
218
+ }
219
+
220
+ CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works.
221
+ CFRelease(cg_event);
222
+
223
+ return UIOHOOK_SUCCESS;
224
+ }
225
+
226
+ static int post_mouse_wheel_event(uiohook_event * const event, CGEventSourceRef src) {
227
+ // FIXME Should I create a source event with the coords?
228
+ // It seems to automagically use the current location of the cursor.
229
+ // Two options: Query the mouse, move it to x/y, scroll, then move back
230
+ // OR disable x/y for scroll events on Windows & X11.
231
+ CGScrollEventUnit scroll_unit;
232
+ if (event->data.wheel.type == WHEEL_BLOCK_SCROLL) {
233
+ // Scrolling data is line-based.
234
+ scroll_unit = kCGScrollEventUnitLine;
235
+ } else {
236
+ // Scrolling data is pixel-based.
237
+ scroll_unit = kCGScrollEventUnitPixel;
238
+ }
239
+
240
+ CGEventRef cg_event = CGEventCreateScrollWheelEvent(
241
+ src,
242
+ kCGScrollEventUnitLine,
243
+ // TODO Currently only support 1 wheel axis.
244
+ (CGWheelCount) 1, // 1 for Y-only, 2 for Y-X, 3 for Y-X-Z
245
+ event->data.wheel.amount * event->data.wheel.rotation
246
+ );
247
+
248
+ if (cg_event == NULL) {
249
+ logger(LOG_LEVEL_ERROR, "%s [%u]: CGEventCreateScrollWheelEvent failed!\n",
250
+ __FUNCTION__, __LINE__);
251
+ return UIOHOOK_ERROR_OUT_OF_MEMORY;
252
+ }
253
+
254
+ CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works.
255
+ CFRelease(cg_event);
256
+
257
+ return UIOHOOK_SUCCESS;
258
+ }
259
+
260
+
261
+ // TODO This should return a status code, UIOHOOK_SUCCESS or otherwise.
262
+ UIOHOOK_API void hook_post_event(uiohook_event * const event) {
263
+ int status = UIOHOOK_FAILURE;
264
+
265
+ CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
266
+ if (src == NULL) {
267
+ logger(LOG_LEVEL_ERROR, "%s [%u]: CGEventSourceCreate failed!\n",
268
+ __FUNCTION__, __LINE__);
269
+ return; // UIOHOOK_ERROR_OUT_OF_MEMORY
270
+ }
271
+
272
+ switch (event->type) {
273
+ case EVENT_KEY_PRESSED:
274
+ case EVENT_KEY_RELEASED:
275
+ status = post_key_event(event, src);
276
+ break;
277
+
278
+
279
+ case EVENT_MOUSE_PRESSED:
280
+ case EVENT_MOUSE_RELEASED:
281
+
282
+ case EVENT_MOUSE_MOVED:
283
+ case EVENT_MOUSE_DRAGGED:
284
+ status = post_mouse_event(event, src);
285
+ break;
286
+
287
+ case EVENT_MOUSE_WHEEL:
288
+ status = post_mouse_wheel_event(event, src);
289
+ break;
290
+
291
+ case EVENT_KEY_TYPED:
292
+ case EVENT_MOUSE_CLICKED:
293
+
294
+ case EVENT_HOOK_ENABLED:
295
+ case EVENT_HOOK_DISABLED:
296
+
297
+ default:
298
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Ignoring post event: %#X.\n",
299
+ __FUNCTION__, __LINE__, event->type);
300
+ }
301
+
302
+ CFRelease(src);
303
+ }