quickwin 2.2.0 → 2.3.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 (111) hide show
  1. package/README.en.md +7 -7
  2. package/README.md +7 -7
  3. package/constants.ts +1063 -0
  4. package/enum_map.d.ts +129 -0
  5. package/examples/PdfCanvas.js +2 -2
  6. package/examples/PdfCanvas.tsx +3 -2
  7. package/examples/{execd.js → execd/main.js} +563 -122
  8. package/examples/execd/tray.js +192 -0
  9. package/examples/{execd_worker.js → execd/worker.js} +7 -7
  10. package/examples/pdf_preview2.js +46 -46
  11. package/examples/pdf_preview2.ts +55 -54
  12. package/examples/pdf_viewer.js +34 -19
  13. package/examples/pdf_viewer.tsx +6 -5
  14. package/examples/test_gallery.js +35 -20
  15. package/examples/test_gallery.tsx +9 -8
  16. package/examples/test_react_lazy.js +34 -19
  17. package/examples/test_react_lazy.tsx +7 -6
  18. package/examples/test_react_listbox.js +34 -19
  19. package/examples/test_react_listbox.tsx +8 -7
  20. package/examples/test_root_window.js +34 -19
  21. package/examples/test_root_window.tsx +5 -5
  22. package/examples/test_tab.js +34 -19
  23. package/examples/test_tab.tsx +7 -6
  24. package/examples/tray_demo.js +8 -8
  25. package/examples/tray_demo.tsx +9 -8
  26. package/lib/dumpRects.js +2 -2
  27. package/lib/dumpRects.ts +3 -2
  28. package/lib/fetch.js +15 -15
  29. package/lib/fetch.ts +16 -15
  30. package/lib/ffi/bind.js +3 -3
  31. package/lib/ffi/bind.ts +3 -3
  32. package/lib/http-server.js +4 -4
  33. package/lib/http-server.ts +5 -4
  34. package/lib/react-qw/components/Button.js +2 -2
  35. package/lib/react-qw/components/CheckBox.js +4 -4
  36. package/lib/react-qw/components/ComboBox.js +10 -10
  37. package/lib/react-qw/components/DateTimePicker.js +12 -12
  38. package/lib/react-qw/components/Input.js +7 -7
  39. package/lib/react-qw/components/Link.js +4 -4
  40. package/lib/react-qw/components/ListBox.js +12 -12
  41. package/lib/react-qw/components/ListView.js +35 -35
  42. package/lib/react-qw/components/PathPicker.js +3 -3
  43. package/lib/react-qw/components/ProgressBar.js +4 -4
  44. package/lib/react-qw/components/RadioButton.js +4 -4
  45. package/lib/react-qw/components/ScrollView.js +28 -28
  46. package/lib/react-qw/components/Slider.js +11 -11
  47. package/lib/react-qw/components/Tab.js +10 -10
  48. package/lib/react-qw/components/Tooltip.js +8 -8
  49. package/lib/react-qw/components/TreeView.js +20 -20
  50. package/lib/react-qw/index.js +5 -5
  51. package/lib/react-qw/index.ts +6 -5
  52. package/lib/react-qw/props.js +4 -4
  53. package/lib/react-qw/props.ts +5 -4
  54. package/lib/react-qw/reconciler.js +10 -10
  55. package/lib/react-qw/reconciler.ts +11 -10
  56. package/lib/text-measure.js +3 -3
  57. package/lib/text-measure.ts +4 -3
  58. package/lib/websocket.js +17 -17
  59. package/lib/websocket.ts +18 -17
  60. package/lib/windows/advapi32.js +7 -7
  61. package/lib/windows/advapi32.ts +7 -7
  62. package/lib/windows/gdi32.js +16 -16
  63. package/lib/windows/gdi32.ts +16 -16
  64. package/lib/windows/kernel32.js +41 -2
  65. package/lib/windows/kernel32.ts +45 -2
  66. package/lib/windows/shell32.js +24 -3
  67. package/lib/windows/shell32.ts +27 -4
  68. package/lib/windows/structs.js +54 -0
  69. package/lib/windows/structs.ts +63 -1
  70. package/lib/windows/user32.js +18 -6
  71. package/lib/windows/user32.ts +18 -6
  72. package/lib/windows/winspool.js +3 -3
  73. package/lib/windows/winspool.ts +3 -3
  74. package/package.json +4 -3
  75. package/quickwin.d.ts +32 -10
  76. package/qwin-nowasm-x86.exe +0 -0
  77. package/qwin-nowasm.exe +0 -0
  78. package/qwin-x86.exe +0 -0
  79. package/qwin.exe +0 -0
  80. package/test/test_ffi_bind.js +7 -7
  81. package/test/test_ffi_bind.ts +8 -8
  82. package/test/test_ffi_struct.js +2 -2
  83. package/test/test_ffi_struct.ts +3 -3
  84. package/test/test_ffi_windows.js +1 -1
  85. package/test/test_ffi_windows.ts +1 -1
  86. package/test/test_files.js +7 -7
  87. package/test/test_files.ts +8 -8
  88. package/test/test_gdi_draw.js +51 -51
  89. package/test/test_gdi_draw.ts +57 -57
  90. package/test/test_http_server.js +3 -3
  91. package/test/test_http_server.ts +4 -3
  92. package/test/test_ipv6.js +3 -3
  93. package/test/test_ipv6.ts +4 -3
  94. package/test/test_net_event.js +3 -3
  95. package/test/test_net_event.ts +4 -3
  96. package/test/test_printer.js +4 -4
  97. package/test/test_printer.ts +5 -5
  98. package/test/test_react_complex.js +36 -21
  99. package/test/test_react_counter.js +34 -19
  100. package/test/test_react_flex.js +36 -21
  101. package/test/test_react_hidden.js +34 -19
  102. package/test/test_react_listview_link.js +35 -20
  103. package/test/test_react_notify.js +52 -37
  104. package/test/test_react_render.js +34 -19
  105. package/test/test_registry.js +6 -6
  106. package/test/test_registry.ts +8 -8
  107. package/test/test_wolfssl_read.js +8 -8
  108. package/test/test_wolfssl_read.ts +9 -8
  109. package/examples/execd.tsx +0 -798
  110. package/examples/execd_worker.ts +0 -127
  111. package/quickwin_const.d.ts +0 -1063
