@earendil-works/pi-tui 0.85.0 → 0.86.0

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 (64) hide show
  1. package/dist/autocomplete.d.ts.map +1 -1
  2. package/dist/autocomplete.js +18 -11
  3. package/dist/autocomplete.js.map +1 -1
  4. package/dist/components/editor.d.ts.map +1 -1
  5. package/dist/components/editor.js +7 -6
  6. package/dist/components/editor.js.map +1 -1
  7. package/dist/components/select-list.d.ts.map +1 -1
  8. package/dist/components/select-list.js +6 -11
  9. package/dist/components/select-list.js.map +1 -1
  10. package/dist/components/settings-list.d.ts.map +1 -1
  11. package/dist/components/settings-list.js +5 -10
  12. package/dist/components/settings-list.js.map +1 -1
  13. package/dist/fuzzy.d.ts.map +1 -1
  14. package/dist/fuzzy.js +23 -22
  15. package/dist/fuzzy.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/latex.d.ts.map +1 -1
  21. package/dist/latex.js +109 -16
  22. package/dist/latex.js.map +1 -1
  23. package/dist/native-modifiers.d.ts +2 -1
  24. package/dist/native-modifiers.d.ts.map +1 -1
  25. package/dist/native-modifiers.js +3 -44
  26. package/dist/native-modifiers.js.map +1 -1
  27. package/dist/native-platform.d.ts +18 -0
  28. package/dist/native-platform.d.ts.map +1 -0
  29. package/dist/native-platform.js +42 -0
  30. package/dist/native-platform.js.map +1 -0
  31. package/dist/terminal.d.ts.map +1 -1
  32. package/dist/terminal.js +2 -20
  33. package/dist/terminal.js.map +1 -1
  34. package/dist/tui-alt-screen.d.ts +5 -3
  35. package/dist/tui-alt-screen.d.ts.map +1 -1
  36. package/dist/tui-alt-screen.js +26 -5
  37. package/dist/tui-alt-screen.js.map +1 -1
  38. package/dist/utils.d.ts +3 -0
  39. package/dist/utils.d.ts.map +1 -1
  40. package/dist/utils.js +4 -0
  41. package/dist/utils.js.map +1 -1
  42. package/native/clipboard.h +170 -0
  43. package/native/darwin/README.md +8 -6
  44. package/native/darwin/build.sh +12 -5
  45. package/native/darwin/prebuilds/darwin-arm64/{darwin-modifiers.node → darwin-platform.node} +0 -0
  46. package/native/darwin/prebuilds/darwin-x64/{darwin-modifiers.node → darwin-platform.node} +0 -0
  47. package/native/darwin/src/darwin-platform.m +93 -0
  48. package/native/linux/README.md +23 -0
  49. package/native/linux/build.sh +65 -0
  50. package/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  51. package/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  52. package/native/linux/src/clipboard-worker.h +70 -0
  53. package/native/linux/src/linux-platform-x11.c +274 -0
  54. package/native/napi.h +89 -0
  55. package/native/win32/README.md +16 -7
  56. package/native/win32/build.mjs +7 -4
  57. package/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  58. package/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  59. package/native/win32/src/win32-platform.c +241 -0
  60. package/package.json +11 -4
  61. package/native/darwin/src/darwin-modifiers.c +0 -70
  62. package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  63. package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  64. package/native/win32/src/win32-console-mode.c +0 -119
@@ -5,7 +5,7 @@ import * as path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  const scriptDir = path.dirname(fileURLToPath(import.meta.url));
8
- const sourceFile = path.join(scriptDir, "src", "win32-console-mode.c");
8
+ const sourceFile = path.join(scriptDir, "src", "win32-platform.c");
9
9
  const temporaryDir = mkdtempSync(path.join(tmpdir(), "pi-tui-win32-"));
10
10
 
