keyspy 1.0.7 → 1.1.1

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 (103) hide show
  1. package/README.md +110 -3
  2. package/bin/keyspy +24 -0
  3. package/dist/cli.d.ts +9 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +359 -0
  6. package/{build → dist}/index.d.ts +10 -10
  7. package/{build → dist}/index.d.ts.map +1 -1
  8. package/dist/index.js +156 -0
  9. package/{build/ts/X11KeyServer.d.ts → dist/platforms/linux/index.d.ts} +5 -5
  10. package/dist/platforms/linux/index.d.ts.map +1 -0
  11. package/dist/platforms/linux/index.js +181 -0
  12. package/dist/platforms/linux/keymap.d.ts +3 -0
  13. package/dist/platforms/linux/keymap.d.ts.map +1 -0
  14. package/dist/platforms/linux/keymap.js +144 -0
  15. package/{build/ts/MacKeyServer.d.ts → dist/platforms/mac/index.d.ts} +5 -5
  16. package/dist/platforms/mac/index.d.ts.map +1 -0
  17. package/dist/platforms/mac/index.js +181 -0
  18. package/dist/platforms/mac/keymap.d.ts +3 -0
  19. package/dist/platforms/mac/keymap.d.ts.map +1 -0
  20. package/dist/platforms/mac/keymap.js +155 -0
  21. package/{build/ts/WinKeyServer.d.ts → dist/platforms/windows/index.d.ts} +5 -5
  22. package/dist/platforms/windows/index.d.ts.map +1 -0
  23. package/dist/platforms/windows/index.js +110 -0
  24. package/{build/ts/_data/WinGlobalKeyLookup.d.ts → dist/platforms/windows/keymap.d.ts} +2 -2
  25. package/dist/platforms/windows/keymap.d.ts.map +1 -0
  26. package/{build/ts/_data/WinGlobalKeyLookup.js → dist/platforms/windows/keymap.js} +1 -1
  27. package/{build/ts/_types → dist/types}/IConfig.d.ts +2 -0
  28. package/dist/types/IConfig.d.ts.map +1 -0
  29. package/{build/ts/_types → dist/types}/IConfig.js +1 -1
  30. package/{build/ts/_types → dist/types}/IGlobalKey.d.ts +1 -1
  31. package/dist/types/IGlobalKey.d.ts.map +1 -0
  32. package/{build/ts/_types → dist/types}/IGlobalKey.js +1 -1
  33. package/dist/types/IGlobalKeyDownMap.d.ts.map +1 -0
  34. package/{build/ts/_types → dist/types}/IGlobalKeyDownMap.js +1 -1
  35. package/dist/types/IGlobalKeyEvent.d.ts.map +1 -0
  36. package/{build/ts/_types → dist/types}/IGlobalKeyEvent.js +1 -1
  37. package/dist/types/IGlobalKeyListener.d.ts.map +1 -0
  38. package/{build/ts/_types → dist/types}/IGlobalKeyListener.js +1 -1
  39. package/dist/types/IGlobalKeyListenerRaw.d.ts.map +1 -0
  40. package/{build/ts/_types → dist/types}/IGlobalKeyListenerRaw.js +1 -1
  41. package/dist/types/IGlobalKeyLookup.d.ts.map +1 -0
  42. package/{build/ts/_types → dist/types}/IGlobalKeyLookup.js +1 -1
  43. package/dist/types/IGlobalKeyResult.d.ts.map +1 -0
  44. package/{build/ts/_types → dist/types}/IGlobalKeyResult.js +1 -1
  45. package/dist/types/IGlobalKeyServer.d.ts.map +1 -0
  46. package/{build/ts/_types → dist/types}/IGlobalKeyServer.js +1 -1
  47. package/{build/ts/_types → dist/types}/IMacConfig.d.ts +2 -0
  48. package/dist/types/IMacConfig.d.ts.map +1 -0
  49. package/{build/ts/_types → dist/types}/IMacConfig.js +1 -1
  50. package/dist/types/IWindowsConfig.d.ts.map +1 -0
  51. package/{build/ts/_types → dist/types}/IWindowsConfig.js +1 -1
  52. package/{build/ts/_types → dist/types}/IX11Config.d.ts +2 -0
  53. package/dist/types/IX11Config.d.ts.map +1 -0
  54. package/{build/ts/_types → dist/types}/IX11Config.js +1 -1
  55. package/dist/utils/permissions.d.ts +10 -0
  56. package/dist/utils/permissions.d.ts.map +1 -0
  57. package/dist/utils/permissions.js +115 -0
  58. package/dist/utils.d.ts +22 -0
  59. package/dist/utils.d.ts.map +1 -0
  60. package/dist/utils.js +39 -0
  61. package/native/MacKeyServer/main.swift +326 -0
  62. package/native/WinKeyServer/main.cpp +402 -0
  63. package/native/X11KeyServer/main.cpp +118 -0
  64. package/package.json +50 -33
  65. package/runtime/MacKeyServer +0 -0
  66. package/scripts/download-binaries.js +7 -7
  67. package/build/index.js +0 -149
  68. package/build/ts/MacKeyServer.d.ts.map +0 -1
  69. package/build/ts/MacKeyServer.js +0 -159
  70. package/build/ts/WinKeyServer.d.ts.map +0 -1
  71. package/build/ts/WinKeyServer.js +0 -88
  72. package/build/ts/X11KeyServer.d.ts.map +0 -1
  73. package/build/ts/X11KeyServer.js +0 -159
  74. package/build/ts/_data/MacGlobalKeyLookup.d.ts +0 -3
  75. package/build/ts/_data/MacGlobalKeyLookup.d.ts.map +0 -1
  76. package/build/ts/_data/MacGlobalKeyLookup.js +0 -155
  77. package/build/ts/_data/WinGlobalKeyLookup.d.ts.map +0 -1
  78. package/build/ts/_data/X11GlobalKeyLookup.d.ts +0 -3
  79. package/build/ts/_data/X11GlobalKeyLookup.d.ts.map +0 -1
  80. package/build/ts/_data/X11GlobalKeyLookup.js +0 -144
  81. package/build/ts/_types/IConfig.d.ts.map +0 -1
  82. package/build/ts/_types/IGlobalKey.d.ts.map +0 -1
  83. package/build/ts/_types/IGlobalKeyDownMap.d.ts.map +0 -1
  84. package/build/ts/_types/IGlobalKeyEvent.d.ts.map +0 -1
  85. package/build/ts/_types/IGlobalKeyListener.d.ts.map +0 -1
  86. package/build/ts/_types/IGlobalKeyListenerRaw.d.ts.map +0 -1
  87. package/build/ts/_types/IGlobalKeyLookup.d.ts.map +0 -1
  88. package/build/ts/_types/IGlobalKeyResult.d.ts.map +0 -1
  89. package/build/ts/_types/IGlobalKeyServer.d.ts.map +0 -1
  90. package/build/ts/_types/IMacConfig.d.ts.map +0 -1
  91. package/build/ts/_types/IWindowsConfig.d.ts.map +0 -1
  92. package/build/ts/_types/IX11Config.d.ts.map +0 -1
  93. package/build/ts/isSpawnEventSupported.d.ts +0 -6
  94. package/build/ts/isSpawnEventSupported.d.ts.map +0 -1
  95. package/build/ts/isSpawnEventSupported.js +0 -18
  96. /package/{build/ts/_types → dist/types}/IGlobalKeyDownMap.d.ts +0 -0
  97. /package/{build/ts/_types → dist/types}/IGlobalKeyEvent.d.ts +0 -0
  98. /package/{build/ts/_types → dist/types}/IGlobalKeyListener.d.ts +0 -0
  99. /package/{build/ts/_types → dist/types}/IGlobalKeyListenerRaw.d.ts +0 -0
  100. /package/{build/ts/_types → dist/types}/IGlobalKeyLookup.d.ts +0 -0
  101. /package/{build/ts/_types → dist/types}/IGlobalKeyResult.d.ts +0 -0
  102. /package/{build/ts/_types → dist/types}/IGlobalKeyServer.d.ts +0 -0
  103. /package/{build/ts/_types → dist/types}/IWindowsConfig.d.ts +0 -0
