@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,722 @@
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 <uiohook.h>
21
+ #include <windows.h>
22
+
23
+ #include "input_helper.h"
24
+ #include "logger.h"
25
+
26
+ #ifndef FOREGROUND_TIMER_MS
27
+ #define FOREGROUND_TIMER_MS 83 // 12 fps
28
+ #endif
29
+
30
+ // Thread and hook handles.
31
+ static DWORD hook_thread_id = 0;
32
+ static HHOOK keyboard_event_hhook = NULL, mouse_event_hhook = NULL;
33
+ static HWINEVENTHOOK win_foreground_hhook = NULL, win_minimizeend_hhook = NULL;
34
+ static UINT_PTR foreground_timer = 0;
35
+
36
+ static HWND foreground_window = NULL;
37
+ static bool is_blocked_by_uipi = true;
38
+
39
+ static UINT WM_UIOHOOK_UIPI_TEST = WM_NULL;
40
+
41
+ // The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH.
42
+ extern HINSTANCE hInst;
43
+
44
+ // Modifiers for tracking key masks.
45
+ static unsigned short int current_modifiers = 0x0000;
46
+
47
+ // Click count globals.
48
+ static unsigned short click_count = 0;
49
+ static DWORD click_time = 0;
50
+ static unsigned short int click_button = MOUSE_NOBUTTON;
51
+ static POINT last_click;
52
+
53
+ // Static event memory.
54
+ static uiohook_event event;
55
+
56
+ // Event dispatch callback.
57
+ static dispatcher_t dispatcher = NULL;
58
+
59
+ UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) {
60
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n",
61
+ __FUNCTION__, __LINE__, dispatch_proc);
62
+
63
+ dispatcher = dispatch_proc;
64
+ }
65
+
66
+ // Send out an event if a dispatcher was set.
67
+ static inline void dispatch_event(uiohook_event *const event) {
68
+ if (dispatcher != NULL) {
69
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n",
70
+ __FUNCTION__, __LINE__, event->type);
71
+
72
+ dispatcher(event);
73
+ } else {
74
+ logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n",
75
+ __FUNCTION__, __LINE__);
76
+ }
77
+ }
78
+
79
+ static void initialize_modifiers();
80
+
81
+ // Set the native modifier mask for future events.
82
+ static inline void set_modifier_mask(unsigned short int mask) {
83
+ current_modifiers |= mask;
84
+ }
85
+
86
+ // Unset the native modifier mask for future events.
87
+ static inline void unset_modifier_mask(unsigned short int mask) {
88
+ current_modifiers &= ~mask;
89
+ }
90
+
91
+ // Get the current native modifier mask state.
92
+ static inline unsigned short int get_modifiers() {
93
+ if (is_blocked_by_uipi) {
94
+ initialize_modifiers();
95
+ is_blocked_by_uipi = false;
96
+ }
97
+ return current_modifiers;
98
+ }
99
+
100
+ // Initialize the modifier mask to the current modifiers.
101
+ static void initialize_modifiers() {
102
+ current_modifiers = 0x0000;
103
+
104
+ // NOTE We are checking the high order bit, so it will be < 0 for a singed short.
105
+ if (GetAsyncKeyState(VK_LSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_L); }
106
+ if (GetAsyncKeyState(VK_RSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_R); }
107
+ if (GetAsyncKeyState(VK_LCONTROL) < 0) { set_modifier_mask(MASK_CTRL_L); }
108
+ if (GetAsyncKeyState(VK_RCONTROL) < 0) { set_modifier_mask(MASK_CTRL_R); }
109
+ if (GetAsyncKeyState(VK_LMENU) < 0) { set_modifier_mask(MASK_ALT_L); }
110
+ if (GetAsyncKeyState(VK_RMENU) < 0) { set_modifier_mask(MASK_ALT_R); }
111
+ if (GetAsyncKeyState(VK_LWIN) < 0) { set_modifier_mask(MASK_META_L); }
112
+ if (GetAsyncKeyState(VK_RWIN) < 0) { set_modifier_mask(MASK_META_R); }
113
+
114
+ if (GetAsyncKeyState(VK_LBUTTON) < 0) { set_modifier_mask(MASK_BUTTON1); }
115
+ if (GetAsyncKeyState(VK_RBUTTON) < 0) { set_modifier_mask(MASK_BUTTON2); }
116
+ if (GetAsyncKeyState(VK_MBUTTON) < 0) { set_modifier_mask(MASK_BUTTON3); }
117
+ if (GetAsyncKeyState(VK_XBUTTON1) < 0) { set_modifier_mask(MASK_BUTTON4); }
118
+ if (GetAsyncKeyState(VK_XBUTTON2) < 0) { set_modifier_mask(MASK_BUTTON5); }
119
+
120
+ if (GetAsyncKeyState(VK_NUMLOCK) < 0) { set_modifier_mask(MASK_NUM_LOCK); }
121
+ if (GetAsyncKeyState(VK_CAPITAL) < 0) { set_modifier_mask(MASK_CAPS_LOCK); }
122
+ if (GetAsyncKeyState(VK_SCROLL) < 0) { set_modifier_mask(MASK_SCROLL_LOCK); }
123
+ }
124
+
125
+ void check_and_update_uipi_state(HWND hwnd) {
126
+ SetLastError(ERROR_SUCCESS);
127
+ PostMessage(hwnd, WM_UIOHOOK_UIPI_TEST, 0, 0);
128
+ if (GetLastError() == ERROR_ACCESS_DENIED) {
129
+ is_blocked_by_uipi = true;
130
+ }
131
+ }
132
+
133
+ void unregister_running_hooks() {
134
+ // Stop the event hook and any timer still running.
135
+ if (win_foreground_hhook != NULL) {
136
+ UnhookWinEvent(win_foreground_hhook);
137
+ win_foreground_hhook = NULL;
138
+ }
139
+
140
+ if (win_minimizeend_hhook != NULL) {
141
+ UnhookWinEvent(win_minimizeend_hhook);
142
+ win_minimizeend_hhook = NULL;
143
+ }
144
+
145
+ if (foreground_timer != 0) {
146
+ KillTimer(NULL, foreground_timer);
147
+ foreground_timer = 0;
148
+ }
149
+
150
+ // Destroy the native hooks.
151
+ if (keyboard_event_hhook != NULL) {
152
+ UnhookWindowsHookEx(keyboard_event_hhook);
153
+ keyboard_event_hhook = NULL;
154
+ }
155
+
156
+ if (mouse_event_hhook != NULL) {
157
+ UnhookWindowsHookEx(mouse_event_hhook);
158
+ mouse_event_hhook = NULL;
159
+ }
160
+ }
161
+
162
+ void hook_start_proc() {
163
+ // Initialize native input helper functions.
164
+ load_input_helper();
165
+
166
+ // Get the local system time in UNIX epoch form.
167
+ uint64_t timestamp = GetMessageTime();
168
+
169
+ // Populate the hook start event.
170
+ event.time = timestamp;
171
+ event.reserved = 0x00;
172
+
173
+ event.type = EVENT_HOOK_ENABLED;
174
+ event.mask = 0x00;
175
+
176
+ // Fire the hook start event.
177
+ dispatch_event(&event);
178
+ }
179
+
180
+ void hook_stop_proc() {
181
+ // Get the local system time in UNIX epoch form.
182
+ uint64_t timestamp = GetMessageTime();
183
+
184
+ // Populate the hook stop event.
185
+ event.time = timestamp;
186
+ event.reserved = 0x00;
187
+
188
+ event.type = EVENT_HOOK_DISABLED;
189
+ event.mask = 0x00;
190
+
191
+ // Fire the hook stop event.
192
+ dispatch_event(&event);
193
+
194
+ // Deinitialize native input helper functions.
195
+ unload_input_helper();
196
+ }
197
+
198
+ static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) {
199
+ // Check and setup modifiers.
200
+ if (kbhook->vkCode == VK_LSHIFT) { set_modifier_mask(MASK_SHIFT_L); }
201
+ else if (kbhook->vkCode == VK_RSHIFT) { set_modifier_mask(MASK_SHIFT_R); }
202
+ else if (kbhook->vkCode == VK_LCONTROL) { set_modifier_mask(MASK_CTRL_L); }
203
+ else if (kbhook->vkCode == VK_RCONTROL) { set_modifier_mask(MASK_CTRL_R); }
204
+ else if (kbhook->vkCode == VK_LMENU) { set_modifier_mask(MASK_ALT_L); }
205
+ else if (kbhook->vkCode == VK_RMENU) { set_modifier_mask(MASK_ALT_R); }
206
+ else if (kbhook->vkCode == VK_LWIN) { set_modifier_mask(MASK_META_L); }
207
+ else if (kbhook->vkCode == VK_RWIN) { set_modifier_mask(MASK_META_R); }
208
+ else if (kbhook->vkCode == VK_NUMLOCK) { set_modifier_mask(MASK_NUM_LOCK); }
209
+ else if (kbhook->vkCode == VK_CAPITAL) { set_modifier_mask(MASK_CAPS_LOCK); }
210
+ else if (kbhook->vkCode == VK_SCROLL) { set_modifier_mask(MASK_SCROLL_LOCK); }
211
+
212
+ // Populate key pressed event.
213
+ event.time = kbhook->time;
214
+ event.reserved = 0x00;
215
+
216
+ event.type = EVENT_KEY_PRESSED;
217
+ event.mask = get_modifiers();
218
+
219
+ event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags);
220
+ event.data.keyboard.rawcode = (uint16_t) kbhook->vkCode;
221
+ event.data.keyboard.keychar = CHAR_UNDEFINED;
222
+
223
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X pressed. (%#X)\n",
224
+ __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
225
+
226
+ // Populate key pressed event.
227
+ dispatch_event(&event);
228
+
229
+ if ((event.mask & (MASK_CTRL)) &&
230
+ (event.mask & (MASK_ALT)) &&
231
+ (event.data.keyboard.keycode == VC_DELETE)) {
232
+ current_modifiers = 0;
233
+ }
234
+
235
+ // If the pressed event was not consumed...
236
+ if (event.reserved ^ 0x01) {
237
+ }
238
+ }
239
+
240
+ static void process_key_released(KBDLLHOOKSTRUCT *kbhook) {
241
+ // Check and setup modifiers.
242
+ if (kbhook->vkCode == VK_LSHIFT) { unset_modifier_mask(MASK_SHIFT_L); }
243
+ else if (kbhook->vkCode == VK_RSHIFT) { unset_modifier_mask(MASK_SHIFT_R); }
244
+ else if (kbhook->vkCode == VK_LCONTROL) { unset_modifier_mask(MASK_CTRL_L); }
245
+ else if (kbhook->vkCode == VK_RCONTROL) { unset_modifier_mask(MASK_CTRL_R); }
246
+ else if (kbhook->vkCode == VK_LMENU) { unset_modifier_mask(MASK_ALT_L); }
247
+ else if (kbhook->vkCode == VK_RMENU) { unset_modifier_mask(MASK_ALT_R); }
248
+ else if (kbhook->vkCode == VK_LWIN) { unset_modifier_mask(MASK_META_L); }
249
+ else if (kbhook->vkCode == VK_RWIN) { unset_modifier_mask(MASK_META_R); }
250
+ else if (kbhook->vkCode == VK_NUMLOCK) { unset_modifier_mask(MASK_NUM_LOCK); }
251
+ else if (kbhook->vkCode == VK_CAPITAL) { unset_modifier_mask(MASK_CAPS_LOCK); }
252
+ else if (kbhook->vkCode == VK_SCROLL) { unset_modifier_mask(MASK_SCROLL_LOCK); }
253
+
254
+ // Populate key pressed event.
255
+ event.time = kbhook->time;
256
+ event.reserved = 0x00;
257
+
258
+ event.type = EVENT_KEY_RELEASED;
259
+ event.mask = get_modifiers();
260
+
261
+ event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags);
262
+ event.data.keyboard.rawcode = (uint16_t) kbhook->vkCode;
263
+ event.data.keyboard.keychar = CHAR_UNDEFINED;
264
+
265
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X released. (%#X)\n",
266
+ __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
267
+
268
+ // Fire key released event.
269
+ dispatch_event(&event);
270
+ }
271
+
272
+ LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) {
273
+ KBDLLHOOKSTRUCT *kbhook = (KBDLLHOOKSTRUCT *) lParam;
274
+ switch (wParam) {
275
+ case WM_KEYDOWN:
276
+ case WM_SYSKEYDOWN:
277
+ process_key_pressed(kbhook);
278
+ break;
279
+
280
+ case WM_KEYUP:
281
+ case WM_SYSKEYUP:
282
+ process_key_released(kbhook);
283
+ break;
284
+
285
+ default:
286
+ // In theory this *should* never execute.
287
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Windows keyboard event: %#X.\n",
288
+ __FUNCTION__, __LINE__, (unsigned int) wParam);
289
+ break;
290
+ }
291
+
292
+ LRESULT hook_result = -1;
293
+ if (nCode < 0 || event.reserved ^ 0x01) {
294
+ hook_result = CallNextHookEx(keyboard_event_hhook, nCode, wParam, lParam);
295
+ } else {
296
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n",
297
+ __FUNCTION__, __LINE__, (long) hook_result);
298
+ }
299
+
300
+ return hook_result;
301
+ }
302
+
303
+
304
+ static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) {
305
+ DWORD timestamp = mshook->time;
306
+
307
+ // Track the number of clicks, the button must match the previous button.
308
+ if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) {
309
+ if (click_count < USHRT_MAX) {
310
+ click_count++;
311
+ } else {
312
+ logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n",
313
+ __FUNCTION__, __LINE__);
314
+ }
315
+ } else {
316
+ // Reset the click count.
317
+ click_count = 1;
318
+
319
+ // Set the previous button.
320
+ click_button = button;
321
+ }
322
+
323
+ // Save this events time to calculate the click_count.
324
+ click_time = timestamp;
325
+
326
+ // Store the last click point.
327
+ last_click.x = mshook->pt.x;
328
+ last_click.y = mshook->pt.y;
329
+
330
+ // Populate mouse pressed event.
331
+ event.time = timestamp;
332
+ event.reserved = 0x00;
333
+
334
+ event.type = EVENT_MOUSE_PRESSED;
335
+ event.mask = get_modifiers();
336
+
337
+ event.data.mouse.button = button;
338
+ event.data.mouse.clicks = click_count;
339
+
340
+ event.data.mouse.x = (int16_t) mshook->pt.x;
341
+ event.data.mouse.y = (int16_t) mshook->pt.y;
342
+
343
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n",
344
+ __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
345
+ event.data.mouse.x, event.data.mouse.y);
346
+
347
+ // Fire mouse pressed event.
348
+ dispatch_event(&event);
349
+ }
350
+
351
+ static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) {
352
+ // Populate mouse released event.
353
+ event.time = mshook->time;
354
+ event.reserved = 0x00;
355
+
356
+ event.type = EVENT_MOUSE_RELEASED;
357
+ event.mask = get_modifiers();
358
+
359
+ event.data.mouse.button = button;
360
+ event.data.mouse.clicks = click_count;
361
+
362
+ event.data.mouse.x = (int16_t) mshook->pt.x;
363
+ event.data.mouse.y = (int16_t) mshook->pt.y;
364
+
365
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u released %u time(s). (%u, %u)\n",
366
+ __FUNCTION__, __LINE__, event.data.mouse.button,
367
+ event.data.mouse.clicks,
368
+ event.data.mouse.x, event.data.mouse.y);
369
+
370
+ // Fire mouse released event.
371
+ dispatch_event(&event);
372
+
373
+ // If the pressed event was not consumed...
374
+ if (event.reserved ^ 0x01 && last_click.x == mshook->pt.x && last_click.y == mshook->pt.y) {
375
+ // Populate mouse clicked event.
376
+ event.time = mshook->time;
377
+ event.reserved = 0x00;
378
+
379
+ event.type = EVENT_MOUSE_CLICKED;
380
+ event.mask = get_modifiers();
381
+
382
+ event.data.mouse.button = button;
383
+ event.data.mouse.clicks = click_count;
384
+ event.data.mouse.x = (int16_t) mshook->pt.x;
385
+ event.data.mouse.y = (int16_t) mshook->pt.y;
386
+
387
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n",
388
+ __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
389
+ event.data.mouse.x, event.data.mouse.y);
390
+
391
+ // Fire mouse clicked event.
392
+ dispatch_event(&event);
393
+ }
394
+
395
+ // Reset the number of clicks.
396
+ if (button == click_button && (long int) (event.time - click_time) > hook_get_multi_click_time()) {
397
+ // Reset the click count.
398
+ click_count = 0;
399
+ }
400
+ }
401
+
402
+ static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) {
403
+ uint64_t timestamp = mshook->time;
404
+
405
+ // We received a mouse move event with the mouse actually moving.
406
+ // This verifies that the mouse was moved after being depressed.
407
+ if (last_click.x != mshook->pt.x || last_click.y != mshook->pt.y) {
408
+ // Reset the click count.
409
+ if (click_count != 0 && (long) (timestamp - click_time) > hook_get_multi_click_time()) {
410
+ click_count = 0;
411
+ }
412
+
413
+ // Populate mouse move event.
414
+ event.time = timestamp;
415
+ event.reserved = 0x00;
416
+
417
+ event.mask = get_modifiers();
418
+
419
+ // Check the modifier mask range for MASK_BUTTON1 - 5.
420
+ bool mouse_dragged = event.mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5);
421
+ if (mouse_dragged) {
422
+ // Create Mouse Dragged event.
423
+ event.type = EVENT_MOUSE_DRAGGED;
424
+ } else {
425
+ // Create a Mouse Moved event.
426
+ event.type = EVENT_MOUSE_MOVED;
427
+ }
428
+
429
+ event.data.mouse.button = MOUSE_NOBUTTON;
430
+ event.data.mouse.clicks = click_count;
431
+ event.data.mouse.x = (int16_t) mshook->pt.x;
432
+ event.data.mouse.y = (int16_t) mshook->pt.y;
433
+
434
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse %s to %u, %u.\n",
435
+ __FUNCTION__, __LINE__, mouse_dragged ? "dragged" : "moved",
436
+ event.data.mouse.x, event.data.mouse.y);
437
+
438
+ // Fire mouse move event.
439
+ dispatch_event(&event);
440
+ }
441
+ }
442
+
443
+ static void process_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) {
444
+ // Track the number of clicks.
445
+ // Reset the click count and previous button.
446
+ click_count = 1;
447
+ click_button = MOUSE_NOBUTTON;
448
+
449
+ // Populate mouse wheel event.
450
+ event.time = mshook->time;
451
+ event.reserved = 0x00;
452
+
453
+ event.type = EVENT_MOUSE_WHEEL;
454
+ event.mask = get_modifiers();
455
+
456
+ event.data.wheel.clicks = click_count;
457
+ event.data.wheel.x = (int16_t) mshook->pt.x;
458
+ event.data.wheel.y = (int16_t) mshook->pt.y;
459
+
460
+ event.data.wheel.rotation = get_scroll_wheel_rotation(mshook->mouseData, direction);
461
+
462
+ UINT uiAction = SPI_GETWHEELSCROLLCHARS;
463
+ if (direction == WHEEL_VERTICAL_DIRECTION) {
464
+ uiAction = SPI_GETWHEELSCROLLLINES;
465
+ }
466
+
467
+ UINT wheel_amount = 3;
468
+ if (SystemParametersInfo(uiAction, 0, &wheel_amount, 0)) {
469
+ if (wheel_amount == WHEEL_PAGESCROLL) {
470
+ event.data.wheel.type = WHEEL_BLOCK_SCROLL;
471
+ event.data.wheel.amount = 1;
472
+ } else {
473
+ event.data.wheel.type = WHEEL_UNIT_SCROLL;
474
+ event.data.wheel.amount = (uint16_t) wheel_amount;
475
+ }
476
+
477
+ // Set the direction based on what event was received.
478
+ event.data.wheel.direction = direction;
479
+
480
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n",
481
+ __FUNCTION__, __LINE__,
482
+ event.data.wheel.type,
483
+ event.data.wheel.amount * event.data.wheel.rotation,
484
+ event.data.wheel.direction,
485
+ event.data.wheel.x, event.data.wheel.y);
486
+
487
+ // Fire mouse wheel event.
488
+ dispatch_event(&event);
489
+ } else {
490
+ event.reserved = 0x01;
491
+ logger(LOG_LEVEL_WARN, "%s [%u]: SystemParametersInfo() failed, event will be ignored.\n",
492
+ __FUNCTION__, __LINE__);
493
+ }
494
+ }
495
+
496
+ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) {
497
+ MSLLHOOKSTRUCT *mshook = (MSLLHOOKSTRUCT *) lParam;
498
+ switch (wParam) {
499
+ case WM_LBUTTONDOWN:
500
+ set_modifier_mask(MASK_BUTTON1);
501
+ process_button_pressed(mshook, MOUSE_BUTTON1);
502
+ break;
503
+
504
+ case WM_RBUTTONDOWN:
505
+ set_modifier_mask(MASK_BUTTON2);
506
+ process_button_pressed(mshook, MOUSE_BUTTON2);
507
+ break;
508
+
509
+ case WM_MBUTTONDOWN:
510
+ set_modifier_mask(MASK_BUTTON3);
511
+ process_button_pressed(mshook, MOUSE_BUTTON3);
512
+ break;
513
+
514
+ case WM_XBUTTONDOWN:
515
+ case WM_NCXBUTTONDOWN:
516
+ if (HIWORD(mshook->mouseData) == XBUTTON1) {
517
+ set_modifier_mask(MASK_BUTTON4);
518
+ process_button_pressed(mshook, MOUSE_BUTTON4);
519
+ } else if (HIWORD(mshook->mouseData) == XBUTTON2) {
520
+ set_modifier_mask(MASK_BUTTON5);
521
+ process_button_pressed(mshook, MOUSE_BUTTON5);
522
+ } else {
523
+ // Extra mouse buttons.
524
+ uint16_t button = HIWORD(mshook->mouseData);
525
+
526
+ // Add support for mouse 4 & 5.
527
+ if (button == 4) {
528
+ set_modifier_mask(MOUSE_BUTTON4);
529
+ } else if (button == 5) {
530
+ set_modifier_mask(MOUSE_BUTTON5);
531
+ }
532
+
533
+ process_button_pressed(mshook, button);
534
+ }
535
+ break;
536
+
537
+
538
+ case WM_LBUTTONUP:
539
+ unset_modifier_mask(MASK_BUTTON1);
540
+ process_button_released(mshook, MOUSE_BUTTON1);
541
+ break;
542
+
543
+ case WM_RBUTTONUP:
544
+ unset_modifier_mask(MASK_BUTTON2);
545
+ process_button_released(mshook, MOUSE_BUTTON2);
546
+ break;
547
+
548
+ case WM_MBUTTONUP:
549
+ unset_modifier_mask(MASK_BUTTON3);
550
+ process_button_released(mshook, MOUSE_BUTTON3);
551
+ break;
552
+
553
+ case WM_XBUTTONUP:
554
+ case WM_NCXBUTTONUP:
555
+ if (HIWORD(mshook->mouseData) == XBUTTON1) {
556
+ unset_modifier_mask(MASK_BUTTON4);
557
+ process_button_released(mshook, MOUSE_BUTTON4);
558
+ } else if (HIWORD(mshook->mouseData) == XBUTTON2) {
559
+ unset_modifier_mask(MASK_BUTTON5);
560
+ process_button_released(mshook, MOUSE_BUTTON5);
561
+ } else {
562
+ // Extra mouse buttons.
563
+ uint16_t button = HIWORD(mshook->mouseData);
564
+
565
+ // Add support for mouse 4 & 5.
566
+ if (button == 4) {
567
+ unset_modifier_mask(MOUSE_BUTTON4);
568
+ } else if (button == 5) {
569
+ unset_modifier_mask(MOUSE_BUTTON5);
570
+ }
571
+
572
+ process_button_released(mshook, MOUSE_BUTTON5);
573
+ }
574
+ break;
575
+
576
+ case WM_MOUSEMOVE:
577
+ process_mouse_moved(mshook);
578
+ break;
579
+
580
+ case WM_MOUSEWHEEL:
581
+ process_mouse_wheel(mshook, WHEEL_VERTICAL_DIRECTION);
582
+ break;
583
+
584
+ /* For horizontal scroll wheel support.
585
+ * NOTE Windows >= Vista
586
+ * case 0x020E:
587
+ */
588
+ case WM_MOUSEHWHEEL:
589
+ process_mouse_wheel(mshook, WHEEL_HORIZONTAL_DIRECTION);
590
+ break;
591
+
592
+ default:
593
+ // In theory this *should* never execute.
594
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Windows mouse event: %#X.\n",
595
+ __FUNCTION__, __LINE__, (unsigned int) wParam);
596
+ break;
597
+ }
598
+
599
+ LRESULT hook_result = -1;
600
+ if (nCode < 0 || event.reserved ^ 0x01) {
601
+ hook_result = CallNextHookEx(mouse_event_hhook, nCode, wParam, lParam);
602
+ } else {
603
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n",
604
+ __FUNCTION__, __LINE__, (long) hook_result);
605
+ }
606
+
607
+ return hook_result;
608
+ }
609
+
610
+
611
+ // Callback function that handles events.
612
+ void CALLBACK win_hook_event_proc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) {
613
+ foreground_window = hWnd;
614
+ check_and_update_uipi_state(hWnd);
615
+ }
616
+
617
+ static VOID CALLBACK foreground_timer_proc(HWND _hwnd, UINT msg, UINT_PTR timerId, DWORD dwmsEventTime)
618
+ {
619
+ HWND system_foreground = GetForegroundWindow();
620
+
621
+ if (foreground_window != system_foreground) {
622
+ foreground_window = system_foreground;
623
+ check_and_update_uipi_state(system_foreground);
624
+ }
625
+ }
626
+
627
+
628
+ UIOHOOK_API int hook_run() {
629
+ int status = UIOHOOK_FAILURE;
630
+
631
+ // Set the thread id we want to signal later.
632
+ hook_thread_id = GetCurrentThreadId();
633
+
634
+ // Spot check the hInst in case the library was statically linked and DllMain
635
+ // did not receive a pointer on load.
636
+ if (hInst == NULL) {
637
+ logger(LOG_LEVEL_WARN, "%s [%u]: hInst was not set by DllMain().\n",
638
+ __FUNCTION__, __LINE__);
639
+
640
+ hInst = GetModuleHandle(NULL);
641
+ if (hInst != NULL) {
642
+ // Initialize native input helper functions.
643
+ load_input_helper();
644
+ } else {
645
+ logger(LOG_LEVEL_ERROR, "%s [%u]: Could not determine hInst for SetWindowsHookEx()! (%#lX)\n",
646
+ __FUNCTION__, __LINE__, (unsigned long) GetLastError());
647
+
648
+ status = UIOHOOK_ERROR_GET_MODULE_HANDLE;
649
+ }
650
+ }
651
+
652
+ // Create the native hooks.
653
+ keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0);
654
+ mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0);
655
+
656
+ win_foreground_hhook = SetWinEventHook(
657
+ EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND,
658
+ NULL, win_hook_event_proc, 0, 0, WINEVENT_OUTOFCONTEXT);
659
+ win_minimizeend_hhook = SetWinEventHook(
660
+ EVENT_SYSTEM_MINIMIZEEND, EVENT_SYSTEM_MINIMIZEEND,
661
+ NULL, win_hook_event_proc, 0, 0, WINEVENT_OUTOFCONTEXT);
662
+ foreground_timer = SetTimer(NULL, 0, FOREGROUND_TIMER_MS, foreground_timer_proc);
663
+
664
+ WM_UIOHOOK_UIPI_TEST = RegisterWindowMessage("UIOHOOK_UIPI_TEST");
665
+
666
+ foreground_window = GetForegroundWindow();
667
+ is_blocked_by_uipi = true; // init modifiers
668
+
669
+ // If we did not encounter a problem, start processing events.
670
+ if (keyboard_event_hhook != NULL && mouse_event_hhook != NULL) {
671
+ if (win_foreground_hhook == NULL || win_minimizeend_hhook == NULL) {
672
+ logger(LOG_LEVEL_WARN, "%s [%u]: SetWinEventHook() failed!\n",
673
+ __FUNCTION__, __LINE__);
674
+ }
675
+
676
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: SetWindowsHookEx() successful.\n",
677
+ __FUNCTION__, __LINE__);
678
+
679
+ // Set the exit status.
680
+ status = UIOHOOK_SUCCESS;
681
+
682
+ // Windows does not have a hook start event or callback so we need to
683
+ // manually fake it.
684
+ hook_start_proc();
685
+
686
+ // Block until the thread receives an WM_QUIT request.
687
+ MSG message;
688
+ while (GetMessage(&message, (HWND) NULL, 0, 0) > 0) {
689
+ TranslateMessage(&message);
690
+ DispatchMessage(&message);
691
+ }
692
+ } else {
693
+ logger(LOG_LEVEL_ERROR, "%s [%u]: SetWindowsHookEx() failed! (%#lX)\n",
694
+ __FUNCTION__, __LINE__, (unsigned long) GetLastError());
695
+
696
+ status = UIOHOOK_ERROR_SET_WINDOWS_HOOK_EX;
697
+ }
698
+
699
+
700
+ // Unregister any hooks that may still be installed.
701
+ unregister_running_hooks();
702
+
703
+ // We must explicitly call the cleanup handler because Windows does not
704
+ // provide a thread cleanup method like POSIX pthread_cleanup_push/pop.
705
+ hook_stop_proc();
706
+
707
+ return status;
708
+ }
709
+
710
+ UIOHOOK_API int hook_stop() {
711
+ int status = UIOHOOK_FAILURE;
712
+
713
+ // Try to exit the thread naturally.
714
+ if (PostThreadMessage(hook_thread_id, WM_QUIT, (WPARAM) NULL, (LPARAM) NULL)) {
715
+ status = UIOHOOK_SUCCESS;
716
+ }
717
+
718
+ logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n",
719
+ __FUNCTION__, __LINE__, status);
720
+
721
+ return status;
722
+ }