@code-yeongyu/senpi-tui 2026.9.12 → 2026.9.13
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.
- package/README.md +53 -4
- package/dist/alt-screen-search.d.ts +21 -1
- package/dist/alt-screen-search.d.ts.map +1 -1
- package/dist/alt-screen-search.js +199 -42
- package/dist/alt-screen-search.js.map +1 -1
- package/dist/components/box.d.ts +3 -1
- package/dist/components/box.d.ts.map +1 -1
- package/dist/components/box.js +28 -0
- package/dist/components/box.js.map +1 -1
- package/dist/components/editor.d.ts +6 -1
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +98 -18
- package/dist/components/editor.js.map +1 -1
- package/dist/components/input.d.ts +12 -1
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +40 -6
- package/dist/components/input.js.map +1 -1
- package/dist/components/latex.d.ts.map +1 -1
- package/dist/components/latex.js +7 -0
- package/dist/components/latex.js.map +1 -1
- package/dist/components/loader.d.ts +3 -0
- package/dist/components/loader.d.ts.map +1 -1
- package/dist/components/loader.js +20 -8
- package/dist/components/loader.js.map +1 -1
- package/dist/components/mouse-region.d.ts +12 -0
- package/dist/components/mouse-region.d.ts.map +1 -0
- package/dist/components/mouse-region.js +19 -0
- package/dist/components/mouse-region.js.map +1 -0
- package/dist/components/scroll-view.d.ts +6 -3
- package/dist/components/scroll-view.d.ts.map +1 -1
- package/dist/components/scroll-view.js +6 -1
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/select-list.d.ts +4 -1
- package/dist/components/select-list.d.ts.map +1 -1
- package/dist/components/select-list.js +48 -2
- package/dist/components/select-list.js.map +1 -1
- package/dist/components/settings-list.d.ts +5 -1
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js +54 -5
- package/dist/components/settings-list.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +3 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +32 -9
- package/dist/layout.js.map +1 -1
- package/dist/native-modifiers.d.ts +2 -1
- package/dist/native-modifiers.d.ts.map +1 -1
- package/dist/native-modifiers.js +3 -44
- package/dist/native-modifiers.js.map +1 -1
- package/dist/native-platform.d.ts +18 -0
- package/dist/native-platform.d.ts.map +1 -0
- package/dist/native-platform.js +42 -0
- package/dist/native-platform.js.map +1 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +2 -20
- package/dist/terminal.js.map +1 -1
- package/dist/tui-alt-screen.d.ts +31 -1
- package/dist/tui-alt-screen.d.ts.map +1 -1
- package/dist/tui-alt-screen.js +335 -41
- package/dist/tui-alt-screen.js.map +1 -1
- package/dist/tui.d.ts +83 -5
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +118 -4
- package/dist/tui.js.map +1 -1
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +9 -0
- package/dist/utils.js.map +1 -1
- package/native/clipboard.h +170 -0
- package/native/darwin/README.md +8 -6
- package/native/darwin/build.sh +12 -5
- package/native/darwin/prebuilds/darwin-arm64/{darwin-modifiers.node → darwin-platform.node} +0 -0
- package/native/darwin/prebuilds/darwin-x64/{darwin-modifiers.node → darwin-platform.node} +0 -0
- package/native/darwin/src/darwin-platform.m +93 -0
- package/native/linux/README.md +23 -0
- package/native/linux/build.sh +65 -0
- package/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/native/linux/src/clipboard-worker.h +70 -0
- package/native/linux/src/linux-platform-x11.c +274 -0
- package/native/napi.h +89 -0
- package/native/win32/README.md +18 -7
- package/native/win32/build.mjs +7 -4
- package/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/native/win32/src/win32-platform.c +241 -0
- package/package.json +10 -3
- package/native/darwin/src/darwin-modifiers.c +0 -70
- package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/native/win32/src/win32-console-mode.c +0 -119
|
@@ -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": "@code-yeongyu/senpi-tui",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.13",
|
|
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,6 +8,7 @@
|
|
|
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 --import tsx --import ./test/setup-multiplexer-env.mjs --test-reporter=dot --test-reporter-destination=stdout test/*.test.ts",
|
|
13
14
|
"prepublishOnly": "npm run clean && npm run build",
|
|
@@ -15,14 +16,20 @@
|
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
18
|
"dist/**/*",
|
|
19
|
+
"native/napi.h",
|
|
20
|
+
"native/clipboard.h",
|
|
18
21
|
"native/win32/prebuilds/**/*.node",
|
|
19
22
|
"native/win32/src/**/*.c",
|
|
20
23
|
"native/win32/build.mjs",
|
|
21
24
|
"native/win32/README.md",
|
|
22
25
|
"native/darwin/prebuilds/**/*.node",
|
|
23
|
-
"native/darwin/src/**/*.
|
|
26
|
+
"native/darwin/src/**/*.m",
|
|
24
27
|
"native/darwin/build.sh",
|
|
25
28
|
"native/darwin/README.md",
|
|
29
|
+
"native/linux/prebuilds/**/*.node",
|
|
30
|
+
"native/linux/src/**/*.{c,h}",
|
|
31
|
+
"native/linux/build.sh",
|
|
32
|
+
"native/linux/README.md",
|
|
26
33
|
"README.md"
|
|
27
34
|
],
|
|
28
35
|
"keywords": [
|
|
@@ -47,7 +54,7 @@
|
|
|
47
54
|
"types": "./dist/index.d.ts",
|
|
48
55
|
"dependencies": {
|
|
49
56
|
"get-east-asian-width": "1.6.0",
|
|
50
|
-
"marked": "18.0.
|
|
57
|
+
"marked": "18.0.11"
|
|
51
58
|
},
|
|
52
59
|
"devDependencies": {
|
|
53
60
|
"@xterm/headless": "6.0.0",
|
|
@@ -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
|
-
}
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
-
}
|