@@ -0,0 +1,402 @@
1
+ #include <windows.h>
2
+ #include <stdio.h>
3
+ #include <stdlib.h>
4
+ #include <iostream>
5
+ #include <thread>
6
+ #include <string>
7
+ #include <cstddef>
8
+ #include <ctime>
9
+ #include <climits>
10
+
11
+ /*
12
+ Description:
13
+ This C++ module uses `SetWindowsHookEx` to create a system-wide Low Level keyboard hook to intercept keyboard
14
+ events before they reach other applications on the operating system. Event handling is offloaded to the
15
+ calling process. The calling process is to decide whether the events should be caught or propogated.
16
+
17
+ The key information sent to the main process is like:
18
+ 4,DOWN,0,1
19
+ 4,UP,0,2
20
+ 14,DOWN,0,3
21
+ 14,UP,0,4
22
+ 37,DOWN,0,5
23
+ 37,UP,0,6
24
+ 37,DOWN,0,7
25
+ 37,UP,0,8
26
+ 31,DOWN,0,9
27
+ 31,UP,0,10
28
+
29
+ Note: the format of the information is as follows:
30
+ VirtualKeyCode,UpDownState,ScanCode,EventID
31
+ p.s. ScanCode has been removed (is 0) from the above for documentation purposes only.
32
+
33
+ when `hello` is typed. If `H` is held down for a long period of time, DOWN events are repeated until released.
34
+ 4,DOWN,35,1
35
+ 4,DOWN,35,2
36
+ 4,DOWN,35,3
37
+ 4,DOWN,35,4
38
+ 4,DOWN,35,5
39
+ 4,DOWN,35,6
40
+ 4,UP,35,7
41
+
42
+ The calling process should send "1,{id}\n" if it wants the tap to block the keypress and "0,{id}\n"
43
+ if it wants the tap to propogate the keypress to the rest of the system (where {id} is the integer id received).
44
+
45
+ Notes:
46
+
47
+ * Capturing keypresses while the windows key is down behaves differently to what you may expect. By default releasing the windows
48
+ key opens the start menu in windows, unless another key is pressed or released before the windows key is released. When a process
49
+ captures a key-down event while the windows key is down this message will not be sent to the OS, and thus will open the start menu.
50
+ In order to prevent the start menu from opening up, we send an artifical VK_HELP key up event, whenever capturing a key while the
51
+ windows key is pressed down. This also applies to the alt key. When this occurs a message "Sending VK_HELP to prevent win_key_up
52
+ triggering start menu" is also send to stderr.
53
+ * The hook procedure should process a message in less time than the data entry specified in the `LowLevelHooksTimeout` value in the
54
+ registry key `HKEY_CURRENT_USER\Control Panel\Desktop`. Note this is sometimes undefined and thus the timeout is an undocumented
55
+ amount of time. If the hook procedure times out, the hook is silently removed without being called and there is no way for the
56
+ application to know whether the hook is removed or not. In order to accommodate for this timeout, our process requires all
57
+ keystrokes to respond within 30ms of dispatch. If your process does not respond within 30ms the event will be propogated to the
58
+ rest of the system.
59
+ * The specific timeout time is declared in the `timeoutTime` global variable.
60
+
61
+ Compilation:
62
+
63
+ 1. Install mingw via VSCode plugin documentation [here](https://code.visualstudio.com/docs/languages/cpp#_example-install-mingwx64).
64
+ 2. Run the following command to build C++ application
65
+
66
+ `"C:\MinGW\bin\c++.exe" "src\bin\WinKeyServer\main.cpp" -o "bin\WinKeyServer.exe" -static-libgcc -static-libstdc++`
67
+
68
+ */
69
+
70
+ //How long to wait before timing out a key response
71
+ int timeoutTime = 30;
72
+
73
+ POINT zeroPoint { 0, 0 };
74
+
75
+ /**
76
+ * HEADERS
77
+ */
78
+ enum KeyState
79
+ {
80
+ none = 0,
81
+ down = 1,
82
+ up = 2
83
+ };
84
+ void MessageLoop();
85
+ __declspec(dllexport) LRESULT CALLBACK KeyboardEvent(int nCode, WPARAM wParam, LPARAM lParam);
86
+ __declspec(dllexport) LRESULT CALLBACK MouseEvent(int nCode, WPARAM wParam, LPARAM lParam);
87
+ bool haltPropogation(bool isMouse, bool isDown, DWORD vkCode, DWORD scanCode, POINT location);
88
+ KeyState getKeyState(WPARAM wParam);
89
+ DWORD getMouseButtonCode(WPARAM wParam);
90
+ void printErr(const char *str);
91
+ std::string awaitLine();
92
+ std::string getString();
93
+ void fakeKey(DWORD iKeyEventF, WORD vkVirtualKey);
94
+ DWORD WINAPI timeoutLoop(LPVOID lpParam);
95
+ DWORD WINAPI checkInputLoop(LPVOID lpParam);
96
+
97
+ struct data
98
+ {
99
+ char *buffer;
100
+ std::size_t size;
101
+ };
102
+
103
+ HHOOK hKeyboardHook;
104
+ HHOOK hMouseHook;
105
+ bool bIsMetaDown = false;
106
+ bool bIsAltDown;
107
+
108
+ /**
109
+ * Main process entry point
110
+ * * Create threads used for timeout
111
+ * * Register lowlevel keyboard hook
112
+ * * Begin message loop
113
+ */
114
+ int main(int argc, char **argv)
115
+ {
116
+ //Get module instance handle
117
+ HINSTANCE hInstance = GetModuleHandle(NULL);
118
+ if (!hInstance)
119
+ return 1;
120
+
121
+ //Create threads to deal with input
122
+ HANDLE timeoutThread = CreateThread(NULL, 0, timeoutLoop, NULL, 0, NULL);
123
+ HANDLE inputThread = CreateThread(NULL, 0, checkInputLoop, NULL, 0, NULL);
124
+
125
+ //Hook to global keyboard events
126
+ hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)KeyboardEvent, hInstance, 0);
127
+ hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, (HOOKPROC)MouseEvent, hInstance, 0);
128
+
129
+ //Wait app is closed
130
+ MessageLoop();
131
+
132
+ //Unhook from global keyboard events
133
+ UnhookWindowsHookEx(hKeyboardHook);
134
+ UnhookWindowsHookEx(hMouseHook);
135
+
136
+ //Dispose the threads
137
+ CloseHandle(timeoutThread);
138
+ CloseHandle(inputThread);
139
+
140
+ return 0;
141
+ }
142
+
143
+ /**
144
+ * Callback which receives and captures low level keyboard events.
145
+ * This callback should return non-zero result if the event is captured, otherwise return
146
+ * result of CallNextHookEx
147
+ */
148
+ __declspec(dllexport) LRESULT CALLBACK KeyboardEvent(int nCode, WPARAM wParam, LPARAM lParam)
149
+ {
150
+ //If key is up or down
151
+ KeyState ks = getKeyState(wParam);
152
+ if ((nCode == HC_ACTION) && ks)
153
+ {
154
+ KBDLLHOOKSTRUCT key = *((KBDLLHOOKSTRUCT *)lParam);
155
+
156
+ //Stop propogation if needed using stdio messaging. 1st param is casted lPARAM.
157
+ //Returning 1 from this function will halt propogation
158
+ if (haltPropogation(false, ks == down, key.vkCode, key.scanCode, zeroPoint))
159
+ {
160
+ //fixes issue https://github.com/LaunchMenu/node-global-key-listener/issues/3
161
+ //TODO: maybe there is a better fix for this which doesn't involve sending arbitrary key events?
162
+ if (bIsMetaDown || bIsAltDown)
163
+ {
164
+ printErr("Sending VK_HELP to prevent win_key_up triggering start menu");
165
+ fakeKey(KEYEVENTF_KEYUP, VK_HELP);
166
+ }
167
+ return 1;
168
+ }
169
+ else
170
+ {
171
+ //Work around for Windows key behaviour. See github issue #3
172
+ if (key.vkCode == VK_LWIN || key.vkCode == VK_RWIN)
173
+ bIsMetaDown = ks == down;
174
+ if (key.vkCode == VK_LMENU || key.vkCode == VK_RMENU)
175
+ bIsAltDown = ks == down;
176
+ }
177
+ }
178
+ return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
179
+ }
180
+
181
+ /**
182
+ * Callback which receives and captures low level mouse events.
183
+ * This callback should return non-zero result if the event is captured, otherwise return
184
+ * result of CallNextHookEx
185
+ */
186
+ __declspec(dllexport) LRESULT CALLBACK MouseEvent(int nCode, WPARAM wParam, LPARAM lParam)
187
+ {
188
+ MOUSEHOOKSTRUCT * pMouseStruct = (MOUSEHOOKSTRUCT *)lParam;
189
+ KeyState ks = getKeyState(wParam);
190
+ DWORD vCode = getMouseButtonCode(wParam);
191
+
192
+ if (nCode >= 0 && pMouseStruct != NULL && ks && vCode)
193
+ {
194
+ //Stop propogation if needed using stdio messaging. 1st param is casted lPARAM.
195
+ //Returning 1 from this function will halt propogation
196
+ if (haltPropogation(true, ks == down, vCode, vCode, pMouseStruct->pt))
197
+ {
198
+ return 1;
199
+ }
200
+ }
201
+
202
+ return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
203
+ }
204
+
205
+ /**
206
+ * Application's core message loop to persist application
207
+ */
208
+ void MessageLoop()
209
+ {
210
+ MSG message;
211
+ while (GetMessage(&message, NULL, 0, 0))
212
+ {
213
+ TranslateMessage(&message);
214
+ DispatchMessage(&message);
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Translates wparam to KeyState enum
220
+ * @returns up if KEYUP or SYSKEYUP, down if KEYDOWN or SYSKEYDOWN else none
221
+ */
222
+ KeyState getKeyState(WPARAM wParam)
223
+ {
224
+ switch (wParam)
225
+ {
226
+ case WM_KEYDOWN:
227
+ return down;
228
+ case WM_KEYUP:
229
+ return up;
230
+
231
+ case WM_SYSKEYDOWN:
232
+ return down;
233
+ case WM_SYSKEYUP:
234
+ return up;
235
+
236
+ case WM_LBUTTONDOWN:
237
+ return down;
238
+ case WM_LBUTTONUP:
239
+ return up;
240
+
241
+ case WM_RBUTTONDOWN:
242
+ return down;
243
+ case WM_RBUTTONUP:
244
+ return up;
245
+
246
+ case WM_MBUTTONDOWN:
247
+ return down;
248
+ case WM_MBUTTONUP:
249
+ return up;
250
+
251
+ default:
252
+ return none;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Translates wparam to VK_* key code
258
+ * @returns VK_LBUTTON, VK_RBUTTON, VK_MBUTTON or 0
259
+ */
260
+ DWORD getMouseButtonCode(WPARAM wParam)
261
+ {
262
+ switch (wParam)
263
+ {
264
+ case WM_LBUTTONDOWN:
265
+ case WM_LBUTTONUP:
266
+ return VK_LBUTTON;
267
+
268
+ case WM_RBUTTONDOWN:
269
+ case WM_RBUTTONUP:
270
+ return VK_RBUTTON;
271
+
272
+ case WM_MBUTTONDOWN:
273
+ case WM_MBUTTONUP:
274
+ return VK_MBUTTON;
275
+
276
+ default:
277
+ return 0;
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Used to send fake virtual key to system.
283
+ * @param iKeyEventF - 0 for key down event, KEYEVENTF_KEYUP for key up events and KEYEVENTF_EXTENDEDKEY for unicode(?)
284
+ * @param vkVirtualKey - Virtual key to send keypress for
285
+ */
286
+ void fakeKey(DWORD iKeyEventF, WORD vkVirtualKey)
287
+ {
288
+ INPUT inputFix;
289
+ inputFix.type = INPUT_KEYBOARD;
290
+ inputFix.ki.wVk = vkVirtualKey;
291
+ inputFix.ki.wScan = 0;
292
+ inputFix.ki.dwFlags = iKeyEventF;
293
+ inputFix.ki.time = 0;
294
+ inputFix.ki.dwExtraInfo = 0;
295
+ SendInput(1, &inputFix, sizeof(inputFix));
296
+ }
297
+
298
+ /**
299
+ * Log error message to stderr
300
+ */
301
+ void printErr(const char str[])
302
+ {
303
+ fprintf(stderr, str);
304
+ fflush(stderr);
305
+ }
306
+
307
+ // Key propagation communication with timeout
308
+ HANDLE signalMutex = CreateMutex(NULL, FALSE, NULL);
309
+ HANDLE requestTimeoutSemaphore = CreateSemaphore(NULL, 0, INT_MAX, NULL);
310
+ HANDLE responseSemaphore = CreateSemaphore(NULL, 0, INT_MAX, NULL);
311
+ long requestTime = 0;
312
+ long responseId = 0;
313
+ long timeoutId = 0;
314
+ long curId = 0;
315
+ std::string output = "";
316
+
317
+ /**
318
+ * haltPropogation
319
+ * Communicates key information with the calling process to identify whether the key event should
320
+ * be propogated to the rest of the OS.
321
+ * @param key - The key pressed.
322
+ * @param isDown - down, if a keydown event occurred, up if a keyup event occurred, none otherwise.
323
+ * @returns Whether the event should be propogated or not.
324
+ * @remark Sends a comma delimited string of the form "keyCode,DOWN,scanCode,eventID" or "keyCode,UP,scanCode,eventID".
325
+ * Expects "1\n" (halt propogation of event) or "0\n" (do not halt propogation of event)
326
+ * @remark This function timeouts after 30ms and returns false in order to propogate the event to the rest of the OS.
327
+ */
328
+ bool haltPropogation(bool isMouse, bool isDown, DWORD vkCode, DWORD scanCode, POINT location)
329
+ {
330
+ curId = curId + 1;
331
+ printf("%s,%s,%i,%i,%ld,%ld,%i\n", (isMouse ? "MOUSE" : "KEYBOARD"), (isDown ? "DOWN" : "UP"), vkCode, scanCode, location.x, location.y, curId);
332
+ fflush(stdout);
333
+
334
+ // Indicate when the next timeout should occur
335
+ requestTime = time(0) * 1000 + timeoutTime;
336
+ ReleaseSemaphore(requestTimeoutSemaphore, 1, NULL);
337
+
338
+ // Wait for any response
339
+ WaitForSingleObject(responseSemaphore, INFINITE);
340
+
341
+ return output == "1";
342
+ }
343
+
344
+ /**
345
+ * Synchronously reads a line from the stdin and tries to report the result to the haltPropogation function (if it hasn't
346
+ * timed out already)
347
+ */
348
+ DWORD WINAPI checkInputLoop(LPVOID lpParam)
349
+ {
350
+ while (true)
351
+ {
352
+ // Retrieve input and extract the code
353
+ std::string entry;
354
+ std::getline(std::cin, entry);
355
+
356
+ int index = entry.find_first_of(",");
357
+ std::string code = entry.substr(0, index);
358
+ int id = atoi((entry.substr(index + 1)).c_str());
359
+
360
+ // Lock the signalling, making sure the timeout doesn't signal it's response yet
361
+ WaitForSingleObject(signalMutex, INFINITE);
362
+ if (timeoutId < id)
363
+ {
364
+ // Set the output and signal that there is a response
365
+ responseId = id;
366
+ output = code;
367
+ ReleaseSemaphore(responseSemaphore, 1, NULL);
368
+ }
369
+ ReleaseMutex(signalMutex);
370
+ }
371
+ return 0;
372
+ }
373
+
374
+ /**
375
+ * Synchronously waits until a timeout occurs and reports this to the haltPropogation function if it hasn't received a response
376
+ * yet.
377
+ */
378
+ DWORD WINAPI timeoutLoop(LPVOID lpParam)
379
+ {
380
+ while (true)
381
+ {
382
+ // Wait for a timeout to be requested
383
+ WaitForSingleObject(requestTimeoutSemaphore, INFINITE);
384
+
385
+ // Calculate how long to sleep in order to wake up at the requested time and start sleeping
386
+ long sleepDuration = requestTime - time(0) * 1000;
387
+ if (sleepDuration > 0)
388
+ Sleep(sleepDuration);
389
+
390
+ // Lock the signalling, making sure the input signalling can't happen before we finished here
391
+ WaitForSingleObject(signalMutex, INFINITE);
392
+ timeoutId = timeoutId + 1;
393
+ if (responseId < timeoutId)
394
+ {
395
+ // Set the output to 0 and signal that there is a response
396
+ output = "0";
397
+ ReleaseSemaphore(responseSemaphore, 1, NULL);
398
+ }
399
+ ReleaseMutex(signalMutex);
400
+ }
401
+ return 0;
402
+ }
@@ -0,0 +1,118 @@
1
+ #include <iostream>
2
+ #include <X11/XKBlib.h>
3
+ #include <X11/extensions/XInput2.h>
4
+
5
+ int main() {
6
+ Display * display = XOpenDisplay(nullptr);
7
+
8
+ if (display == nullptr) {
9
+ std::cerr << "Cannot open X display" << std::endl;
10
+ exit(1);
11
+ }
12
+
13
+ int xiOpcode, queryEvent, queryError;
14
+ if (!XQueryExtension(display, "XInputExtension", &xiOpcode, &queryEvent, &queryError)) {
15
+ std::cerr << "X Input extension not available" << std::endl;
16
+ exit(2);
17
+ }
18
+
19
+ int majorVersion = 2, minorVersion = 0;
20
+ int queryResult = XIQueryVersion(display, &majorVersion, &minorVersion);
21
+ if (queryResult == BadRequest) {
22
+ std::cerr << "Expected X Input version 2.0" << std::endl;
23
+ exit(3);
24
+ } else if (queryResult != Success) {
25
+ std::cerr << "Cannot query X Input version" << std::endl;
26
+ exit(4);
27
+ }
28
+
29
+ Window rootWindow = DefaultRootWindow(display);
30
+ XIEventMask eventMask;
31
+ eventMask.deviceid = XIAllDevices;
32
+ eventMask.mask_len = XIMaskLen(XI_LASTEVENT);
33
+
34
+ eventMask.mask = (unsigned char *) calloc(eventMask.mask_len, sizeof(unsigned char));
35
+ XISetMask(eventMask.mask, XI_RawKeyPress);
36
+ XISetMask(eventMask.mask, XI_RawKeyRelease);
37
+ XISetMask(eventMask.mask, XI_RawButtonPress);
38
+ XISetMask(eventMask.mask, XI_RawButtonRelease);
39
+
40
+ XISelectEvents(display, rootWindow, &eventMask, 1);
41
+ XSync(display, false);
42
+ free(eventMask.mask);
43
+
44
+ int xkbOpcode, xkbEventCode;
45
+ if (!XkbQueryExtension(display, &xkbOpcode, &xkbEventCode, &queryError, &majorVersion, &minorVersion)) {
46
+ std::cerr << "X keyboard extension not available" << std::endl;
47
+ exit(5);
48
+ }
49
+
50
+ long eventId = 0;
51
+
52
+ int lastEvtype = 0;
53
+ KeyCode lastKeyCode = 0;
54
+
55
+ while (true) {
56
+ XEvent xEvent;
57
+ XGenericEventCookie *cookie = (XGenericEventCookie*)&xEvent.xcookie;
58
+ XNextEvent(display, &xEvent);
59
+
60
+ if (XGetEventData(display, cookie)) {
61
+ if (
62
+ cookie->type == GenericEvent
63
+ && cookie->extension == xiOpcode
64
+ ) {
65
+ XIRawEvent *xInputRawEvent = (XIRawEvent *) cookie->data;
66
+
67
+ bool isKeyboard =
68
+ cookie->evtype == XI_RawKeyRelease
69
+ || cookie->evtype == XI_RawKeyPress;
70
+
71
+ bool isMouse =
72
+ cookie->evtype == XI_RawButtonRelease
73
+ || cookie->evtype == XI_RawButtonPress;
74
+
75
+ KeyCode keyCode = xInputRawEvent->detail;
76
+ int x = 0;
77
+ int y = 0;
78
+
79
+ if (isMouse) {
80
+ Window root, child;
81
+ int windowX, windowY;
82
+ unsigned int mask;
83
+
84
+ XQueryPointer(display, rootWindow, &root, &child, &x, &y, &windowX, &windowY, &mask);
85
+ }
86
+
87
+ bool isDown =
88
+ cookie->evtype == XI_RawKeyPress
89
+ || cookie->evtype == XI_RawButtonPress;
90
+
91
+ bool isDuplicate = lastEvtype == cookie->evtype
92
+ && lastKeyCode == keyCode;
93
+
94
+ if ((isKeyboard || isMouse) && !isDuplicate) {
95
+ std::cout
96
+ << (isKeyboard ? "KEYBOARD" : "MOUSE")
97
+ << ","
98
+ << (isDown ? "DOWN" : "UP")
99
+ << ","
100
+ << (long) keyCode
101
+ << ","
102
+ << x
103
+ << ","
104
+ << y
105
+ << ","
106
+ << eventId++
107
+ << std::endl;
108
+
109
+ std::flush(std::cout);
110
+ }
111
+
112
+ lastEvtype = cookie->evtype;
113
+ lastKeyCode = keyCode;
114
+ }
115
+ XFreeEventData(display, cookie);
116
+ }
117
+ }
118
+ }
package/package.json CHANGED
@@ -1,11 +1,41 @@
1
1
  {
2
2
  "name": "keyspy",
3
- "version": "1.0.7",
3
+ "version": "1.1.1",
4
4
  "description": "A cross-platform global keyboard and mouse listener for Node.js",
5
- "main": "build/index.js",
6
- "types": "build/index.d.ts",
5
+ "keywords": [
6
+ "cross-platform",
7
+ "global",
8
+ "hook",
9
+ "key",
10
+ "keyboard",
11
+ "listener",
12
+ "monitor",
13
+ "mouse",
14
+ "node",
15
+ "spy",
16
+ "macos",
17
+ "function-keys"
18
+ ],
19
+ "author": "teomyth",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/teomyth/keyspy.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/teomyth/keyspy/issues"
27
+ },
28
+ "homepage": "https://github.com/teomyth/keyspy#readme",
29
+ "main": "dist/index.js",
30
+ "types": "dist/index.d.ts",
31
+ "bin": {
32
+ "keyspy": "bin/keyspy"
33
+ },
7
34
  "files": [
8
- "build/**/*",
35
+ "dist/**/*",
36
+ "bin/**/*",
37
+ "native/**/*",
38
+ "runtime/**/*",
9
39
  "scripts/download-binaries.js",
10
40
  "README.md",
11
41
  "LICENSE"
@@ -13,8 +43,16 @@
13
43
  "scripts": {
14
44
  "build": "tsc",
15
45
  "build:native": "node scripts/build-native.js",
16
- "clean": "rm -rf build dist coverage test-results .turbo",
46
+ "build:dev": "npm run build:native && npm run build",
47
+ "check-permissions": "npm run build && node scripts/check-permissions.js",
48
+ "clean": "rm -rf dist build runtime coverage test-results .turbo junit.xml tsconfig.tsbuildinfo",
17
49
  "dev": "tsc --watch",
50
+ "dev:setup": "npm run build:dev",
51
+ "cli": "npm run build && node dist/cli.js",
52
+ "cli:fresh": "npm run build:dev && node dist/cli.js",
53
+ "cli:v": "npm run build && node dist/cli.js -v",
54
+ "cli:vv": "npm run build && node dist/cli.js -vv",
55
+ "cli:vvv": "npm run build && node dist/cli.js -vvv",
18
56
  "format": "biome format --write .",
19
57
  "format:check": "biome format .",
20
58
  "lint": "biome lint .",
@@ -30,48 +68,27 @@
30
68
  "test": "jest",
31
69
  "test:coverage": "jest --coverage",
32
70
  "test:integration": "jest tests/integration",
33
- "test:manual": "npx ts-node examples/manual-test.ts",
71
+ "test:integration:safe": "npm run check-permissions && jest tests/integration",
34
72
  "test:unit": "jest tests/unit",
35
73
  "test:watch": "jest --watch"
36
74
  },
37
75
  "dependencies": {
38
- "@expo/sudo-prompt": "^9.3.2"
76
+ "@expo/sudo-prompt": "^9.3.2",
77
+ "cli-table3": "^0.6.5"
39
78
  },
40
79
  "devDependencies": {
41
80
  "@biomejs/biome": "^1.9.4",
42
81
  "@release-it/conventional-changelog": "^10.0.1",
43
- "@types/jest": "^29.5.14",
44
- "@types/node": "^22.15.30",
45
- "jest": "^29.7.0",
82
+ "@types/jest": "^30.0.0",
83
+ "@types/node": "^24.0.3",
84
+ "jest": "^30.0.0",
46
85
  "jest-junit": "^16.0.0",
47
86
  "release-it": "^19.0.3",
48
- "ts-jest": "^29.3.4",
87
+ "ts-jest": "^29.4.0",
49
88
  "ts-node": "^10.9.2",
50
89
  "turbo": "^2.5.4",
51
90
  "typescript": "^5.8.3"
52
91
  },
53
- "keywords": [
54
- "cross-platform",
55
- "global",
56
- "hook",
57
- "key",
58
- "keyboard",
59
- "listener",
60
- "monitor",
61
- "mouse",
62
- "node",
63
- "spy"
64
- ],
65
- "author": "teomyth",
66
- "license": "MIT",
67
- "repository": {
68
- "type": "git",
69
- "url": "git+https://github.com/teomyth/keyspy.git"
70
- },
71
- "bugs": {
72
- "url": "https://github.com/teomyth/keyspy/issues"
73
- },
74
- "homepage": "https://github.com/teomyth/keyspy#readme",
75
92
  "engines": {
76
93
  "node": ">=18.0.0"
77
94
  },
Binary file
@@ -99,13 +99,13 @@ async function downloadAndExtract() {
99
99
  try {
100
100
  const { file: binaryFile, archive } = getPlatformInfo();
101
101
 
102
- // Create bin directory
103
- const binDir = path.join(__dirname, "..", "bin");
104
- if (!fs.existsSync(binDir)) {
105
- fs.mkdirSync(binDir, { recursive: true });
102
+ // Create runtime directory for downloaded binaries
103
+ const runtimeDir = path.join(__dirname, "..", "runtime");
104
+ if (!fs.existsSync(runtimeDir)) {
105
+ fs.mkdirSync(runtimeDir, { recursive: true });
106
106
  }
107
107
 
108
- const binaryPath = path.join(binDir, binaryFile);
108
+ const binaryPath = path.join(runtimeDir, binaryFile);
109
109
 
110
110
  // Check if binary already exists
111
111
  if (fs.existsSync(binaryPath)) {
@@ -115,7 +115,7 @@ async function downloadAndExtract() {
115
115
 
116
116
  // Download URL
117
117
  const downloadUrl = `https://github.com/teomyth/keyspy/releases/download/v${version}/${archive}`;
118
- const archivePath = path.join(binDir, archive);
118
+ const archivePath = path.join(runtimeDir, archive);
119
119
 
120
120
  log(`Platform: ${platform}-${arch}`);
121
121
  log(`Binary: ${binaryFile}`);
@@ -126,7 +126,7 @@ async function downloadAndExtract() {
126
126
  log(`Downloaded: ${archive}`);
127
127
 
128
128
  // Extract archive
129
- if (extractTarGz(archivePath, binDir)) {
129
+ if (extractTarGz(archivePath, runtimeDir)) {
130
130
  log(`Extracted: ${archive}`);
131
131
 
132
132
  // Make binary executable (Unix systems)