quickwin 2.1.1 → 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.
- package/README.en.md +9 -9
- package/README.md +9 -9
- package/constants.ts +1063 -0
- package/enum_map.d.ts +129 -0
- package/examples/PdfCanvas.js +2 -2
- package/examples/PdfCanvas.tsx +3 -2
- package/examples/{exec_server.js → execd/main.js} +1114 -287
- package/examples/execd/tray.js +192 -0
- package/examples/execd/worker.js +122 -0
- package/examples/pdf_preview2.js +46 -46
- package/examples/pdf_preview2.ts +55 -54
- package/examples/pdf_viewer.js +134 -130
- package/examples/pdf_viewer.tsx +6 -5
- package/examples/test_gallery.js +135 -131
- package/examples/test_gallery.tsx +9 -8
- package/examples/test_react_lazy.js +130 -128
- package/examples/test_react_lazy.tsx +7 -6
- package/examples/test_react_listbox.js +130 -128
- package/examples/test_react_listbox.tsx +8 -7
- package/examples/test_root_window.js +130 -128
- package/examples/test_root_window.tsx +5 -5
- package/examples/test_tab.js +130 -128
- package/examples/test_tab.tsx +7 -6
- package/examples/tray_demo.js +8 -8
- package/examples/tray_demo.tsx +9 -8
- package/lib/dumpRects.js +2 -2
- package/lib/dumpRects.ts +3 -2
- package/lib/fetch.js +15 -15
- package/lib/fetch.ts +16 -15
- package/lib/ffi/bind.js +3 -3
- package/lib/ffi/bind.ts +3 -3
- package/lib/ffi/ctype.ts +3 -1
- package/lib/ffi/struct.js +23 -7
- package/lib/ffi/struct.ts +50 -23
- package/lib/http-server.js +5 -5
- package/lib/http-server.ts +6 -5
- package/lib/react-qw/components/Button.js +2 -2
- package/lib/react-qw/components/CheckBox.js +4 -4
- package/lib/react-qw/components/ComboBox.js +10 -10
- package/lib/react-qw/components/DateTimePicker.js +12 -12
- package/lib/react-qw/components/Input.js +7 -7
- package/lib/react-qw/components/Link.js +4 -4
- package/lib/react-qw/components/ListBox.js +12 -12
- package/lib/react-qw/components/ListView.js +35 -35
- package/lib/react-qw/components/PathPicker.js +3 -3
- package/lib/react-qw/components/ProgressBar.js +4 -4
- package/lib/react-qw/components/RadioButton.js +4 -4
- package/lib/react-qw/components/ScrollView.js +28 -28
- package/lib/react-qw/components/Slider.js +11 -11
- package/lib/react-qw/components/Tab.js +10 -10
- package/lib/react-qw/components/Tooltip.js +8 -8
- package/lib/react-qw/components/TreeView.js +20 -20
- package/lib/react-qw/index.js +5 -5
- package/lib/react-qw/index.ts +6 -5
- package/lib/react-qw/props.js +4 -4
- package/lib/react-qw/props.ts +5 -4
- package/lib/react-qw/reconciler.js +10 -10
- package/lib/react-qw/reconciler.ts +11 -10
- package/lib/text-measure.js +3 -3
- package/lib/text-measure.ts +4 -3
- package/lib/websocket.js +17 -17
- package/lib/websocket.ts +18 -17
- package/lib/windows/advapi32.js +7 -7
- package/lib/windows/advapi32.ts +7 -7
- package/lib/windows/gdi32.js +17 -17
- package/lib/windows/gdi32.ts +17 -17
- package/lib/windows/kernel32.js +41 -2
- package/lib/windows/kernel32.ts +45 -2
- package/lib/windows/shell32.js +24 -3
- package/lib/windows/shell32.ts +27 -4
- package/lib/windows/structs.js +230 -92
- package/lib/windows/structs.ts +278 -92
- package/lib/windows/user32.js +18 -6
- package/lib/windows/user32.ts +18 -6
- package/lib/windows/winspool.js +3 -3
- package/lib/windows/winspool.ts +3 -3
- package/package.json +4 -3
- package/quickwin.d.ts +32 -10
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/test/test_ffi_bind.js +12 -10
- package/test/test_ffi_bind.ts +14 -12
- package/test/test_ffi_struct.js +60 -26
- package/test/test_ffi_struct.ts +58 -28
- package/test/test_ffi_windows.js +1 -1
- package/test/test_ffi_windows.ts +1 -1
- package/test/test_files.js +7 -7
- package/test/test_files.ts +8 -8
- package/test/test_gdi_draw.js +51 -51
- package/test/test_gdi_draw.ts +57 -57
- package/test/test_http_server.js +3 -3
- package/test/test_http_server.ts +4 -3
- package/test/test_ipv6.js +3 -3
- package/test/test_ipv6.ts +4 -3
- package/test/test_net_event.js +3 -3
- package/test/test_net_event.ts +4 -3
- package/test/test_printer.js +4 -4
- package/test/test_printer.ts +5 -5
- package/test/test_react_complex.js +132 -130
- package/test/test_react_counter.js +130 -128
- package/test/test_react_flex.js +132 -130
- package/test/test_react_hidden.js +130 -128
- package/test/test_react_listview_link.js +131 -129
- package/test/test_react_notify.js +148 -146
- package/test/test_react_render.js +130 -128
- package/test/test_registry.js +6 -6
- package/test/test_registry.ts +8 -8
- package/test/test_wolfssl_read.js +8 -8
- package/test/test_wolfssl_read.ts +9 -8
- package/examples/exec_server.tsx +0 -488
- package/examples/exec_server_worker.js +0 -395
- package/examples/exec_server_worker.ts +0 -417
- package/examples/setres.js +0 -79
- package/examples/setres.ts +0 -86
- package/quickwin_const.d.ts +0 -1037
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
};
|
package/examples/pdf_preview2.js
CHANGED
|
@@ -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 /*
|
|
165
|
-
if (!(style & 1048576 /*
|
|
166
|
-
gui.SetWindowLongPtr(hwndCanvas, -16 /*
|
|
167
|
-
gui.SetWindowPos(hwndCanvas, 0 /*
|
|
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 /*
|
|
170
|
-
gui.ShowScrollBar(hwndCanvas, 0 /*
|
|
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 /*
|
|
175
|
-
if (style & 1048576 /*
|
|
176
|
-
gui.SetWindowLongPtr(hwndCanvas, -16 /*
|
|
177
|
-
gui.SetWindowPos(hwndCanvas, 0 /*
|
|
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 /*
|
|
179
|
+
gui.ShowScrollBar(hwndCanvas, 0 /* ScrollBar.HORZ */, false);
|
|
180
180
|
}
|
|
181
181
|
if (currentPixmap.h > ch) {
|
|
182
|
-
gui.SetScrollInfo(hwndCanvas, 1 /*
|
|
183
|
-
gui.ShowScrollBar(hwndCanvas, 1 /*
|
|
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 /*
|
|
187
|
+
gui.ShowScrollBar(hwndCanvas, 1 /* ScrollBar.VERT */, false);
|
|
188
188
|
}
|
|
189
|
-
gui.SetWindowPos(hwndContent, 0 /*
|
|
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 /*
|
|
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 /*
|
|
221
|
-
gui.SetScrollInfo(hwndCanvas, 0 /*
|
|
222
|
-
gui.SetScrollInfo(hwndCanvas, 1 /*
|
|
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 /*
|
|
231
|
-
const WS_VIS = 268435456 /*
|
|
232
|
-
const WS_BORDER = 8388608 /*
|
|
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 /*
|
|
243
|
+
if (msg === 2 /* WmMsg.DESTROY */) {
|
|
244
244
|
gui.PostQuitMessage(0);
|
|
245
245
|
return 0;
|
|
246
246
|
}
|
|
247
|
-
if (msg === 5 /*
|
|
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 /*
|
|
252
|
-
gui.SetWindowPos(hwndEdit, 0 /*
|
|
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 /*
|
|
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 /*
|
|
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 /*
|
|
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 /*
|
|
302
|
+
if (msg === 20 /* WmMsg.ERASEBKGND */)
|
|
303
303
|
return 1;
|
|
304
|
-
if (msg === 15 /*
|
|
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 /*
|
|
321
|
+
if (msg === 20 /* WmMsg.ERASEBKGND */)
|
|
322
322
|
return 1;
|
|
323
|
-
if (msg === 15 /*
|
|
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 /*
|
|
337
|
-
const bar = msg === 277 /*
|
|
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 /*
|
|
342
|
-
if (code === 5 /*
|
|
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 /*
|
|
344
|
+
else if (code === 0 /* ScrollCmd.LINEUP */)
|
|
345
345
|
newPos -= 20;
|
|
346
|
-
else if (code === 1 /*
|
|
346
|
+
else if (code === 1 /* ScrollCmd.LINEDOWN */)
|
|
347
347
|
newPos += 20;
|
|
348
|
-
else if (code === 2 /*
|
|
348
|
+
else if (code === 2 /* ScrollCmd.PAGEUP */)
|
|
349
349
|
newPos -= info.page;
|
|
350
|
-
else if (code === 3 /*
|
|
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 /*
|
|
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 /*
|
|
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 /*
|
|
365
|
-
const bar = isHorz ? 0 /*
|
|
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 /*
|
|
376
|
+
if (msg === 5 /* WmMsg.SIZE */) {
|
|
377
377
|
updateScrollRange();
|
|
378
378
|
return gui.DefWindowProc(hwnd, msg, wParam, lParam);
|
|
379
379
|
}
|