@@ -0,0 +1,192 @@
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,10 +1,10 @@
1
1
  import * as os from 'os';
2
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';
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
8
  // worker 没有可观测的输出通道:绑定失败会静默中断 worker 脚本,主线程永远等不到消息。
9
9
  // 失败原因落盘 diag.log 以便定位(典型:DLL 位数与进程不匹配 -> ERROR_BAD_EXE_FORMAT 193)。
10
10
  const diagFile = (stage) => {
@@ -21,7 +21,7 @@ const HANDLE_FLAG_INHERIT = 0x1;
21
21
  const STARTF_USESTDHANDLES = 0x100;
22
22
  const WAIT_INFINITE = 0xffffffff;
23
23
  // STARTUPINFOW / PROCESS_INFORMATION 的布局随进程位宽(x86 68B / x64 104B),由 struct 按 os.arch 推。
24
- // os.arch 是编译期的进程指针宽度(quickjs-libc.c OS_ARCH),WOW64 下的 32 位进程得 ia32 布局——
24
+ // os.arch 是编译期的进程指针宽度(src/quickjs-libc.c OS_ARCH),WOW64 下的 32 位进程得 ia32 布局——
25
25
  // 本地 win7 VM 上的 execd 就是 32 位进程跑在 64 位系统上。
26
26
  const parent = os.Worker.parent;
27
27
  // GetSystemDirectoryW 拼 cmd.exe 全路径。
@@ -114,7 +114,7 @@ parent.onmessage = (e) => {
114
114
  parent.postMessage({ type: 'result', id: msg.id, code, error });
115
115
  }
116
116
  // 父侧每个请求起一个新 worker,一次只处理一个请求,处理完立刻释放自己的 port:
117
- // port_list 清空后 js_os_poll 返回 -1(quickjs-libc.c:2506)→ 本线程退出,
117
+ // port_list 清空后 js_os_poll 返回 -1(src/quickjs-libc.c:2506)→ 本线程退出,
118
118
  // JSRuntime 随之释放。不设 null 的话线程常驻(实测 win7 30 次请求线程 4→35)。
119
119
  // 注意父侧 `worker.onmessage = null` 只释放父侧 port,对本线程无效——必须在这里设。
120
120
  // 消息已在 js_post_message_pipe 进队列,父侧读的是同一份 refcount 共享管道,线程退出不影响。
@@ -161,32 +161,32 @@ function updateScrollRange() {
161
161
  scrollX = Math.min(scrollX, maxX);
162
162
  scrollY = Math.min(scrollY, maxY);
163
163
  if (currentPixmap.w > cw) {
164
- const style = gui.GetWindowLongPtr(hwndCanvas, -16 /* gui.Gwlp.STYLE */) | 0;
165
- if (!(style & 1048576 /* gui.WindowStyle.HSCROLL */)) {
166
- gui.SetWindowLongPtr(hwndCanvas, -16 /* gui.Gwlp.STYLE */, style | 1048576 /* gui.WindowStyle.HSCROLL */);
167
- gui.SetWindowPos(hwndCanvas, 0 /* gui.SetWindowPosHwnd.TOP */, 0, 0, 0, 0, 2 /* gui.SetWindowPosFlag.SWP_NOMOVE */ | 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */ | 32 /* gui.SetWindowPosFlag.SWP_FRAMECHANGED */);
164
+ const style = gui.GetWindowLongPtr(hwndCanvas, -16 /* Gwlp.STYLE */) | 0;
165
+ if (!(style & 1048576 /* WindowStyle.HSCROLL */)) {
166
+ gui.SetWindowLongPtr(hwndCanvas, -16 /* Gwlp.STYLE */, style | 1048576 /* WindowStyle.HSCROLL */);
167
+ gui.SetWindowPos(hwndCanvas, 0 /* SetWindowPosHwnd.TOP */, 0, 0, 0, 0, 2 /* SetWindowPosFlag.SWP_NOMOVE */ | 1 /* SetWindowPosFlag.SWP_NOSIZE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */ | 32 /* SetWindowPosFlag.SWP_FRAMECHANGED */);
168
168
  }
169
- gui.SetScrollInfo(hwndCanvas, 0 /* gui.ScrollBar.HORZ */, { min: 0, max: currentPixmap.w - 1, page: cw, pos: scrollX }, true);
170
- gui.ShowScrollBar(hwndCanvas, 0 /* gui.ScrollBar.HORZ */, true);
169
+ gui.SetScrollInfo(hwndCanvas, 0 /* ScrollBar.HORZ */, { min: 0, max: currentPixmap.w - 1, page: cw, pos: scrollX }, true);
170
+ gui.ShowScrollBar(hwndCanvas, 0 /* ScrollBar.HORZ */, true);
171
171
  }
172
172
  else {
173
173
  scrollX = 0;
174
- const style = gui.GetWindowLongPtr(hwndCanvas, -16 /* gui.Gwlp.STYLE */) | 0;
175
- if (style & 1048576 /* gui.WindowStyle.HSCROLL */) {
176
- gui.SetWindowLongPtr(hwndCanvas, -16 /* gui.Gwlp.STYLE */, style & ~1048576 /* gui.WindowStyle.HSCROLL */);
177
- gui.SetWindowPos(hwndCanvas, 0 /* gui.SetWindowPosHwnd.TOP */, 0, 0, 0, 0, 2 /* gui.SetWindowPosFlag.SWP_NOMOVE */ | 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */ | 32 /* gui.SetWindowPosFlag.SWP_FRAMECHANGED */);
174
+ const style = gui.GetWindowLongPtr(hwndCanvas, -16 /* Gwlp.STYLE */) | 0;
175
+ if (style & 1048576 /* WindowStyle.HSCROLL */) {
176
+ gui.SetWindowLongPtr(hwndCanvas, -16 /* Gwlp.STYLE */, style & ~1048576 /* WindowStyle.HSCROLL */);
177
+ gui.SetWindowPos(hwndCanvas, 0 /* SetWindowPosHwnd.TOP */, 0, 0, 0, 0, 2 /* SetWindowPosFlag.SWP_NOMOVE */ | 1 /* SetWindowPosFlag.SWP_NOSIZE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */ | 32 /* SetWindowPosFlag.SWP_FRAMECHANGED */);
178
178
  }
179
- gui.ShowScrollBar(hwndCanvas, 0 /* gui.ScrollBar.HORZ */, false);
179
+ gui.ShowScrollBar(hwndCanvas, 0 /* ScrollBar.HORZ */, false);
180
180
  }
181
181
  if (currentPixmap.h > ch) {
182
- gui.SetScrollInfo(hwndCanvas, 1 /* gui.ScrollBar.VERT */, { min: 0, max: currentPixmap.h - 1, page: ch, pos: scrollY }, true);
183
- gui.ShowScrollBar(hwndCanvas, 1 /* gui.ScrollBar.VERT */, true);
182
+ gui.SetScrollInfo(hwndCanvas, 1 /* ScrollBar.VERT */, { min: 0, max: currentPixmap.h - 1, page: ch, pos: scrollY }, true);
183
+ gui.ShowScrollBar(hwndCanvas, 1 /* ScrollBar.VERT */, true);
184
184
  }
185
185
  else {
186
186
  scrollY = 0;
187
- gui.ShowScrollBar(hwndCanvas, 1 /* gui.ScrollBar.VERT */, false);
187
+ gui.ShowScrollBar(hwndCanvas, 1 /* ScrollBar.VERT */, false);
188
188
  }
189
- gui.SetWindowPos(hwndContent, 0 /* gui.SetWindowPosHwnd.TOP */, -scrollX, -scrollY, 0, 0, 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
189
+ gui.SetWindowPos(hwndContent, 0 /* SetWindowPosHwnd.TOP */, -scrollX, -scrollY, 0, 0, 1 /* SetWindowPosFlag.SWP_NOSIZE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */);
190
190
  }
191
191
  function showPdf(mupdf, path, pageIdx) {
192
192
  assertNonNullable(hwndMain);
@@ -202,7 +202,7 @@ function showPdf(mupdf, path, pageIdx) {
202
202
  gui.SetWindowText(hwndMain, 'PDF 预览 - 第 ' + (pageIdx + 1) + '/' + totalPages + ' 页');
203
203
  scrollX = 0;
204
204
  scrollY = 0;
205
- gui.SetWindowPos(hwndContent, 0 /* gui.SetWindowPosHwnd.TOP */, 0, 0, pix.w, pix.h, 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */ | 2 /* gui.SetWindowPosFlag.SWP_NOMOVE */);
205
+ gui.SetWindowPos(hwndContent, 0 /* SetWindowPosHwnd.TOP */, 0, 0, pix.w, pix.h, 4 /* SetWindowPosFlag.SWP_NOZORDER */ | 2 /* SetWindowPosFlag.SWP_NOMOVE */);
206
206
  updateScrollRange();
207
207
  gui.InvalidateRect(hwndContent, null, true);
208
208
  }
@@ -217,9 +217,9 @@ function doScroll(dx, dy) {
217
217
  const maxY = Math.max(0, currentPixmap.h - ch);
218
218
  scrollX = Math.max(0, Math.min(maxX, scrollX + dx));
219
219
  scrollY = Math.max(0, Math.min(maxY, scrollY + dy));
220
- gui.SetWindowPos(hwndContent, 0 /* gui.SetWindowPosHwnd.TOP */, -scrollX, -scrollY, 0, 0, 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
221
- gui.SetScrollInfo(hwndCanvas, 0 /* gui.ScrollBar.HORZ */, { pos: scrollX }, true);
222
- gui.SetScrollInfo(hwndCanvas, 1 /* gui.ScrollBar.VERT */, { pos: scrollY }, true);
220
+ gui.SetWindowPos(hwndContent, 0 /* SetWindowPosHwnd.TOP */, -scrollX, -scrollY, 0, 0, 1 /* SetWindowPosFlag.SWP_NOSIZE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */);
221
+ gui.SetScrollInfo(hwndCanvas, 0 /* ScrollBar.HORZ */, { pos: scrollX }, true);
222
+ gui.SetScrollInfo(hwndCanvas, 1 /* ScrollBar.VERT */, { pos: scrollY }, true);
223
223
  }
224
224
  async function main() {
225
225
  const mupdf = await loadMupdf();
@@ -227,9 +227,9 @@ async function main() {
227
227
  gui.MessageBox('加载 mupdf WASM 失败');
228
228
  return;
229
229
  }
230
- const WS_CHILD = 1073741824 /* gui.WindowStyle.CHILD */;
231
- const WS_VIS = 268435456 /* gui.WindowStyle.VISIBLE */;
232
- const WS_BORDER = 8388608 /* gui.WindowStyle.BORDER */;
230
+ const WS_CHILD = 1073741824 /* WindowStyle.CHILD */;
231
+ const WS_VIS = 268435456 /* WindowStyle.VISIBLE */;
232
+ const WS_BORDER = 8388608 /* WindowStyle.BORDER */;
233
233
  const ctrlY = 12, ctrlH = 26, gap = 4, btnW = 80, btnPageW = 72;
234
234
  let hwndBtnOpen = null;
235
235
  let hwndBtnPrev = null;
@@ -240,20 +240,20 @@ async function main() {
240
240
  assertNonNullable(hwndBtnOpen);
241
241
  assertNonNullable(hwndBtnPrev);
242
242
  assertNonNullable(hwndBtnNext);
243
- if (msg === 2 /* gui.WmMsg.DESTROY */) {
243
+ if (msg === 2 /* WmMsg.DESTROY */) {
244
244
  gui.PostQuitMessage(0);
245
245
  return 0;
246
246
  }
247
- if (msg === 5 /* gui.WmMsg.SIZE */) {
247
+ if (msg === 5 /* WmMsg.SIZE */) {
248
248
  const cr = gui.GetClientRect(hwnd);
249
249
  if (cr) {
250
250
  const cw = cr.right - cr.left, ch = cr.bottom - cr.top;
251
- gui.SetWindowPos(hwndCanvas, 0 /* gui.SetWindowPosHwnd.TOP */, 0, 50, cw, Math.max(1, ch - 50), 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
252
- gui.SetWindowPos(hwndEdit, 0 /* gui.SetWindowPosHwnd.TOP */, 0, 0, Math.max(100, cw - 320), 0, 2 /* gui.SetWindowPosFlag.SWP_NOMOVE */ | 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
251
+ gui.SetWindowPos(hwndCanvas, 0 /* SetWindowPosHwnd.TOP */, 0, 50, cw, Math.max(1, ch - 50), 4 /* SetWindowPosFlag.SWP_NOZORDER */);
252
+ gui.SetWindowPos(hwndEdit, 0 /* SetWindowPosHwnd.TOP */, 0, 0, Math.max(100, cw - 320), 0, 2 /* SetWindowPosFlag.SWP_NOMOVE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */);
253
253
  }
254
254
  return gui.DefWindowProc(hwnd, msg, wParam, lParam);
255
255
  }
256
- if (msg === 273 /* gui.WmMsg.COMMAND */) {
256
+ if (msg === 273 /* WmMsg.COMMAND */) {
257
257
  const hCtrl = lParam;
258
258
  if (hCtrl === hwndBtnOpen) {
259
259
  const path = openPdfFileDialog();
@@ -276,7 +276,7 @@ async function main() {
276
276
  }
277
277
  return gui.DefWindowProc(hwnd, msg, wParam, lParam);
278
278
  });
279
- hwndMain = gui.CreateWindow('PdfViewer2', 'PDF 预览 2', 13565952 /* gui.WindowStyle.OVERLAPPEDWINDOW */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, 100, 100, 1100, 800, null, null);
279
+ hwndMain = gui.CreateWindow('PdfViewer2', 'PDF 预览 2', 13565952 /* WindowStyle.OVERLAPPEDWINDOW */ | 33554432 /* WindowStyle.CLIPCHILDREN */, 100, 100, 1100, 800, null, null);
280
280
  if (!hwndMain) {
281
281
  gui.MessageBox('创建主窗口失败');
282
282
  return;
@@ -287,7 +287,7 @@ async function main() {
287
287
  hwndBtnPrev = gui.CreateWindow('BUTTON', '上一页', WS_CHILD | WS_VIS, ctrlY + btnW + gap + 480 + gap, ctrlY, btnPageW, ctrlH, hwndMain, null);
288
288
  hwndBtnNext = gui.CreateWindow('BUTTON', '下一页', WS_CHILD | WS_VIS, ctrlY + btnW + gap + 480 + gap + btnPageW + gap, ctrlY, btnPageW, ctrlH, hwndMain, null);
289
289
  // 画布(带滚动条)
290
- hwndCanvas = gui.CreateWindow('STATIC', '', WS_CHILD | WS_VIS | 33554432 /* gui.WindowStyle.CLIPCHILDREN */ | 2097152 /* gui.WindowStyle.VSCROLL */ | 1048576 /* gui.WindowStyle.HSCROLL */, 0, 50, 1100, 750, hwndMain, null);
290
+ hwndCanvas = gui.CreateWindow('STATIC', '', WS_CHILD | WS_VIS | 33554432 /* WindowStyle.CLIPCHILDREN */ | 2097152 /* WindowStyle.VSCROLL */ | 1048576 /* WindowStyle.HSCROLL */, 0, 50, 1100, 750, hwndMain, null);
291
291
  // 内容窗口(画 pixmap)
292
292
  hwndContent = gui.CreateWindow('STATIC', '', WS_CHILD | WS_VIS, 0, 0, 100, 100, hwndCanvas, null);
293
293
  assertNonNullable(hwndMain);
@@ -299,9 +299,9 @@ async function main() {
299
299
  assertNonNullable(hwndBtnNext);
300
300
  // 内容窗口 PAINT
301
301
  gui.SetWindowProc(hwndContent, (hwnd, msg, wParam, lParam) => {
302
- if (msg === 20 /* gui.WmMsg.ERASEBKGND */)
302
+ if (msg === 20 /* WmMsg.ERASEBKGND */)
303
303
  return 1;
304
- if (msg === 15 /* gui.WmMsg.PAINT */) {
304
+ if (msg === 15 /* WmMsg.PAINT */) {
305
305
  gui.DefWindowProc(hwnd, msg, wParam, lParam);
306
306
  const pm = currentPixmap;
307
307
  if (!pm)
@@ -318,9 +318,9 @@ async function main() {
318
318
  });
319
319
  // 画布消息处理(PAINT 填白、滚动条、滚轮)
320
320
  gui.SetWindowProc(hwndCanvas, (hwnd, msg, wParam, lParam) => {
321
- if (msg === 20 /* gui.WmMsg.ERASEBKGND */)
321
+ if (msg === 20 /* WmMsg.ERASEBKGND */)
322
322
  return 1;
323
- if (msg === 15 /* gui.WmMsg.PAINT */) {
323
+ if (msg === 15 /* WmMsg.PAINT */) {
324
324
  const cr = gui.GetClientRect(hwnd);
325
325
  if (cr) {
326
326
  const cw = cr.right - cr.left, ch = cr.bottom - cr.top;
@@ -333,36 +333,36 @@ async function main() {
333
333
  gui.DefWindowProc(hwnd, msg, wParam, lParam);
334
334
  return 0;
335
335
  }
336
- if (msg === 277 /* gui.WmMsg.VSCROLL */ || msg === 276 /* gui.WmMsg.HSCROLL */) {
337
- const bar = msg === 277 /* gui.WmMsg.VSCROLL */ ? 1 /* gui.ScrollBar.VERT */ : 0 /* gui.ScrollBar.HORZ */;
336
+ if (msg === 277 /* WmMsg.VSCROLL */ || msg === 276 /* WmMsg.HSCROLL */) {
337
+ const bar = msg === 277 /* WmMsg.VSCROLL */ ? 1 /* ScrollBar.VERT */ : 0 /* ScrollBar.HORZ */;
338
338
  const info = gui.GetScrollInfo(hwnd, bar);
339
339
  const maxPos = info.max - info.page + 1;
340
340
  const code = wParam & 0xFFFF;
341
- let newPos = bar === 1 /* gui.ScrollBar.VERT */ ? scrollY : scrollX;
342
- if (code === 5 /* gui.ScrollCmd.THUMBTRACK */ || code === 4 /* gui.ScrollCmd.THUMBPOSITION */)
341
+ let newPos = bar === 1 /* ScrollBar.VERT */ ? scrollY : scrollX;
342
+ if (code === 5 /* ScrollCmd.THUMBTRACK */ || code === 4 /* ScrollCmd.THUMBPOSITION */)
343
343
  newPos = Math.max(0, Math.min(maxPos, (wParam >> 16) & 0xFFFF));
344
- else if (code === 0 /* gui.ScrollCmd.LINEUP */)
344
+ else if (code === 0 /* ScrollCmd.LINEUP */)
345
345
  newPos -= 20;
346
- else if (code === 1 /* gui.ScrollCmd.LINEDOWN */)
346
+ else if (code === 1 /* ScrollCmd.LINEDOWN */)
347
347
  newPos += 20;
348
- else if (code === 2 /* gui.ScrollCmd.PAGEUP */)
348
+ else if (code === 2 /* ScrollCmd.PAGEUP */)
349
349
  newPos -= info.page;
350
- else if (code === 3 /* gui.ScrollCmd.PAGEDOWN */)
350
+ else if (code === 3 /* ScrollCmd.PAGEDOWN */)
351
351
  newPos += info.page;
352
352
  else
353
353
  return 0;
354
354
  newPos = Math.max(0, Math.min(maxPos, newPos));
355
- if (bar === 1 /* gui.ScrollBar.VERT */)
355
+ if (bar === 1 /* ScrollBar.VERT */)
356
356
  doScroll(0, newPos - scrollY);
357
357
  else
358
358
  doScroll(newPos - scrollX, 0);
359
359
  return 0;
360
360
  }
361
- if (msg === 522 /* gui.WmMsg.MOUSEWHEEL */) {
361
+ if (msg === 522 /* WmMsg.MOUSEWHEEL */) {
362
362
  const raw = (wParam >>> 16) & 0xFFFF;
363
363
  const wheel = raw >= 0x8000 ? raw - 0x10000 : raw;
364
- const isHorz = (wParam & 4 /* gui.MouseKeyFlag.MK_SHIFT */) !== 0;
365
- const bar = isHorz ? 0 /* gui.ScrollBar.HORZ */ : 1 /* gui.ScrollBar.VERT */;
364
+ const isHorz = (wParam & 4 /* MouseKeyFlag.MK_SHIFT */) !== 0;
365
+ const bar = isHorz ? 0 /* ScrollBar.HORZ */ : 1 /* ScrollBar.VERT */;
366
366
  const info = gui.GetScrollInfo(hwnd, bar);
367
367
  const maxPos = info.max - info.page + 1;
368
368
  let newPos = (isHorz ? scrollX : scrollY) - Math.round(wheel * 40 / 120);
@@ -373,7 +373,7 @@ async function main() {
373
373
  doScroll(0, newPos - scrollY);
374
374
  return 0;
375
375
  }
376
- if (msg === 5 /* gui.WmMsg.SIZE */) {
376
+ if (msg === 5 /* WmMsg.SIZE */) {
377
377
  updateScrollRange();
378
378
  return gui.DefWindowProc(hwnd, msg, wParam, lParam);
379
379
  }