11
11
  const targets = [
@@ -121,7 +121,7 @@ function resolveMingwCompiler(target) {
121
121
  function installOutput(temporaryOutput, target) {
122
122
  const outputDir = path.join(scriptDir, "prebuilds", `win32-${target.arch}`);
123
123
  mkdirSync(outputDir, { recursive: true });
124
- const output = path.join(outputDir, "win32-console-mode.node");
124
+ const output = path.join(outputDir, "win32-platform.node");
125
125
  copyFileSync(temporaryOutput, output);
126
126
  chmodSync(output, 0o755);
127
127
  console.log(`Built ${path.relative(process.cwd(), output)}`);
@@ -129,7 +129,7 @@ function installOutput(temporaryOutput, target) {
129
129
 
130
130
  function buildWithMsvc(target, vsDevCmd) {
131
131
  const buildDir = path.join(temporaryDir, `msvc-${target.arch}`);
132
- const temporaryOutput = path.join(buildDir, "win32-console-mode.node");
132
+ const temporaryOutput = path.join(buildDir, "win32-platform.node");
133
133
  mkdirSync(buildDir, { recursive: true });
134
134
 
135
135
  const batchFile = path.join(buildDir, "build.cmd");
@@ -154,6 +154,7 @@ function buildWithMsvc(target, vsDevCmd) {
154
154
  "/NOENTRY",
155
155
  "/NODEFAULTLIB",
156
156
  "kernel32.lib",
157
+ "user32.lib",
157
158
  "/OPT:REF",
158
159
  "/OPT:ICF",
159
160
  ].join(" "),
@@ -174,7 +175,7 @@ function buildWithMingw(target) {
174
175
  }
175
176
 
176
177
  const buildDir = path.join(temporaryDir, `mingw-${target.arch}`);
177
- const temporaryOutput = path.join(buildDir, "win32-console-mode.node");
178
+ const temporaryOutput = path.join(buildDir, "win32-platform.node");
178
179
  mkdirSync(buildDir, { recursive: true });
179
180
 
180
181
  const args = [
@@ -183,12 +184,14 @@ function buildWithMingw(target) {
183
184
  "-Wall",
184
185
  "-Wextra",
185
186
  "-Oz",
187
+ "-fno-builtin",
186
188
  "-shared",
187
189
  "-nostdlib",
188
190
  "-Wl,--no-entry",
189
191
  "-Wl,--strip-all",
190
192
  sourceFile,
191
193
  "-lkernel32",
194
+ "-luser32",
192
195
  "-o",
193
196
  temporaryOutput,
194
197
  ];
@@ -0,0 +1,241 @@
1
+ #define PI_CLIPBOARD_WRITE
2
+ #include "../../clipboard.h"
3
+
4
+ #ifndef BI_ALPHABITFIELDS
5
+ #define BI_ALPHABITFIELDS 6
6
+ #endif
7
+
8
+ #define KEY_PRESSED_MASK 0x8000
9
+ #define OPEN_CLIPBOARD_ATTEMPTS 10
10
+ #define OPEN_CLIPBOARD_RETRY_MS 5
11
+ #define BITMAP_FILE_HEADER_SIZE 14
12
+
13
+ static int string_equals(const char* left, const char* right) {
14
+ while (*left && *right && *left == *right) {
15
+ left++;
16
+ right++;
17
+ }
18
+ return *left == 0 && *right == 0;
19
+ }
20
+
21
+ static int is_key_pressed(int virtual_key) {
22
+ return ((unsigned short)GetAsyncKeyState(virtual_key) & KEY_PRESSED_MASK) != 0;
23
+ }
24
+
25
+ static int is_modifier_name_pressed(const char* name) {
26
+ if (string_equals(name, "shift")) return is_key_pressed(VK_SHIFT) || is_key_pressed(VK_LSHIFT) || is_key_pressed(VK_RSHIFT);
27
+ if (string_equals(name, "control")) return is_key_pressed(VK_CONTROL) || is_key_pressed(VK_LCONTROL) || is_key_pressed(VK_RCONTROL);
28
+ if (string_equals(name, "option") || string_equals(name, "alt")) return is_key_pressed(VK_MENU) || is_key_pressed(VK_LMENU) || is_key_pressed(VK_RMENU);
29
+ if (string_equals(name, "command") || string_equals(name, "super") || string_equals(name, "win")) return is_key_pressed(VK_LWIN) || is_key_pressed(VK_RWIN);
30
+ return 0;
31
+ }
32
+
33
+ static napi_value __cdecl enable_virtual_terminal_input(napi_env env, napi_callback_info info) {
34
+ (void)info;
35
+
36
+ HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
37
+ DWORD mode = 0;
38
+ bool enabled = handle != INVALID_HANDLE_VALUE &&
39
+ GetConsoleMode(handle, &mode) &&
40
+ SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_INPUT);
41
+
42
+ napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
43
+ napi_value result = 0;
44
+ if (!napi_get_boolean || napi_get_boolean(env, enabled, &result) != 0) {
45
+ return fail(env, "Could not configure console input");
46
+ }
47
+ return result;
48
+ }
49
+
50
+ static napi_value __cdecl is_modifier_pressed(napi_env env, napi_callback_info info) {
51
+ napi_get_cb_info_fn napi_get_cb_info = (napi_get_cb_info_fn)node_symbol("napi_get_cb_info");
52
+ napi_get_value_string_utf8_fn napi_get_value_string_utf8 =
53
+ (napi_get_value_string_utf8_fn)node_symbol("napi_get_value_string_utf8");
54
+ napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
55
+
56
+ bool pressed = false;
57
+ if (napi_get_cb_info && napi_get_value_string_utf8) {
58
+ size_t argc = 1;
59
+ napi_value args[1] = {0};
60
+ if (napi_get_cb_info(env, info, &argc, args, 0, 0) == 0 && argc >= 1 && args[0]) {
61
+ char name[16] = {0};
62
+ size_t copied = 0;
63
+ if (napi_get_value_string_utf8(env, args[0], name, sizeof(name), &copied) == 0) {
64
+ pressed = is_modifier_name_pressed(name);
65
+ }
66
+ }
67
+ }
68
+
69
+ napi_value result = 0;
70
+ if (!napi_get_boolean || napi_get_boolean(env, pressed, &result) != 0) {
71
+ return fail(env, "Could not inspect modifier state");
72
+ }
73
+ return result;
74
+ }
75
+
76
+ static bool open_clipboard(HWND owner) {
77
+ for (int attempt = 0; attempt < OPEN_CLIPBOARD_ATTEMPTS; attempt++) {
78
+ if (OpenClipboard(owner)) return true;
79
+ Sleep(OPEN_CLIPBOARD_RETRY_MS);
80
+ }
81
+ return false;
82
+ }
83
+
84
+ static uint16_t read_u16(const uint8_t* data) {
85
+ return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
86
+ }
87
+
88
+ static uint32_t read_u32(const uint8_t* data) {
89
+ return (uint32_t)data[0] |
90
+ ((uint32_t)data[1] << 8) |
91
+ ((uint32_t)data[2] << 16) |
92
+ ((uint32_t)data[3] << 24);
93
+ }
94
+
95
+ static void write_u32(uint8_t* data, uint32_t value) {
96
+ data[0] = (uint8_t)value;
97
+ data[1] = (uint8_t)(value >> 8);
98
+ data[2] = (uint8_t)(value >> 16);
99
+ data[3] = (uint8_t)(value >> 24);
100
+ }
101
+
102
+ static void copy_bytes(uint8_t* destination, const uint8_t* source, size_t length) {
103
+ volatile uint8_t* target = destination;
104
+ const volatile uint8_t* input = source;
105
+ for (size_t index = 0; index < length; index++) target[index] = input[index];
106
+ }
107
+
108
+ static size_t dib_pixel_offset(const uint8_t* dib, size_t size) {
109
+ if (size < 12) return 0;
110
+ uint32_t header_size = read_u32(dib);
111
+
112
+ if (header_size == 12) {
113
+ uint16_t bits_per_pixel = read_u16(dib + 10);
114
+ uint32_t color_count = bits_per_pixel <= 8 ? 1u << bits_per_pixel : 0;
115
+ size_t offset = header_size + (size_t)color_count * 3;
116
+ return offset <= size ? offset : 0;
117
+ }
118
+
119
+ if (header_size < 40 || header_size > size) return 0;
120
+ uint16_t bits_per_pixel = read_u16(dib + 14);
121
+ uint32_t compression = read_u32(dib + 16);
122
+ uint32_t color_count = read_u32(dib + 32);
123
+ if (!color_count && bits_per_pixel <= 8) color_count = 1u << bits_per_pixel;
124
+
125
+ size_t offset = header_size + (size_t)color_count * 4;
126
+ if (header_size == 40 && compression == BI_BITFIELDS) offset += 3 * sizeof(uint32_t);
127
+ if (header_size == 40 && compression == BI_ALPHABITFIELDS) offset += 4 * sizeof(uint32_t);
128
+ return offset <= size ? offset : 0;
129
+ }
130
+
131
+ static void read_clipboard_image(clipboard_job* job) {
132
+ UINT png_format = RegisterClipboardFormatW(L"PNG");
133
+ if (!(png_format && IsClipboardFormatAvailable(png_format)) &&
134
+ !IsClipboardFormatAvailable(CF_DIBV5) && !IsClipboardFormatAvailable(CF_DIB)) {
135
+ job->format = CLIPBOARD_EMPTY;
136
+ return;
137
+ }
138
+ HGLOBAL handle = png_format && IsClipboardFormatAvailable(png_format)
139
+ ? (HGLOBAL)GetClipboardData(png_format) : 0;
140
+ bool is_png = handle != 0;
141
+ if (!handle && IsClipboardFormatAvailable(CF_DIBV5)) handle = (HGLOBAL)GetClipboardData(CF_DIBV5);
142
+ if (!handle && IsClipboardFormatAvailable(CF_DIB)) handle = (HGLOBAL)GetClipboardData(CF_DIB);
143
+
144
+ const uint8_t* source = handle ? (const uint8_t*)GlobalLock(handle) : 0;
145
+ size_t size = handle ? GlobalSize(handle) : 0;
146
+ if (!source || !size) {
147
+ if (source) GlobalUnlock(handle);
148
+ job->error = "Clipboard does not contain an image";
149
+ return;
150
+ }
151
+ if (is_png) {
152
+ clipboard_copy(job, source, size, CLIPBOARD_BUFFER);
153
+ } else {
154
+ size_t offset = dib_pixel_offset(source, size);
155
+ uint8_t* bitmap = offset && size <= UINT32_MAX - BITMAP_FILE_HEADER_SIZE
156
+ ? clipboard_alloc(size + BITMAP_FILE_HEADER_SIZE) : 0;
157
+ if (bitmap) {
158
+ bitmap[0] = 'B';
159
+ bitmap[1] = 'M';
160
+ write_u32(bitmap + 2, (uint32_t)(size + BITMAP_FILE_HEADER_SIZE));
161
+ write_u32(bitmap + 10, (uint32_t)(BITMAP_FILE_HEADER_SIZE + offset));
162
+ copy_bytes(bitmap + BITMAP_FILE_HEADER_SIZE, source, size);
163
+ job->data = bitmap;
164
+ job->length = size + BITMAP_FILE_HEADER_SIZE;
165
+ job->format = CLIPBOARD_BUFFER;
166
+ } else {
167
+ job->error = "Could not create clipboard image buffer";
168
+ }
169
+ }
170
+ GlobalUnlock(handle);
171
+ }
172
+
173
+ static void clipboard_execute(clipboard_job* job) {
174
+ // A real clipboard owner is required for text writes, including under Node
175
+ // without a console. Create and destroy it on the same worker thread.
176
+ HWND owner = 0;
177
+ if (job->operation == CLIPBOARD_WRITE) {
178
+ owner = CreateWindowExW(0, L"STATIC", 0, 0, 0, 0, 0, 0,
179
+ HWND_MESSAGE, 0, GetModuleHandleA(0), 0);
180
+ if (!owner) {
181
+ job->error = "Could not create clipboard owner window";
182
+ return;
183
+ }
184
+ }
185
+ if (!open_clipboard(owner)) {
186
+ if (owner) DestroyWindow(owner);
187
+ job->error = "Could not open clipboard";
188
+ return;
189
+ }
190
+ if (job->operation == CLIPBOARD_WRITE) {
191
+ size_t size = (job->length + 1) * sizeof(uint16_t);
192
+ HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE, size);
193
+ void* text = handle ? GlobalLock(handle) : 0;
194
+ bool success = false;
195
+ if (text) {
196
+ copy_bytes(text, job->data, size);
197
+ GlobalUnlock(handle);
198
+ success = EmptyClipboard() && SetClipboardData(CF_UNICODETEXT, handle) != 0;
199
+ }
200
+ if (!success) {
201
+ if (handle) GlobalFree(handle);
202
+ job->error = "Could not set clipboard text";
203
+ }
204
+ } else if (job->operation == CLIPBOARD_TEXT) {
205
+ if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) {
206
+ job->format = CLIPBOARD_EMPTY;
207
+ } else {
208
+ HGLOBAL handle = (HGLOBAL)GetClipboardData(CF_UNICODETEXT);
209
+ const uint16_t* text = handle ? GlobalLock(handle) : 0;
210
+ if (text) {
211
+ size_t capacity = GlobalSize(handle) / sizeof(uint16_t);
212
+ size_t length = 0;
213
+ while (length < capacity && text[length]) length++;
214
+ clipboard_copy(job, text, length * sizeof(uint16_t), CLIPBOARD_UTF16);
215
+ GlobalUnlock(handle);
216
+ } else {
217
+ job->error = "Clipboard does not contain text";
218
+ }
219
+ }
220
+ } else {
221
+ read_clipboard_image(job);
222
+ }
223
+ CloseClipboard();
224
+ if (owner) DestroyWindow(owner);
225
+ }
226
+
227
+ BOOL WINAPI _DllMainCRTStartup(HINSTANCE instance, DWORD reason, LPVOID reserved) {
228
+ (void)instance;
229
+ (void)reason;
230
+ (void)reserved;
231
+ return TRUE;
232
+ }
233
+
234
+ PI_NAPI_EXPORT napi_value PI_NAPI_CALL napi_register_module_v1(napi_env env, napi_value exports) {
235
+ set_function_export(env, exports, "enableVirtualTerminalInput", enable_virtual_terminal_input);
236
+ set_function_export(env, exports, "isModifierPressed", is_modifier_pressed);
237
+ set_function_export(env, exports, "getText", get_clipboard_text);
238
+ set_function_export(env, exports, "setText", set_clipboard_text);
239
+ set_function_export(env, exports, "getImage", get_clipboard_image);
240
+ return exports;
241
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-tui",
3
- "version": "0.85.0",
3
+ "version": "0.86.0",
4
4
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,20 +8,27 @@
8
8
  "clean": "shx rm -rf dist",
9
9
  "build": "tsgo -p tsconfig.build.json",
10
10
  "build:native:darwin": "native/darwin/build.sh",
11
+ "build:native:linux": "native/linux/build.sh",
11
12
  "build:native:win32": "node native/win32/build.mjs",
12
13
  "test": "node --test --test-reporter=dot --test-reporter-destination=stdout test/*.test.ts",
13
14
  "prepublishOnly": "npm run clean && npm run build"
14
15
  },
15
16
  "files": [
16
17
  "dist/**/*",
18
+ "native/napi.h",
19
+ "native/clipboard.h",
17
20
  "native/win32/prebuilds/**/*.node",
18
21
  "native/win32/src/**/*.c",
19
22
  "native/win32/build.mjs",
20
23
  "native/win32/README.md",
21
24
  "native/darwin/prebuilds/**/*.node",
22
- "native/darwin/src/**/*.c",
25
+ "native/darwin/src/**/*.m",
23
26
  "native/darwin/build.sh",
24
27
  "native/darwin/README.md",
28
+ "native/linux/prebuilds/**/*.node",
29
+ "native/linux/src/**/*.{c,h}",
30
+ "native/linux/build.sh",
31
+ "native/linux/README.md",
25
32
  "README.md"
26
33
  ],
27
34
  "keywords": [
@@ -46,10 +53,10 @@
46
53
  "types": "./dist/index.d.ts",
47
54
  "dependencies": {
48
55
  "get-east-asian-width": "1.6.0",
49
- "marked": "18.0.5"
56
+ "marked": "18.0.11"
50
57
  },
51
58
  "devDependencies": {
52
59
  "@xterm/headless": "5.5.0",
53
- "chalk": "5.6.2"
60
+ "chalk": "6.0.0"
54
61
  }
55
62
  }
@@ -1,70 +0,0 @@
1
- #include <CoreGraphics/CoreGraphics.h>
2
- #include <dlfcn.h>
3
- #include <stdbool.h>
4
- #include <stddef.h>
5
- #include <string.h>
6
-
7
- #define NAPI_AUTO_LENGTH ((size_t)-1)
8
-
9
- typedef void* napi_env;
10
- typedef void* napi_value;
11
- typedef void* napi_callback_info;
12
- typedef napi_value (*napi_callback)(napi_env, napi_callback_info);
13
- typedef int (*napi_create_function_fn)(napi_env, const char*, size_t, napi_callback, void*, napi_value*);
14
- typedef int (*napi_set_named_property_fn)(napi_env, napi_value, const char*, napi_value);
15
- typedef int (*napi_get_boolean_fn)(napi_env, bool, napi_value*);
16
- typedef int (*napi_get_cb_info_fn)(napi_env, napi_callback_info, size_t*, napi_value*, napi_value*, void**);
17
- typedef int (*napi_get_value_string_utf8_fn)(napi_env, napi_value, char*, size_t, size_t*);
18
-
19
- static void* node_symbol(const char* name) {
20
- return dlsym(RTLD_DEFAULT, name);
21
- }
22
-
23
- static CGEventFlags modifier_mask_for_name(const char* name) {
24
- if (strcmp(name, "shift") == 0) return kCGEventFlagMaskShift;
25
- if (strcmp(name, "command") == 0) return kCGEventFlagMaskCommand;
26
- if (strcmp(name, "control") == 0) return kCGEventFlagMaskControl;
27
- if (strcmp(name, "option") == 0) return kCGEventFlagMaskAlternate;
28
- return 0;
29
- }
30
-
31
- static napi_value is_modifier_pressed(napi_env env, napi_callback_info info) {
32
- napi_get_cb_info_fn napi_get_cb_info = (napi_get_cb_info_fn)node_symbol("napi_get_cb_info");
33
- napi_get_value_string_utf8_fn napi_get_value_string_utf8 = (napi_get_value_string_utf8_fn)node_symbol("napi_get_value_string_utf8");
34
- napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
35
-
36
- bool pressed = false;
37
- if (napi_get_cb_info && napi_get_value_string_utf8) {
38
- size_t argc = 1;
39
- napi_value args[1] = {0};
40
- if (napi_get_cb_info(env, info, &argc, args, 0, 0) == 0 && argc >= 1 && args[0]) {
41
- char name[16] = {0};
42
- size_t copied = 0;
43
- if (napi_get_value_string_utf8(env, args[0], name, sizeof(name), &copied) == 0) {
44
- CGEventFlags mask = modifier_mask_for_name(name);
45
- if (mask != 0) {
46
- CGEventFlags flags = CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState);
47
- pressed = (flags & mask) != 0;
48
- }
49
- }
50
- }
51
- }
52
-
53
- napi_value result = 0;
54
- if (napi_get_boolean) napi_get_boolean(env, pressed, &result);
55
- return result;
56
- }
57
-
58
- __attribute__((visibility("default"))) napi_value napi_register_module_v1(napi_env env, napi_value exports) {
59
- napi_create_function_fn napi_create_function = (napi_create_function_fn)node_symbol("napi_create_function");
60
- napi_set_named_property_fn napi_set_named_property = (napi_set_named_property_fn)node_symbol("napi_set_named_property");
61
-
62
- napi_value fn = 0;
63
- if (napi_create_function &&
64
- napi_set_named_property &&
65
- napi_create_function(env, "isModifierPressed", NAPI_AUTO_LENGTH, is_modifier_pressed, 0, &fn) == 0) {
66
- napi_set_named_property(env, exports, "isModifierPressed", fn);
67
- }
68
-
69
- return exports;
70
- }
@@ -1,119 +0,0 @@
1
- #include <windows.h>
2
-
3
- #ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
4
- #define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
5
- #endif
6
-
7
- #define NAPI_AUTO_LENGTH ((unsigned long long)-1)
8
- #define KEY_PRESSED_MASK 0x8000
9
-
10
- typedef void* napi_env;
11
- typedef void* napi_value;
12
- typedef void* napi_callback_info;
13
- typedef napi_value (__cdecl *napi_callback)(napi_env, napi_callback_info);
14
- typedef int (__cdecl *napi_create_function_fn)(napi_env, const char*, unsigned long long, napi_callback, void*, napi_value*);
15
- typedef int (__cdecl *napi_set_named_property_fn)(napi_env, napi_value, const char*, napi_value);
16
- typedef int (__cdecl *napi_get_boolean_fn)(napi_env, int, napi_value*);
17
- typedef int (__cdecl *napi_get_cb_info_fn)(napi_env, napi_callback_info, unsigned long long*, napi_value*, napi_value*, void**);
18
- typedef int (__cdecl *napi_get_value_string_utf8_fn)(napi_env, napi_value, char*, unsigned long long, unsigned long long*);
19
- typedef SHORT (WINAPI *get_async_key_state_fn)(int);
20
-
21
- static void* node_symbol(const char* name) {
22
- HMODULE module = GetModuleHandleA(0);
23
- void* proc = module ? (void*)GetProcAddress(module, name) : 0;
24
- if (proc) return proc;
25
-
26
- module = GetModuleHandleA("node.dll");
27
- return module ? (void*)GetProcAddress(module, name) : 0;
28
- }
29
-
30
- static int string_equals(const char* left, const char* right) {
31
- while (*left && *right && *left == *right) {
32
- left++;
33
- right++;
34
- }
35
- return *left == 0 && *right == 0;
36
- }
37
-
38
- static get_async_key_state_fn get_async_key_state_symbol(void) {
39
- static int loaded = 0;
40
- static get_async_key_state_fn get_async_key_state = 0;
41
-
42
- if (!loaded) {
43
- HMODULE module = GetModuleHandleA("user32.dll");
44
- if (!module) module = LoadLibraryA("user32.dll");
45
- get_async_key_state = module ? (get_async_key_state_fn)GetProcAddress(module, "GetAsyncKeyState") : 0;
46
- loaded = 1;
47
- }
48
-
49
- return get_async_key_state;
50
- }
51
-
52
- static int is_key_pressed(int virtual_key) {
53
- get_async_key_state_fn get_async_key_state = get_async_key_state_symbol();
54
- return get_async_key_state && (((unsigned short)get_async_key_state(virtual_key)) & KEY_PRESSED_MASK) != 0;
55
- }
56
-
57
- static int is_modifier_name_pressed(const char* name) {
58
- if (string_equals(name, "shift")) return is_key_pressed(VK_SHIFT) || is_key_pressed(VK_LSHIFT) || is_key_pressed(VK_RSHIFT);
59
- if (string_equals(name, "control")) return is_key_pressed(VK_CONTROL) || is_key_pressed(VK_LCONTROL) || is_key_pressed(VK_RCONTROL);
60
- if (string_equals(name, "option") || string_equals(name, "alt")) return is_key_pressed(VK_MENU) || is_key_pressed(VK_LMENU) || is_key_pressed(VK_RMENU);
61
- if (string_equals(name, "command") || string_equals(name, "super") || string_equals(name, "win")) return is_key_pressed(VK_LWIN) || is_key_pressed(VK_RWIN);
62
- return 0;
63
- }
64
-
65
- static napi_value __cdecl enable_virtual_terminal_input(napi_env env, napi_callback_info info) {
66
- (void)info;
67
-
68
- HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
69
- DWORD mode = 0;
70
- int enabled = handle != INVALID_HANDLE_VALUE &&
71
- GetConsoleMode(handle, &mode) &&
72
- SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_INPUT);
73
-
74
- napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
75
- napi_value result = 0;
76
- if (napi_get_boolean) napi_get_boolean(env, enabled, &result);
77
- return result;
78
- }
79
-
80
- static napi_value __cdecl is_modifier_pressed(napi_env env, napi_callback_info info) {
81
- napi_get_cb_info_fn napi_get_cb_info = (napi_get_cb_info_fn)node_symbol("napi_get_cb_info");
82
- napi_get_value_string_utf8_fn napi_get_value_string_utf8 = (napi_get_value_string_utf8_fn)node_symbol("napi_get_value_string_utf8");
83
- napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
84
-
85
- int pressed = 0;
86
- if (napi_get_cb_info && napi_get_value_string_utf8) {
87
- unsigned long long argc = 1;
88
- napi_value args[1] = {0};
89
- if (napi_get_cb_info(env, info, &argc, args, 0, 0) == 0 && argc >= 1 && args[0]) {
90
- char name[16] = {0};
91
- unsigned long long copied = 0;
92
- if (napi_get_value_string_utf8(env, args[0], name, sizeof(name), &copied) == 0) {
93
- pressed = is_modifier_name_pressed(name);
94
- }
95
- }
96
- }
97
-
98
- napi_value result = 0;
99
- if (napi_get_boolean) napi_get_boolean(env, pressed, &result);
100
- return result;
101
- }
102
-
103
- static void set_function_export(napi_env env, napi_value exports, const char* name, napi_callback callback) {
104
- napi_create_function_fn napi_create_function = (napi_create_function_fn)node_symbol("napi_create_function");
105
- napi_set_named_property_fn napi_set_named_property = (napi_set_named_property_fn)node_symbol("napi_set_named_property");
106
-
107
- napi_value fn = 0;
108
- if (napi_create_function &&
109
- napi_set_named_property &&
110
- napi_create_function(env, name, NAPI_AUTO_LENGTH, callback, 0, &fn) == 0) {
111
- napi_set_named_property(env, exports, name, fn);
112
- }
113
- }
114
-
115
- __declspec(dllexport) napi_value __cdecl napi_register_module_v1(napi_env env, napi_value exports) {
116
- set_function_export(env, exports, "enableVirtualTerminalInput", enable_virtual_terminal_input);
117
- set_function_export(env, exports, "isModifierPressed", is_modifier_pressed);
118
- return exports;
119
- }