quickwin 3.0.1 → 3.0.3

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 (163) hide show
  1. package/README.en.md +3 -0
  2. package/README.md +2 -0
  3. package/package.json +2 -3
  4. package/qwin-nowasm-x86.exe +0 -0
  5. package/qwin-nowasm.exe +0 -0
  6. package/qwin-x86.exe +0 -0
  7. package/qwin.exe +0 -0
  8. package/examples/PdfCanvas.js +0 -38
  9. package/examples/PdfCanvas.tsx +0 -54
  10. package/examples/execd/main.js +0 -23379
  11. package/examples/execd/tray.js +0 -192
  12. package/examples/execd/worker.js +0 -122
  13. package/examples/pdf_preview2.js +0 -398
  14. package/examples/pdf_preview2.ts +0 -353
  15. package/examples/pdf_viewer.js +0 -22515
  16. package/examples/pdf_viewer.tsx +0 -228
  17. package/examples/test_gallery.js +0 -18692
  18. package/examples/test_gallery.tsx +0 -291
  19. package/examples/test_react_lazy.js +0 -18560
  20. package/examples/test_react_lazy.tsx +0 -65
  21. package/examples/test_react_listbox.js +0 -18562
  22. package/examples/test_react_listbox.tsx +0 -73
  23. package/examples/test_root_window.js +0 -18532
  24. package/examples/test_root_window.tsx +0 -41
  25. package/examples/test_tab.js +0 -18563
  26. package/examples/test_tab.tsx +0 -77
  27. package/examples/tray_demo.js +0 -86
  28. package/examples/tray_demo.tsx +0 -89
  29. package/test/add.wasm +0 -0
  30. package/test/bench_ffi_callraw.js +0 -235
  31. package/test/bench_ffi_callraw.ts +0 -235
  32. package/test/bench_ffi_overhead.js +0 -139
  33. package/test/bench_ffi_overhead.ts +0 -141
  34. package/test/bench_sendmessage.js +0 -104
  35. package/test/bench_sendmessage.ts +0 -100
  36. package/test/bench_sendmessage_nonnull.js +0 -187
  37. package/test/bench_sendmessage_nonnull.ts +0 -188
  38. package/test/complex.wasm +0 -0
  39. package/test/complex_imports.wasm +0 -0
  40. package/test/debug/http_server_e2e.js +0 -31
  41. package/test/debug/minwasm.js +0 -52
  42. package/test/debug/mupdf_debug.js +0 -86
  43. package/test/debug/wasm.js +0 -122
  44. package/test/debug/wasm_export_debug.js +0 -54
  45. package/test/debug/wasm_names.js +0 -36
  46. package/test/example.pdf +0 -0
  47. package/test/global_imports.wasm +0 -0
  48. package/test/import_func.wasm +0 -0
  49. package/test/imports.wasm +0 -0
  50. package/test/mupdf-cdn.js +0 -40
  51. package/test/mupdf-cdn.ts +0 -43
  52. package/test/run.js +0 -111
  53. package/test/run.ts +0 -116
  54. package/test/sjlj.wasm +0 -0
  55. package/test/test_basic.js +0 -7
  56. package/test/test_basic.ts +0 -9
  57. package/test/test_basic_fetch.js +0 -63
  58. package/test/test_basic_fetch.ts +0 -59
  59. package/test/test_brotli.js +0 -48
  60. package/test/test_brotli.ts +0 -52
  61. package/test/test_fetch_cache.js +0 -169
  62. package/test/test_fetch_cache.ts +0 -187
  63. package/test/test_ffi.js +0 -157
  64. package/test/test_ffi.ts +0 -170
  65. package/test/test_ffi_abi.js +0 -290
  66. package/test/test_ffi_abi.ts +0 -274
  67. package/test/test_ffi_array.js +0 -116
  68. package/test/test_ffi_array.ts +0 -141
  69. package/test/test_ffi_bind.js +0 -656
  70. package/test/test_ffi_bind.ts +0 -670
  71. package/test/test_ffi_struct.js +0 -779
  72. package/test/test_ffi_struct.ts +0 -828
  73. package/test/test_ffi_struct_layout.js +0 -495
  74. package/test/test_ffi_struct_layout.ts +0 -543
  75. package/test/test_ffi_user32.js +0 -103
  76. package/test/test_ffi_user32.ts +0 -113
  77. package/test/test_ffi_windows.js +0 -172
  78. package/test/test_ffi_windows.ts +0 -187
  79. package/test/test_files.js +0 -102
  80. package/test/test_files.ts +0 -121
  81. package/test/test_frame_encoding.js +0 -128
  82. package/test/test_frame_encoding.ts +0 -132
  83. package/test/test_gdi_draw.js +0 -681
  84. package/test/test_gdi_draw.ts +0 -725
  85. package/test/test_helper.js +0 -91
  86. package/test/test_helper.ts +0 -88
  87. package/test/test_http_import.js +0 -96
  88. package/test/test_http_import.ts +0 -93
  89. package/test/test_http_server.js +0 -214
  90. package/test/test_http_server.ts +0 -211
  91. package/test/test_import_dir.js +0 -4
  92. package/test/test_import_dir.ts +0 -5
  93. package/test/test_ipv6.js +0 -134
  94. package/test/test_ipv6.ts +0 -130
  95. package/test/test_jsx.js +0 -1314
  96. package/test/test_meta_url.js +0 -3
  97. package/test/test_meta_url.ts +0 -4
  98. package/test/test_mupdf_render.js +0 -51
  99. package/test/test_mupdf_render.ts +0 -55
  100. package/test/test_mupdf_twice.js +0 -62
  101. package/test/test_mupdf_twice.ts +0 -65
  102. package/test/test_mupdf_wasm.js +0 -39
  103. package/test/test_mupdf_wasm.ts +0 -32
  104. package/test/test_net_event.js +0 -57
  105. package/test/test_net_event.ts +0 -56
  106. package/test/test_net_fetch.js +0 -384
  107. package/test/test_net_fetch.ts +0 -373
  108. package/test/test_net_websocket.js +0 -180
  109. package/test/test_net_websocket.ts +0 -164
  110. package/test/test_polyfill.js +0 -144
  111. package/test/test_polyfill.ts +0 -159
  112. package/test/test_printer.js +0 -137
  113. package/test/test_printer.ts +0 -148
  114. package/test/test_proc.js +0 -141
  115. package/test/test_proc.ts +0 -163
  116. package/test/test_react_complex.js +0 -18678
  117. package/test/test_react_counter.js +0 -18555
  118. package/test/test_react_flex.js +0 -18871
  119. package/test/test_react_hidden.js +0 -18560
  120. package/test/test_react_listview_link.js +0 -18590
  121. package/test/test_react_notify.js +0 -18797
  122. package/test/test_react_render.js +0 -18550
  123. package/test/test_registry.js +0 -74
  124. package/test/test_registry.ts +0 -95
  125. package/test/test_sysinfo.js +0 -122
  126. package/test/test_sysinfo.ts +0 -141
  127. package/test/test_url.js +0 -173
  128. package/test/test_url.ts +0 -183
  129. package/test/test_wasm_basic.js +0 -85
  130. package/test/test_wasm_basic.ts +0 -73
  131. package/test/test_wasm_import_global.js +0 -41
  132. package/test/test_wasm_import_global.ts +0 -45
  133. package/test/test_wasm_sjlj.js +0 -145
  134. package/test/test_wasm_sjlj.ts +0 -125
  135. package/test/test_wasm_types.js +0 -97
  136. package/test/test_wasm_types.ts +0 -124
  137. package/test/test_wolfssl_read.js +0 -203
  138. package/test/test_wolfssl_read.ts +0 -184
  139. package/test/test_worker.js +0 -21
  140. package/test/test_worker.ts +0 -24
  141. package/test/test_worker_concurrent.js +0 -45
  142. package/test/test_worker_concurrent.ts +0 -56
  143. package/test/test_worker_data_url.js +0 -46
  144. package/test/test_worker_data_url.ts +0 -51
  145. package/test/test_worker_fetch.js +0 -26
  146. package/test/test_worker_fetch.ts +0 -27
  147. package/test/test_worker_net.js +0 -69
  148. package/test/test_worker_net.ts +0 -66
  149. package/test/test_worker_wasm_concurrent.js +0 -44
  150. package/test/test_worker_wasm_concurrent.ts +0 -45
  151. package/test/types.wasm +0 -0
  152. package/test/worker_fetch.js +0 -13
  153. package/test/worker_fetch.ts +0 -13
  154. package/test/worker_helper.js +0 -1
  155. package/test/worker_helper.ts +0 -1
  156. package/test/worker_http.js +0 -18
  157. package/test/worker_http.ts +0 -20
  158. package/test/worker_import_lib.js +0 -12
  159. package/test/worker_import_lib.ts +0 -13
  160. package/test/worker_module.js +0 -16
  161. package/test/worker_module.ts +0 -17
  162. package/test/worker_wasm_concurrent.js +0 -22
  163. package/test/worker_wasm_concurrent.ts +0 -22
