@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,337 @@
1
+ #include <stdlib.h>
2
+ #include <string.h>
3
+ #include <node_api.h>
4
+ #include <uiohook.h>
5
+ #include "napi_helpers.h"
6
+ #include "uiohook_worker.h"
7
+
8
+ static napi_threadsafe_function threadsafe_fn = NULL;
9
+ static bool is_worker_running = false;
10
+
11
+ void dispatch_proc(uiohook_event* const event) {
12
+ if (threadsafe_fn == NULL) return;
13
+
14
+ uiohook_event* copied_event = malloc(sizeof(uiohook_event));
15
+ memcpy(copied_event, event, sizeof(uiohook_event));
16
+ if (copied_event->type == EVENT_MOUSE_DRAGGED) {
17
+ copied_event->type = EVENT_MOUSE_MOVED;
18
+ }
19
+
20
+ napi_status status = napi_call_threadsafe_function(threadsafe_fn, copied_event, napi_tsfn_nonblocking);
21
+ if (status == napi_closing) {
22
+ threadsafe_fn = NULL;
23
+ free(copied_event);
24
+ return;
25
+ }
26
+ NAPI_FATAL_IF_FAILED(status, "dispatch_proc", "napi_call_threadsafe_function");
27
+ }
28
+
29
+ napi_value uiohook_to_js_event(napi_env env, uiohook_event* event) {
30
+ napi_status status;
31
+
32
+ napi_value event_obj;
33
+ status = napi_create_object(env, &event_obj);
34
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_object");
35
+
36
+ napi_value e_type;
37
+ status = napi_create_uint32(env, event->type, &e_type);
38
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32");
39
+
40
+ napi_value e_altKey;
41
+ status = napi_get_boolean(env, (event->mask & (MASK_ALT)), &e_altKey);
42
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_get_boolean");
43
+
44
+ napi_value e_ctrlKey;
45
+ status = napi_get_boolean(env, (event->mask & (MASK_CTRL)), &e_ctrlKey);
46
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_get_boolean");
47
+
48
+ napi_value e_metaKey;
49
+ status = napi_get_boolean(env, (event->mask & (MASK_META)), &e_metaKey);
50
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_get_boolean");
51
+
52
+ napi_value e_shiftKey;
53
+ status = napi_get_boolean(env, (event->mask & (MASK_SHIFT)), &e_shiftKey);
54
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_get_boolean");
55
+
56
+ napi_value e_time;
57
+ status = napi_create_double(env, (double)event->time, &e_time);
58
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_double");
59
+
60
+ if (event->type == EVENT_KEY_PRESSED || event->type == EVENT_KEY_RELEASED) {
61
+ napi_value e_keycode;
62
+ status = napi_create_uint32(env, event->data.keyboard.keycode, &e_keycode);
63
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32");
64
+
65
+ napi_property_descriptor descriptors[] = {
66
+ { "type", NULL, NULL, NULL, NULL, e_type, napi_enumerable, NULL },
67
+ { "time", NULL, NULL, NULL, NULL, e_time, napi_enumerable, NULL },
68
+ { "altKey", NULL, NULL, NULL, NULL, e_altKey, napi_enumerable, NULL },
69
+ { "ctrlKey", NULL, NULL, NULL, NULL, e_ctrlKey, napi_enumerable, NULL },
70
+ { "metaKey", NULL, NULL, NULL, NULL, e_metaKey, napi_enumerable, NULL },
71
+ { "shiftKey", NULL, NULL, NULL, NULL, e_shiftKey, napi_enumerable, NULL },
72
+ { "keycode", NULL, NULL, NULL, NULL, e_keycode, napi_enumerable, NULL },
73
+ };
74
+ status = napi_define_properties(env, event_obj, sizeof(descriptors) / sizeof(descriptors[0]), descriptors);
75
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_define_properties");
76
+ return event_obj;
77
+ }
78
+ else if (event->type == EVENT_MOUSE_MOVED || event->type == EVENT_MOUSE_PRESSED || event->type == EVENT_MOUSE_RELEASED || event->type == EVENT_MOUSE_CLICKED) {
79
+ napi_value e_x;
80
+ status = napi_create_int32(env, event->data.mouse.x, &e_x);
81
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32");
82
+
83
+ napi_value e_y;
84
+ status = napi_create_int32(env, event->data.mouse.y, &e_y);
85
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32");
86
+
87
+ napi_value e_button;
88
+ status = napi_create_uint32(env, event->data.mouse.button, &e_button);
89
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32");
90
+
91
+ napi_value e_clicks;
92
+ status = napi_create_uint32(env, event->data.mouse.clicks, &e_clicks);
93
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32");
94
+
95
+ napi_property_descriptor descriptors[] = {
96
+ { "type", NULL, NULL, NULL, NULL, e_type, napi_enumerable, NULL },
97
+ { "time", NULL, NULL, NULL, NULL, e_time, napi_enumerable, NULL },
98
+ { "altKey", NULL, NULL, NULL, NULL, e_altKey, napi_enumerable, NULL },
99
+ { "ctrlKey", NULL, NULL, NULL, NULL, e_ctrlKey, napi_enumerable, NULL },
100
+ { "metaKey", NULL, NULL, NULL, NULL, e_metaKey, napi_enumerable, NULL },
101
+ { "shiftKey", NULL, NULL, NULL, NULL, e_shiftKey, napi_enumerable, NULL },
102
+ { "x", NULL, NULL, NULL, NULL, e_x, napi_enumerable, NULL },
103
+ { "y", NULL, NULL, NULL, NULL, e_y, napi_enumerable, NULL },
104
+ { "button", NULL, NULL, NULL, NULL, e_button, napi_enumerable, NULL },
105
+ { "clicks", NULL, NULL, NULL, NULL, e_clicks, napi_enumerable, NULL },
106
+ };
107
+ status = napi_define_properties(env, event_obj, sizeof(descriptors) / sizeof(descriptors[0]), descriptors);
108
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_define_properties");
109
+ return event_obj;
110
+ }
111
+ else if (event->type == EVENT_MOUSE_WHEEL) {
112
+ napi_value e_x;
113
+ status = napi_create_int32(env, event->data.wheel.x, &e_x);
114
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32");
115
+
116
+ napi_value e_y;
117
+ status = napi_create_int32(env, event->data.wheel.y, &e_y);
118
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32");
119
+
120
+ napi_value e_clicks;
121
+ status = napi_create_uint32(env, event->data.wheel.clicks, &e_clicks);
122
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32");
123
+
124
+ napi_value e_amount;
125
+ status = napi_create_uint32(env, event->data.wheel.amount, &e_amount);
126
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32");
127
+
128
+ napi_value e_direction;
129
+ status = napi_create_uint32(env, event->data.wheel.direction, &e_direction);
130
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32");
131
+
132
+ napi_value e_rotation;
133
+ status = napi_create_int32(env, event->data.wheel.rotation, &e_rotation);
134
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32");
135
+
136
+ napi_property_descriptor descriptors[] = {
137
+ { "type", NULL, NULL, NULL, NULL, e_type, napi_enumerable, NULL },
138
+ { "time", NULL, NULL, NULL, NULL, e_time, napi_enumerable, NULL },
139
+ { "altKey", NULL, NULL, NULL, NULL, e_altKey, napi_enumerable, NULL },
140
+ { "ctrlKey", NULL, NULL, NULL, NULL, e_ctrlKey, napi_enumerable, NULL },
141
+ { "metaKey", NULL, NULL, NULL, NULL, e_metaKey, napi_enumerable, NULL },
142
+ { "shiftKey", NULL, NULL, NULL, NULL, e_shiftKey, napi_enumerable, NULL },
143
+ { "x", NULL, NULL, NULL, NULL, e_x, napi_enumerable, NULL },
144
+ { "y", NULL, NULL, NULL, NULL, e_y, napi_enumerable, NULL },
145
+ { "clicks", NULL, NULL, NULL, NULL, e_clicks, napi_enumerable, NULL },
146
+ { "amount", NULL, NULL, NULL, NULL, e_amount, napi_enumerable, NULL },
147
+ { "direction", NULL, NULL, NULL, NULL, e_direction, napi_enumerable, NULL },
148
+ { "rotation", NULL, NULL, NULL, NULL, e_rotation, napi_enumerable, NULL },
149
+ };
150
+ status = napi_define_properties(env, event_obj, sizeof(descriptors) / sizeof(descriptors[0]), descriptors);
151
+ NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_define_properties");
152
+ return event_obj;
153
+ }
154
+
155
+ return NULL; // never
156
+ }
157
+
158
+ void tsfn_to_js_proxy(napi_env env, napi_value js_callback, void* context, void* _event) {
159
+ uiohook_event* event = (uiohook_event*)_event;
160
+
161
+ if (env == NULL || js_callback == NULL || is_worker_running == false) {
162
+ free(event);
163
+ return;
164
+ }
165
+
166
+ napi_status status;
167
+
168
+ napi_value event_obj = uiohook_to_js_event(env, event);
169
+
170
+ napi_value global;
171
+ status = napi_get_global(env, &global);
172
+ NAPI_FATAL_IF_FAILED(status, "tsfn_to_js_proxy", "napi_get_global");
173
+
174
+ status = napi_call_function(env, global, js_callback, 1, &event_obj, NULL);
175
+ NAPI_FATAL_IF_FAILED(status, "tsfn_to_js_proxy", "napi_call_function");
176
+
177
+ free(event);
178
+ }
179
+
180
+ napi_value AddonStart(napi_env env, napi_callback_info info) {
181
+ if (is_worker_running == true)
182
+ return NULL;
183
+
184
+ napi_status status;
185
+
186
+ size_t info_argc = 1;
187
+ napi_value info_argv[1];
188
+ status = napi_get_cb_info(env, info, &info_argc, info_argv, NULL, NULL);
189
+ NAPI_THROW_IF_FAILED(env, status, NULL);
190
+
191
+ napi_value cb = info_argv[0];
192
+
193
+ napi_value async_resource_name;
194
+ status = napi_create_string_utf8(env, "UIOHOOK_NAPI", NAPI_AUTO_LENGTH, &async_resource_name);
195
+ NAPI_THROW_IF_FAILED(env, status, NULL);
196
+
197
+ status = napi_create_threadsafe_function(env, cb, NULL, async_resource_name, 0, 1, NULL, NULL, NULL, tsfn_to_js_proxy, &threadsafe_fn);
198
+ NAPI_THROW_IF_FAILED(env, status, NULL);
199
+
200
+ int worker_status = uiohook_worker_start(dispatch_proc);
201
+
202
+ if (worker_status != UIOHOOK_SUCCESS) {
203
+ napi_release_threadsafe_function(threadsafe_fn, napi_tsfn_release);
204
+ threadsafe_fn = NULL;
205
+ }
206
+
207
+ switch (worker_status) {
208
+ case UIOHOOK_SUCCESS: {
209
+ is_worker_running = true;
210
+ return NULL;
211
+ }
212
+ case UIOHOOK_ERROR_THREAD_CREATE:
213
+ NAPI_THROW(env, "UIOHOOK_ERROR_THREAD_CREATE", "Failed to create worker thread.", NULL);
214
+ case UIOHOOK_ERROR_OUT_OF_MEMORY:
215
+ NAPI_THROW(env, "UIOHOOK_ERROR_OUT_OF_MEMORY", "Failed to allocate memory.", NULL);
216
+ case UIOHOOK_ERROR_X_OPEN_DISPLAY:
217
+ NAPI_THROW(env, "UIOHOOK_ERROR_X_OPEN_DISPLAY", "Failed to open X11 display.", NULL);
218
+ case UIOHOOK_ERROR_X_RECORD_NOT_FOUND:
219
+ NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_NOT_FOUND", "Unable to locate XRecord extension.", NULL);
220
+ case UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE:
221
+ NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE", "Unable to allocate XRecord range.", NULL);
222
+ case UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT:
223
+ NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT", "Unable to allocate XRecord context.", NULL);
224
+ case UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT:
225
+ NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT", "Failed to enable XRecord context.", NULL);
226
+ case UIOHOOK_ERROR_SET_WINDOWS_HOOK_EX:
227
+ NAPI_THROW(env, "UIOHOOK_ERROR_SET_WINDOWS_HOOK_EX", "Failed to register low level windows hook.", NULL);
228
+ case UIOHOOK_ERROR_AXAPI_DISABLED:
229
+ NAPI_THROW(env, "UIOHOOK_ERROR_AXAPI_DISABLED", "Failed to enable access for assistive devices.", NULL);
230
+ case UIOHOOK_ERROR_CREATE_EVENT_PORT:
231
+ NAPI_THROW(env, "UIOHOOK_ERROR_CREATE_EVENT_PORT", "Failed to create apple event port.", NULL);
232
+ case UIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE:
233
+ NAPI_THROW(env, "UIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE", "Failed to create apple run loop source.", NULL);
234
+ case UIOHOOK_ERROR_GET_RUNLOOP:
235
+ NAPI_THROW(env, "UIOHOOK_ERROR_GET_RUNLOOP", "Failed to acquire apple run loop.", NULL);
236
+ case UIOHOOK_ERROR_CREATE_OBSERVER:
237
+ NAPI_THROW(env, "UIOHOOK_ERROR_CREATE_OBSERVER", "Failed to create apple run loop observer.", NULL);
238
+ case UIOHOOK_FAILURE:
239
+ default:
240
+ NAPI_THROW(env, "UIOHOOK_FAILURE", "An unknown hook error occurred.", NULL);
241
+ }
242
+ }
243
+
244
+ napi_value AddonStop(napi_env env, napi_callback_info info) {
245
+ if (is_worker_running == false)
246
+ return NULL;
247
+
248
+ int status = uiohook_worker_stop();
249
+
250
+ switch (status) {
251
+ case UIOHOOK_SUCCESS: {
252
+ is_worker_running = false;
253
+ napi_release_threadsafe_function(threadsafe_fn, napi_tsfn_release);
254
+ threadsafe_fn = NULL;
255
+ return NULL;
256
+ }
257
+ case UIOHOOK_ERROR_OUT_OF_MEMORY:
258
+ NAPI_THROW(env, "UIOHOOK_ERROR_OUT_OF_MEMORY", "Failed to allocate memory.", NULL);
259
+ case UIOHOOK_ERROR_X_RECORD_GET_CONTEXT:
260
+ NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_GET_CONTEXT", "Failed to get XRecord context.", NULL);
261
+ case UIOHOOK_FAILURE:
262
+ default:
263
+ NAPI_THROW(env, "UIOHOOK_FAILURE", "An unknown hook error occurred.", NULL);
264
+ }
265
+ }
266
+
267
+ void AddonCleanUp (void* arg) {
268
+ if (is_worker_running) {
269
+ uiohook_worker_stop();
270
+ }
271
+ }
272
+
273
+ typedef enum {
274
+ key_tap,
275
+ key_down,
276
+ key_up,
277
+ force_uint = 0xFFFFFFFF
278
+ } key_tap_type;
279
+
280
+ napi_value AddonKeyTap (napi_env env, napi_callback_info info) {
281
+ napi_status status;
282
+
283
+ size_t info_argc = 2;
284
+ napi_value info_argv[2];
285
+ status = napi_get_cb_info(env, info, &info_argc, info_argv, NULL, NULL);
286
+ NAPI_THROW_IF_FAILED(env, status, NULL);
287
+
288
+ // [0] KeyCode
289
+ uint32_t keycode;
290
+ status = napi_get_value_uint32(env, info_argv[0], &keycode);
291
+ NAPI_THROW_IF_FAILED(env, status, NULL);
292
+
293
+ // [1] KeyTapType
294
+ key_tap_type tap_type;
295
+ status = napi_get_value_uint32(env, info_argv[1], (int32_t*)&tap_type);
296
+ NAPI_THROW_IF_FAILED(env, status, NULL);
297
+
298
+ uiohook_event event;
299
+ memset(&event, 0, sizeof(event));
300
+ event.data.keyboard.keycode = keycode;
301
+
302
+ if (tap_type != key_up) {
303
+ event.type = EVENT_KEY_PRESSED;
304
+ hook_post_event(&event);
305
+ }
306
+ if (tap_type != key_down) {
307
+ event.type = EVENT_KEY_RELEASED;
308
+ hook_post_event(&event);
309
+ }
310
+
311
+ return NULL;
312
+ }
313
+
314
+ NAPI_MODULE_INIT() {
315
+ napi_status status;
316
+ napi_value export_fn;
317
+
318
+ status = napi_create_function(env, NULL, 0, AddonStart, NULL, &export_fn);
319
+ NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_create_function");
320
+ status = napi_set_named_property(env, exports, "start", export_fn);
321
+ NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_set_named_property");
322
+
323
+ status = napi_create_function(env, NULL, 0, AddonStop, NULL, &export_fn);
324
+ NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_create_function");
325
+ status = napi_set_named_property(env, exports, "stop", export_fn);
326
+ NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_set_named_property");
327
+
328
+ status = napi_create_function(env, NULL, 0, AddonKeyTap, NULL, &export_fn);
329
+ NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_create_function");
330
+ status = napi_set_named_property(env, exports, "keyTap", export_fn);
331
+ NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_set_named_property");
332
+
333
+ status = napi_add_env_cleanup_hook(env, AddonCleanUp, NULL);
334
+ NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_add_env_cleanup_hook");
335
+
336
+ return exports;
337
+ }
@@ -0,0 +1,51 @@
1
+ #include <string.h>
2
+ #include "napi_helpers.h"
3
+
4
+ napi_value error_create(napi_env env) {
5
+ napi_status status;
6
+ napi_value error = NULL;
7
+ bool is_exception_pending;
8
+ const napi_extended_error_info* info;
9
+
10
+ // We must retrieve the last error info before doing anything else, because
11
+ // doing anything else will replace the last error info.
12
+ status = napi_get_last_error_info(env, &info);
13
+ NAPI_FATAL_IF_FAILED(status, "error_create", "napi_get_last_error_info");
14
+
15
+ status = napi_is_exception_pending(env, &is_exception_pending);
16
+ NAPI_FATAL_IF_FAILED(status, "error_create", "napi_is_exception_pending");
17
+
18
+ // A pending exception takes precedence over any internal error status.
19
+ if (is_exception_pending) {
20
+ status = napi_get_and_clear_last_exception(env, &error);
21
+ NAPI_FATAL_IF_FAILED(status, "error_create", "napi_get_and_clear_last_exception");
22
+ }
23
+ else {
24
+ const char* error_message = info->error_message != NULL ?
25
+ info->error_message : "Error in native callback";
26
+
27
+ napi_value message;
28
+ status = napi_create_string_utf8(
29
+ env,
30
+ error_message,
31
+ strlen(error_message),
32
+ &message);
33
+ NAPI_FATAL_IF_FAILED(status, "error_create", "napi_create_string_utf8");
34
+
35
+ switch (info->error_code) {
36
+ case napi_object_expected:
37
+ case napi_string_expected:
38
+ case napi_boolean_expected:
39
+ case napi_number_expected:
40
+ status = napi_create_type_error(env, NULL, message, &error);
41
+ NAPI_FATAL_IF_FAILED(status, "error_create", "napi_create_type_error");
42
+ break;
43
+ default:
44
+ status = napi_create_error(env, NULL, message, &error);
45
+ NAPI_FATAL_IF_FAILED(status, "error_create", "napi_create_error");
46
+ break;
47
+ }
48
+ }
49
+
50
+ return error;
51
+ }
@@ -0,0 +1,53 @@
1
+ #ifndef ADDON_SRC_HELPERS_H_
2
+ #define ADDON_SRC_HELPERS_H_
3
+
4
+ #include <node_api.h>
5
+
6
+ #define NAPI_FATAL_IF_FAILED(status, location, message) \
7
+ do { \
8
+ if ((status) != napi_ok) { \
9
+ napi_fatal_error(location, NAPI_AUTO_LENGTH, \
10
+ message, NAPI_AUTO_LENGTH); \
11
+ } \
12
+ } while (0)
13
+
14
+ #define NAPI_THROW_IF_FAILED_VOID(env, status) \
15
+ if ((status) != napi_ok) { \
16
+ NAPI_FATAL_IF_FAILED( \
17
+ napi_throw(env, error_create(env)), \
18
+ "NAPI_THROW_IF_FAILED_VOID", \
19
+ "napi_throw"); \
20
+ return; \
21
+ }
22
+
23
+ #define NAPI_THROW_IF_FAILED(env, status, ...) \
24
+ if ((status) != napi_ok) { \
25
+ NAPI_FATAL_IF_FAILED( \
26
+ napi_throw(env, error_create(env)), \
27
+ "NAPI_THROW_IF_FAILED_VOID", \
28
+ "napi_throw"); \
29
+ return __VA_ARGS__; \
30
+ }
31
+
32
+ #define NAPI_THROW_VOID(env, code, msg) \
33
+ do { \
34
+ NAPI_FATAL_IF_FAILED( \
35
+ napi_throw_error(env, (code), (msg)), \
36
+ "NAPI_THROW_VOID", \
37
+ "napi_throw_error"); \
38
+ return; \
39
+ } while (0)
40
+
41
+ #define NAPI_THROW(env, code, msg, ...) \
42
+ do { \
43
+ NAPI_FATAL_IF_FAILED( \
44
+ napi_throw_error(env, (code), (msg)), \
45
+ "NAPI_THROW", \
46
+ "napi_throw_error"); \
47
+ return __VA_ARGS__; \
48
+ } while (0)
49
+
50
+
51
+ napi_value error_create(napi_env env);
52
+
53
+ #endif // !ADDON_SRC_HELPERS_H_
@@ -0,0 +1,200 @@
1
+ #include <stdarg.h>
2
+ #include <stdio.h>
3
+ #include <uiohook.h>
4
+ #include <uv.h>
5
+
6
+ #ifdef _WIN32
7
+ #include <windows.h>
8
+ #else
9
+ #include <pthread.h>
10
+ #include <sched.h>
11
+ #endif
12
+
13
+ #include "uiohook_worker.h"
14
+
15
+ // Thread and mutex variables.
16
+ static uv_thread_t hook_thread;
17
+ static int hook_thread_status;
18
+ static uv_mutex_t hook_running_mutex;
19
+ static uv_mutex_t hook_control_mutex;
20
+ static uv_cond_t hook_control_cond;
21
+
22
+ static dispatcher_t user_dispatcher = NULL;
23
+
24
+ bool logger_proc(unsigned int level, const char* format, ...) {
25
+ bool status = false;
26
+
27
+ va_list args;
28
+ switch (level) {
29
+ case LOG_LEVEL_WARN:
30
+ case LOG_LEVEL_ERROR:
31
+ va_start(args, format);
32
+ status = vfprintf(stderr, format, args) >= 0;
33
+ va_end(args);
34
+ break;
35
+ }
36
+
37
+ return status;
38
+ }
39
+
40
+ // NOTE: The following callback executes on the same thread that hook_run() is called
41
+ // from. This is important because hook_run() attaches to the operating systems
42
+ // event dispatcher and may delay event delivery to the target application.
43
+ // Furthermore, some operating systems may choose to disable your hook if it
44
+ // takes to long to process. If you need to do any extended processing, please
45
+ // do so by copying the event to your own queued dispatch thread.
46
+ void worker_dispatch_proc(uiohook_event* const event) {
47
+ switch (event->type) {
48
+ case EVENT_HOOK_ENABLED:
49
+ // Lock the running mutex so we know if the hook is enabled.
50
+ uv_mutex_lock(&hook_running_mutex);
51
+
52
+ // Signal control cond so hook_enable() can continue.
53
+ uv_mutex_lock(&hook_control_mutex);
54
+ uv_cond_signal(&hook_control_cond);
55
+ uv_mutex_unlock(&hook_control_mutex);
56
+ break;
57
+
58
+ case EVENT_HOOK_DISABLED:
59
+ // Lock the control mutex until we exit.
60
+ uv_mutex_lock(&hook_control_mutex);
61
+
62
+ // Unlock the running mutex so we know if the hook is disabled.
63
+ uv_mutex_unlock(&hook_running_mutex);
64
+ break;
65
+
66
+ case EVENT_KEY_PRESSED:
67
+ case EVENT_KEY_RELEASED:
68
+ // case EVENT_KEY_TYPED:
69
+ case EVENT_MOUSE_CLICKED:
70
+ case EVENT_MOUSE_PRESSED:
71
+ case EVENT_MOUSE_RELEASED:
72
+ case EVENT_MOUSE_MOVED:
73
+ case EVENT_MOUSE_DRAGGED:
74
+ case EVENT_MOUSE_WHEEL: {
75
+ user_dispatcher(event);
76
+ break;
77
+ }
78
+
79
+ default:
80
+ break;
81
+ }
82
+ }
83
+
84
+ void hook_thread_proc(void* arg) {
85
+ #ifdef _WIN32
86
+ // Attempt to set the thread priority to time critical.
87
+ HANDLE this_thread = GetCurrentThread();
88
+ if (SetThreadPriority(this_thread, THREAD_PRIORITY_TIME_CRITICAL) == FALSE) {
89
+ logger_proc(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %li for thread %#p! (%#lX)\n",
90
+ __FUNCTION__, __LINE__, (long)THREAD_PRIORITY_TIME_CRITICAL,
91
+ this_thread, (unsigned long)GetLastError());
92
+ }
93
+ #else
94
+ // Raise the thread priority
95
+ pthread_t this_thread = pthread_self();
96
+ struct sched_param params = {
97
+ .sched_priority = (sched_get_priority_max(SCHED_RR) / 2)
98
+ };
99
+ if (pthread_setschedparam(this_thread, SCHED_RR, &params) != 0) {
100
+ logger_proc(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %i for thread 0x%lX!\n",
101
+ __FUNCTION__, __LINE__, params.sched_priority, (unsigned long)this_thread);
102
+ }
103
+ #endif
104
+
105
+ // Set the hook status.
106
+ hook_thread_status = hook_run();
107
+
108
+ // Make sure we signal that we have passed any exception throwing code for
109
+ // the waiting hook_enable().
110
+ uv_cond_signal(&hook_control_cond);
111
+ uv_mutex_unlock(&hook_control_mutex);
112
+ }
113
+
114
+ int hook_enable() {
115
+ // Lock the thread control mutex. This will be unlocked when the
116
+ // thread has finished starting, or when it has fully stopped.
117
+ uv_mutex_lock(&hook_control_mutex);
118
+
119
+ // Set the initial status.
120
+ int status = UIOHOOK_FAILURE;
121
+
122
+ if (uv_thread_create(&hook_thread, hook_thread_proc, NULL) == 0) {
123
+ // Wait for the thread to indicate that it has passed the
124
+ // initialization portion by blocking until either a EVENT_HOOK_ENABLED
125
+ // event is received or the thread terminates.
126
+ uv_cond_wait(&hook_control_cond, &hook_control_mutex);
127
+
128
+ if (uv_mutex_trylock(&hook_running_mutex) == 0) {
129
+ // Lock Successful; The hook is not running but the hook_control_cond
130
+ // was signaled! This indicates that there was a startup problem!
131
+
132
+ // Get the status back from the thread.
133
+ uv_thread_join(&hook_thread);
134
+ status = hook_thread_status;
135
+ }
136
+ else {
137
+ // Lock Failure; The hook is currently running and wait was signaled
138
+ // indicating that we have passed all possible start checks. We can
139
+ // always assume a successful startup at this point.
140
+ status = UIOHOOK_SUCCESS;
141
+ }
142
+
143
+ logger_proc(LOG_LEVEL_DEBUG, "%s [%u]: Thread Result: (%#X).\n",
144
+ __FUNCTION__, __LINE__, status);
145
+ }
146
+ else {
147
+ status = UIOHOOK_ERROR_THREAD_CREATE;
148
+ }
149
+
150
+ // Make sure the control mutex is unlocked.
151
+ uv_mutex_unlock(&hook_control_mutex);
152
+
153
+ return status;
154
+ }
155
+
156
+
157
+ int uiohook_worker_start(dispatcher_t dispatch_proc) {
158
+ // Lock the thread control mutex. This will be unlocked when the
159
+ // thread has finished starting, or when it has fully stopped.
160
+
161
+ // Create event handles for the thread hook.
162
+ uv_mutex_init(&hook_running_mutex);
163
+ uv_mutex_init(&hook_control_mutex);
164
+ uv_cond_init(&hook_control_cond);
165
+
166
+ // Set the logger callback for library output.
167
+ hook_set_logger_proc(logger_proc);
168
+
169
+ // Set the event callback for uiohook events.
170
+ hook_set_dispatch_proc(worker_dispatch_proc);
171
+
172
+ user_dispatcher = dispatch_proc;
173
+
174
+ // Start the hook and block.
175
+ // NOTE If EVENT_HOOK_ENABLED was delivered, the status will always succeed.
176
+ int status = hook_enable();
177
+ if (status != UIOHOOK_SUCCESS) {
178
+ // Close event handles for the thread hook.
179
+ uv_mutex_destroy(&hook_running_mutex);
180
+ uv_mutex_destroy(&hook_control_mutex);
181
+ uv_cond_destroy(&hook_control_cond);
182
+ }
183
+
184
+ return status;
185
+ }
186
+
187
+ int uiohook_worker_stop() {
188
+ int status = hook_stop();
189
+
190
+ if (status == UIOHOOK_SUCCESS) {
191
+ uv_thread_join(&hook_thread);
192
+
193
+ // Close event handles for the thread hook.
194
+ uv_mutex_destroy(&hook_running_mutex);
195
+ uv_mutex_destroy(&hook_control_mutex);
196
+ uv_cond_destroy(&hook_control_cond);
197
+ }
198
+
199
+ return status;
200
+ }
@@ -0,0 +1,12 @@
1
+ #ifndef ADDON_SRC_UIOHOOK_WORKER_H_
2
+ #define ADDON_SRC_UIOHOOK_WORKER_H_
3
+
4
+ #include <uiohook.h>
5
+
6
+ #define UIOHOOK_ERROR_THREAD_CREATE 0x10
7
+
8
+ int uiohook_worker_start(dispatcher_t dispatch_proc);
9
+
10
+ int uiohook_worker_stop();
11
+
12
+ #endif // !ADDON_SRC_UIOHOOK_WORKER_H_