@@ -1,192 +0,0 @@
1
- // /tray_list 后端:托盘通知区枚举(可见区 + 折叠溢出区)+ 单图标可见性探针。
2
- // 从 main.tsx 拆出(worker.ts 同款配对模块),纯同步 FFI,无状态可复用。
3
- //
4
- // 通知区 = 两个容器,各藏 ToolbarWindow32:Shell_TrayWnd(可见区)/
5
- // NotifyIconOverflowWindow(折叠面板:平时隐藏窗口,点 ^ 才弹出;XP 无此窗)。
6
- // toolbar 属 explorer,TB_GETBUTTON/TB_GETITEMRECT 的出参 buffer 须落 explorer 地址
7
- // 空间——VirtualAllocEx → SendMessage → ReadProcessMemory(跨进程 SendMessage 不
8
- // marshal 指针,理由见 lib/windows/kernel32.ts 远程内存段注释)。
9
- // TBBUTTON 布局随 **explorer 位宽 = OS 位宽**(与本进程无关:x86 qwin 读 x64 explorer
10
- // 也按 64 位解析;OS64 = IsWow64Process 探针),mingw commctrl.h 实锚:
11
- // x86 20B:dwData@12(u32)、iString@16(u32) ;x64 32B:dwData@16(u64)、iString@24(u64)
12
- // (int idCommand@4 + fsState/fsStyle + bReserved[2|6] + DWORD_PTR + INT_PTR)
13
- // dwData → explorer 内 TRAYDATA{hwnd@0(ptr 宽), uID@(ptr 宽)(u32), …}——shell 把注册
14
- // 图标的消息窗口塞在这(非文档,XP~Win10 十年稳定,AHK TrayIcon_GetInfo 同源)。
15
- // chevron(^)/分隔符等非通知按钮:dwData 非指针或解出的 hWnd 过不了 IsWindow → 自然滤除。
16
- // 折叠面板未展开时按钮 rect 未布局(全 0 → null)或陈旧——overflow 区 rect 仅在面板
17
- // 展开后可靠(诚实返回,调用方先展开再取)。
18
- import * as os from 'os';
19
- import * as gui from 'gui';
20
- import { BYTE, WCHAR } from '../../lib/ffi/bind.js';
21
- import { NULL, PtrArrayBuffer } from '../../lib/ffi/ctype.js';
22
- import { ClientToScreen, FindWindow, FindWindowEx, GetClassName, GetWindowRect, GetWindowThreadProcessId, IsWindow, } from '../../lib/windows/user32.js';
23
- import { CloseHandle, GetCurrentProcess, IsWow64Process, OpenProcess, ReadProcessMemory, VirtualAllocEx, VirtualFreeEx, } from '../../lib/windows/kernel32.js';
24
- import { Shell_NotifyIconGetRect } from '../../lib/windows/shell32.js';
25
- import { NOTIFYICONIDENTIFIER, POINT, RECT } from '../../lib/windows/structs.js';
26
- const TB_GETBUTTON = 0x0417, TB_BUTTONCOUNT = 0x0418, TB_GETITEMRECT = 0x041d; // commctrl.h WM_USER+23/24/29
27
- const CLS_BUF = 512; // GetClassName 缓冲(WCHAR 数)
28
- // OS 位宽探针(须先于下三行常量):x64 进程恒 x64 OS;x86 进程看 WOW64 → 远程结构
29
- // 按 explorer 位宽解析(32 位 OS 的 IsWow64Process 恒写 0,XP SP2+ 均有此导出)。
30
- const OS64 = os.arch === 'x64' || (() => {
31
- const wow = new PtrArrayBuffer(4);
32
- return IsWow64Process(GetCurrentProcess(), wow) !== 0 && new Uint32Array(wow)[0] !== 0;
33
- })();
34
- const TBBUTTON_DWDATA = OS64 ? 16 : 12;
35
- const TBBUTTON_ISTRING = OS64 ? 24 : 16;
36
- const TBBUTTON_SZ = OS64 ? 32 : 20;
37
- const TRAYDATA_UID = OS64 ? 8 : 4; // uID 偏移 = hwnd 一个指针宽
38
- const REMOTE_BUF = 256; // TBBUTTON(≤32)/RECT(16)/tooltip(128 wchar) 分时复用一块
39
- /** 子树深搜 ToolbarWindow32:FindWindowEx 只看直接子级,而通知区工具栏埋在
40
- * TrayNotifyWnd/SysPager 等中间层下——递归全收,chevron 也可能是独立 toolbar,
41
- * 交给按钮解析按 IsWindow 滤 */
42
- function findToolbars(root) {
43
- const out = [];
44
- const cls = WCHAR.alloc(CLS_BUF);
45
- const walk = (parent) => {
46
- let child = FindWindowEx(parent, NULL, NULL, NULL);
47
- while (child) {
48
- const n = GetClassName(child, cls, CLS_BUF);
49
- if (n > 0 && WCHAR.decode(cls) === 'ToolbarWindow32')
50
- out.push(child);
51
- walk(child);
52
- child = FindWindowEx(parent, child, NULL, NULL);
53
- }
54
- };
55
- walk(root);
56
- return out;
57
- }
58
- /** 枚举一个 toolbar 的通知区按钮(远程读全程;按钮非法即跳过,不整栏失败) */
59
- function readToolbar(tb, area, out) {
60
- // TB_* 是 commctrl 通用 toolbar 消息,不在 gui 模块生成的消息枚举(AnyMsg 白名单)里
61
- // ——走 gui.SendMessage(C 直绑收裸 msg:number,运行时同为 SendMessageW)
62
- const count = gui.SendMessage(tb, TB_BUTTONCOUNT, 0, 0);
63
- if (!Number.isInteger(count) || count <= 0 || count > 512)
64
- return;
65
- const pidBuf = BYTE.alloc(4);
66
- if (GetWindowThreadProcessId(tb, pidBuf) === 0)
67
- return;
68
- const pid = new Uint32Array(pidBuf)[0];
69
- const proc = OpenProcess(0x38, 0, pid); // VM_OPERATION|VM_READ|VM_WRITE
70
- if (!proc)
71
- throw new Error('OpenProcess(explorer ' + pid + ') failed');
72
- const remote = VirtualAllocEx(proc, NULL, REMOTE_BUF, 0x3000, 0x04); // MEM_COMMIT|RESERVE, RW
73
- if (!remote) {
74
- CloseHandle(proc);
75
- throw new Error('VirtualAllocEx failed');
76
- }
77
- try {
78
- const tbBuf = BYTE.alloc(REMOTE_BUF), trBuf = BYTE.alloc(REMOTE_BUF), tipBuf = BYTE.alloc(REMOTE_BUF);
79
- const dvTb = new DataView(tbBuf), dvTr = new DataView(trBuf);
80
- for (let i = 0; i < count; i++) {
81
- if (!gui.SendMessage(tb, TB_GETBUTTON, i, remote))
82
- continue;
83
- if (!ReadProcessMemory(proc, remote, tbBuf, TBBUTTON_SZ, NULL))
84
- continue;
85
- const dwData = OS64 ? Number(dvTb.getBigUint64(TBBUTTON_DWDATA, true))
86
- : dvTb.getUint32(TBBUTTON_DWDATA, true);
87
- if (dwData < 0x10000)
88
- continue; // 分隔符/chevron:dwData 非指针
89
- if (!ReadProcessMemory(proc, dwData, trBuf, TRAYDATA_UID + 4, NULL))
90
- continue;
91
- const hWnd = OS64 ? Number(dvTr.getBigUint64(0, true)) : dvTr.getUint32(0, true);
92
- const uID = dvTr.getUint32(TRAYDATA_UID, true);
93
- if (!hWnd || !IsWindow(hWnd))
94
- continue; // 非通知区按钮滤除
95
- // rect:TB_GETITEMRECT 写 remote → 读回 4×i32 客户区 → ClientToScreen 屏幕化
96
- let rect = null;
97
- if (gui.SendMessage(tb, TB_GETITEMRECT, i, remote) && ReadProcessMemory(proc, remote, trBuf, 16, NULL)) {
98
- const L = dvTr.getInt32(0, true), T = dvTr.getInt32(4, true);
99
- const R = dvTr.getInt32(8, true), B = dvTr.getInt32(12, true);
100
- if (!(L === 0 && T === 0 && R === 0 && B === 0)) {
101
- const tl = POINT.encode({ x: L, y: T }), br = POINT.encode({ x: R, y: B });
102
- if (ClientToScreen(tb, tl.ptr) && ClientToScreen(tb, br.ptr)) {
103
- const a = POINT.decode(tl), b = POINT.decode(br);
104
- rect = { left: a.x, top: a.y, right: b.x, bottom: b.y };
105
- }
106
- }
107
- }
108
- // tooltip:iString = 远程 UTF-16 字符串(INT_PTR;<0x10000 = IS_INTRESOURCE 无文本)
109
- const iStr = OS64 ? Number(dvTb.getBigUint64(TBBUTTON_ISTRING, true))
110
- : dvTb.getUint32(TBBUTTON_ISTRING, true);
111
- let tooltip = null;
112
- if (iStr >= 0x10000 && ReadProcessMemory(proc, iStr, tipBuf, 256, NULL)) {
113
- const dvTip = new DataView(tipBuf);
114
- let s = '';
115
- for (let off = 0; off < 256; off += 2) {
116
- const c = dvTip.getUint16(off, true);
117
- if (c === 0)
118
- break;
119
- s += String.fromCharCode(c);
120
- }
121
- tooltip = s === '' ? null : s;
122
- }
123
- out.push({ area, index: i, hWnd, uID, tooltip, rect });
124
- }
125
- }
126
- finally {
127
- VirtualFreeEx(proc, remote, 0, 0x8000); // MEM_RELEASE
128
- CloseHandle(proc);
129
- }
130
- }
131
- /** 枚举容器内全部 toolbar 的图标——overflow 容器内实测仅一个 toolbar(xp 519-591 /
132
- * win7 折叠态 winRect (1,1)-(1,1) 零尺寸但按钮照读),全读即所得 */
133
- function collectArea(root, area, out) {
134
- for (const tb of findToolbars(root))
135
- readToolbar(tb, area, out);
136
- }
137
- /** 可见区只读"主条":TrayNotifyWnd 下可能多个 ToolbarWindow32——主通知区条 + explorer
138
- * 的"提升暂存"条(空时零尺寸 0 按钮;偶发≈1/4 全新启动把新注册的折叠图标暂存于此,
139
- * TB_GETITEMRECT 返回压在折叠键(^)位的幽灵 rect,展开溢出面板后图标回 overflow 条、
140
- * 幽灵消失——幽灵 rect 点下去只会命中任务栏,须滤除)。通知区条右缘恒贴时钟左界、
141
- * 图标从右往左生长(实测 xp 591 / win7 1190),暂存条恒在其左侧(空 @1136 / 幽灵
142
- * @1108)——取右缘最靠右者,幽灵自然落选;xp 单条无选择余地,无回归 */
143
- function collectVisible(root, out) {
144
- let main = null;
145
- let mainRight = -Infinity;
146
- for (const tb of findToolbars(root)) {
147
- const rc = RECT.alloc();
148
- if (!GetWindowRect(tb, rc.ptr))
149
- continue;
150
- const right = RECT.decode(rc).right;
151
- if (right > mainRight) {
152
- mainRight = right;
153
- main = tb;
154
- }
155
- }
156
- if (main)
157
- readToolbar(main, 'visible', out);
158
- }
159
- /** 通知区全量图标:可见区 Shell_TrayWnd + 折叠区 NotifyIconOverflowWindow(后者
160
- * XP 不存在 → 恒空);空托盘/查无窗口 → 空数组(不抛)。
161
- * 可见区定位在 TrayNotifyWnd 容器内——Shell_TrayWnd 下还有快速启动/窗口按钮等
162
- * ToolbarWindow32(XP 实测窗口按钮区被全树递归误收成假图标),通知区图标才有
163
- * TRAYDATA 语义;查无 TrayNotifyWnd 再回退全树(罕见布局兜底) */
164
- export function listTrayIcons() {
165
- const icons = [];
166
- const tray = FindWindow('Shell_TrayWnd', NULL);
167
- if (tray) {
168
- const notifyArea = FindWindowEx(tray, NULL, 'TrayNotifyWnd', NULL);
169
- collectVisible(notifyArea ? notifyArea : tray, icons); // NULL 为 0 → falsy 收窄
170
- }
171
- const overflow = FindWindow('NotifyIconOverflowWindow', NULL);
172
- if (overflow)
173
- collectArea(overflow, 'overflow', icons);
174
- return icons;
175
- }
176
- /** 单图标探针(Shell_NotifyIconGetRect,**Win7+**;XP 无导出 → hrName 'unsupported')
177
- * 实测语义(比 SO 结论细):hr=0 found——图标在通知区内,rect = 其容器位置,溢出面板
178
- * 未展开时是残留布局非屏幕可点位;hr=1 hidden——按文档为不可见态(本机未复现,
179
- * Win7 溢出图标实测亦返 0);E_FAIL not_found——不存在,或该图标按 GUID 注册
180
- * (网络/操作中心等系统图标实测 hWnd+uID 查不到,如实返回非 bug) */
181
- export function probeTrayIcon(hWnd, uID) {
182
- if (!Shell_NotifyIconGetRect)
183
- return { hr: -1, hrName: 'unsupported', rect: null };
184
- const rc = RECT.alloc();
185
- const hr = Shell_NotifyIconGetRect({ cbSize: NOTIFYICONIDENTIFIER.size, hWnd, uID }, rc.ptr);
186
- const hrName = hr === 0 ? 'found' : hr === 1 ? 'hidden'
187
- : hr === -2147467259 ? 'not_found' : 'hr=0x' + ((hr >>> 0).toString(16));
188
- if (hr !== 0)
189
- return { hr, hrName, rect: null };
190
- const r = RECT.decode(rc);
191
- return { hr, hrName, rect: { left: r.left, top: r.top, right: r.right, bottom: r.bottom } };
192
- }
@@ -1,122 +0,0 @@
1
- import * as os from 'os';
2
- import * as std from 'std';
3
- import { WCHAR } from '../../lib/ffi/bind.js';
4
- import { NULL, PtrArrayBuffer } from '../../lib/ffi/ctype.js';
5
- import { struct } from '../../lib/ffi/struct.js';
6
- import { CloseHandle, CreatePipe, CreateProcess, GetExitCodeProcess, GetLastError, GetSystemDirectory, ReadFile, SetHandleInformation, WaitForSingleObject, } from '../../lib/windows/kernel32.js';
7
- import { PROCESS_INFORMATION, STARTUPINFOW } from '../../lib/windows/structs.js';
8
- // worker 没有可观测的输出通道:绑定失败会静默中断 worker 脚本,主线程永远等不到消息。
9
- // 失败原因落盘 diag.log 以便定位(典型:DLL 位数与进程不匹配 -> ERROR_BAD_EXE_FORMAT 193)。
10
- const diagFile = (stage) => {
11
- try {
12
- const f = std.open('diag.log', 'a');
13
- if (f) {
14
- f.puts(`${Date.now()} ${stage}\n`);
15
- f.close();
16
- }
17
- }
18
- catch (ex) { /* best effort */ }
19
- };
20
- const HANDLE_FLAG_INHERIT = 0x1;
21
- const STARTF_USESTDHANDLES = 0x100;
22
- const WAIT_INFINITE = 0xffffffff;
23
- // STARTUPINFOW / PROCESS_INFORMATION 的布局随进程位宽(x86 68B / x64 104B),由 struct 按 os.arch 推。
24
- // os.arch 是编译期的进程指针宽度(src/quickjs-libc.c OS_ARCH),WOW64 下的 32 位进程得 ia32 布局——
25
- // 本地 win7 VM 上的 execd 就是 32 位进程跑在 64 位系统上。
26
- const parent = os.Worker.parent;
27
- // GetSystemDirectoryW 拼 cmd.exe 全路径。
28
- // 探针实测(XP/Win7 均如此):CreateProcessW 的 lpCommandLine 第一个 token 若是
29
- // 裸 "cmd.exe",PATH 解析在本环境失败(err 267 ERROR_DIRECTORY / 123 ERROR_INVALID_NAME),
30
- // 用全路径即正常。cmd 内部再解析 ping/dir 等用自己的 PATH,无此问题。
31
- let sysDir = 'C:\\Windows\\System32';
32
- try {
33
- const out = WCHAR.alloc(256);
34
- const n = GetSystemDirectory(out, 256);
35
- const s = n > 0 ? WCHAR.decode(out) : '';
36
- if (s)
37
- sysDir = s;
38
- }
39
- catch (ex) {
40
- diagFile('SYS-DIR-FAIL: ' + String(ex));
41
- }
42
- // 用 CreateProcessW + CreatePipe 直接捕获子进程 stdout(替代 winpty)。
43
- // 纯捕获场景(/exec 从不交互),管道方案比 winpty 少一层 agent 中转:
44
- // - 流式粒度更好:探针实测 XP 上逐行实时到达(winpty 攒批 ~4s)
45
- // - 无 winpty.dll / winpty-agent.exe 依赖,纯 kernel32
46
- // 编码:无统一代码页转换——cmd 内置经 /u 恒为 UTF-16LE,外部程序各自原生
47
- // (qwin= UTF-8、中文系统工具= GBK),客户端按来源分别解码。
48
- // 同步阻塞读循环(worker 线程自转),主线程事件循环不受影响。
49
- // CreatePipe 的两个 HANDLE 出参槽:一个结构两个指针字段,读回走 decode
50
- // CreatePipe 成功后两端句柄必非零(失败已在下方 throw)——字段标 '!',decode 直出 Ptr
51
- const PIPE_HANDLES = /* @__PURE__ */ struct('PIPE_HANDLES', { hRead: '<HFILE>ptr!', hWrite: '<HFILE>ptr!' });
52
- function runCmd(id, cmd) {
53
- // —— 管道 + 继承设置(读端不可继承,否则 EOF 永不触发)——
54
- const pipe = PIPE_HANDLES.alloc();
55
- if (!CreatePipe(pipe.ptr, pipe.ptr + PIPE_HANDLES.offsetOf('hWrite'), NULL, 0)) {
56
- throw new Error('CreatePipe err=' + GetLastError());
57
- }
58
- const { hRead, hWrite } = PIPE_HANDLES.decode(pipe);
59
- SetHandleInformation(hWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
60
- SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0);
61
- // —— STARTUPINFO(写端作为子进程 stdout/stderr)——
62
- const pi = PROCESS_INFORMATION.alloc();
63
- // 恒加 /u:cmd 内置输出(echo/dir/type 与 cmd 自身报错)直接写 UTF-16LE,绕开「按代码页
64
- // 编码成字节」这一步(Tiny11 缺 NLS 时中文变 ?;win7/XP 的 GBK 无损只是 ACP=936 的 locale 巧合)。
65
- // /u 在所有 Win 版本行为一致,故不分版本——早期用 RtlGetVersion 判 NT>=10 只对 win11 加 /u,已删。
66
- // 外部程序(qwin/ping 等)输出仍为各自原生编码,/u 不参与(实测 ping 仍透传 GBK)。见 .agents/CMD_ENCODING_ACP_NLS.md §8。
67
- const cmdline = `${sysDir}\\cmd.exe /u /c ${cmd}`;
68
- if (!CreateProcess(NULL, cmdline, NULL, NULL, 1, 0, NULL, NULL, { cb: STARTUPINFOW.size, dwFlags: STARTF_USESTDHANDLES, hStdOutput: hWrite, hStdError: hWrite }, pi.ptr)) {
69
- CloseHandle(hRead);
70
- CloseHandle(hWrite);
71
- throw new Error('CreateProcessW err=' + GetLastError() + ' cmd=' + cmdline);
72
- }
73
- const { hProcess, hThread, dwProcessId } = PROCESS_INFORMATION.decode(pi);
74
- CloseHandle(hThread);
75
- CloseHandle(hWrite);
76
- parent.postMessage({ type: 'info', id, pid: dwProcessId });
77
- // —— 同步阻塞读循环(子进程树全退出、写端全关 -> EOF -> 收尾)——
78
- const bufB = new PtrArrayBuffer(4096);
79
- const buf = new Uint8Array(bufB);
80
- const nReadB = new PtrArrayBuffer(4);
81
- const nRead = new Uint32Array(nReadB);
82
- for (;;) {
83
- nRead[0] = 0;
84
- if (!ReadFile(hRead, bufB, 4096, nReadB, NULL))
85
- break;
86
- const n = nRead[0];
87
- if (n === 0)
88
- break;
89
- parent.postMessage({ type: 'data', id, chunk: buf.slice(0, n) });
90
- }
91
- WaitForSingleObject(hProcess, WAIT_INFINITE);
92
- const ecB = new PtrArrayBuffer(4);
93
- const ec = new Int32Array(ecB);
94
- GetExitCodeProcess(hProcess, ecB);
95
- const code = ec[0];
96
- CloseHandle(hRead);
97
- CloseHandle(hProcess);
98
- return code;
99
- }
100
- parent.onmessage = (e) => {
101
- // worker 只剩 /exec 一个租客:外部命令不可控,必须与主进程隔离(长阻塞无上界)。
102
- // 内置端点(截屏/窗口列表/改分辨率)全部在主进程同步跑——bug 当场 throw → 500,
103
- // 而不是在 worker 里静默挂掉等 timer 兜底成 504。
104
- const msg = e.data;
105
- if (msg.type === 'run' && typeof msg.cmd === 'string') {
106
- let code = -1;
107
- let error = null;
108
- try {
109
- code = runCmd(msg.id, msg.cmd);
110
- }
111
- catch (ex) {
112
- error = String(ex);
113
- }
114
- parent.postMessage({ type: 'result', id: msg.id, code, error });
115
- }
116
- // 父侧每个请求起一个新 worker,一次只处理一个请求,处理完立刻释放自己的 port:
117
- // port_list 清空后 js_os_poll 返回 -1(src/quickjs-libc.c:2506)→ 本线程退出,
118
- // JSRuntime 随之释放。不设 null 的话线程常驻(实测 win7 30 次请求线程 4→35)。
119
- // 注意父侧 `worker.onmessage = null` 只释放父侧 port,对本线程无效——必须在这里设。
120
- // 消息已在 js_post_message_pipe 进队列,父侧读的是同一份 refcount 共享管道,线程退出不影响。
121
- parent.onmessage = null;
122
- };