quickwin 1.2.1 → 2.0.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 (137) hide show
  1. package/README.en.md +3 -3
  2. package/README.md +21 -24
  3. package/examples/PdfCanvas.js +8 -37
  4. package/examples/PdfCanvas.tsx +11 -41
  5. package/examples/exec_server.js +20054 -1423
  6. package/examples/exec_server.tsx +489 -0
  7. package/examples/exec_server_worker.js +383 -33
  8. package/examples/exec_server_worker.ts +408 -30
  9. package/examples/pdf_preview2.js +31 -73
  10. package/examples/pdf_preview2.ts +33 -76
  11. package/examples/pdf_viewer.js +1496 -616
  12. package/examples/pdf_viewer.tsx +23 -64
  13. package/examples/setres.js +22 -47
  14. package/examples/setres.ts +23 -57
  15. package/examples/test_gallery.js +1491 -525
  16. package/examples/test_react_lazy.js +1455 -500
  17. package/examples/test_react_listbox.js +1454 -499
  18. package/examples/test_root_window.js +1450 -495
  19. package/examples/test_tab.js +1455 -500
  20. package/lib/abort.js +70 -0
  21. package/lib/abort.ts +69 -0
  22. package/lib/certs.js +214 -0
  23. package/lib/certs.ts +224 -0
  24. package/lib/exec-log.js +15 -0
  25. package/lib/exec-log.ts +30 -0
  26. package/lib/fetch.js +37 -4
  27. package/lib/fetch.ts +35 -4
  28. package/lib/ffi/bind.js +352 -0
  29. package/lib/ffi/bind.ts +442 -0
  30. package/lib/ffi/ctype.js +168 -0
  31. package/lib/ffi/ctype.ts +331 -0
  32. package/lib/ffi/index.js +6 -0
  33. package/lib/ffi/index.ts +6 -0
  34. package/lib/ffi/struct.js +412 -0
  35. package/lib/ffi/struct.ts +664 -0
  36. package/lib/http-common.js +43 -5
  37. package/lib/http-common.ts +48 -6
  38. package/lib/http-server.js +187 -29
  39. package/lib/http-server.ts +177 -26
  40. package/lib/react-qw/components/DateTimePicker.js +20 -33
  41. package/lib/react-qw/components/Input.js +3 -10
  42. package/lib/react-qw/components/Link.js +7 -22
  43. package/lib/react-qw/components/ListView.js +80 -141
  44. package/lib/react-qw/components/PathPicker.js +36 -80
  45. package/lib/react-qw/components/ScrollView.js +15 -8
  46. package/lib/react-qw/components/Slider.js +2 -1
  47. package/lib/react-qw/components/Tab.js +15 -20
  48. package/lib/react-qw/components/Tooltip.js +21 -20
  49. package/lib/react-qw/components/TreeView.js +14 -47
  50. package/lib/react-qw/index.js +4 -2
  51. package/lib/react-qw/index.ts +4 -2
  52. package/lib/react-qw/nmhdr.js +5 -0
  53. package/lib/react-qw/nmhdr.ts +7 -0
  54. package/lib/react-qw/props.js +7 -2
  55. package/lib/react-qw/props.ts +7 -3
  56. package/lib/react-qw/reconciler.js +23 -13
  57. package/lib/react-qw/reconciler.ts +22 -13
  58. package/lib/text-measure.js +18 -35
  59. package/lib/text-measure.ts +20 -37
  60. package/lib/websocket.js +12 -2
  61. package/lib/websocket.ts +15 -2
  62. package/lib/windows/comctl32.js +18 -0
  63. package/lib/windows/comctl32.ts +20 -0
  64. package/lib/windows/comdlg32.js +7 -0
  65. package/lib/windows/comdlg32.ts +9 -0
  66. package/lib/windows/gdi32.js +90 -0
  67. package/lib/windows/gdi32.ts +97 -0
  68. package/lib/windows/kernel32.js +66 -0
  69. package/lib/windows/kernel32.ts +76 -0
  70. package/lib/windows/ntdll.js +8 -0
  71. package/lib/windows/ntdll.ts +10 -0
  72. package/lib/windows/ole32.js +7 -0
  73. package/lib/windows/ole32.ts +9 -0
  74. package/lib/windows/shell32.js +11 -0
  75. package/lib/windows/shell32.ts +14 -0
  76. package/lib/windows/structs.js +384 -0
  77. package/lib/windows/structs.ts +422 -0
  78. package/lib/windows/user32.js +469 -0
  79. package/lib/windows/user32.ts +502 -0
  80. package/package.json +1 -1
  81. package/quickwin.d.ts +22 -33
  82. package/quickwin_const.d.ts +1 -0
  83. package/qwin-nowasm-x86.exe +0 -0
  84. package/qwin-nowasm.exe +0 -0
  85. package/qwin-x86.exe +0 -0
  86. package/qwin.exe +0 -0
  87. package/test/bench_ffi_callraw.js +236 -0
  88. package/test/bench_ffi_callraw.ts +236 -0
  89. package/test/bench_ffi_overhead.js +139 -0
  90. package/test/bench_ffi_overhead.ts +141 -0
  91. package/test/bench_sendmessage.js +104 -0
  92. package/test/bench_sendmessage.ts +100 -0
  93. package/test/bench_sendmessage_nonnull.js +187 -0
  94. package/test/bench_sendmessage_nonnull.ts +188 -0
  95. package/test/run.js +14 -4
  96. package/test/run.ts +15 -5
  97. package/test/test_basic_fetch.js +8 -0
  98. package/test/test_basic_fetch.ts +9 -0
  99. package/test/test_ffi.js +33 -47
  100. package/test/test_ffi.ts +41 -60
  101. package/test/test_ffi_abi.js +235 -0
  102. package/test/test_ffi_abi.ts +218 -0
  103. package/test/test_ffi_bind.js +572 -0
  104. package/test/test_ffi_bind.ts +597 -0
  105. package/test/test_ffi_struct.js +745 -0
  106. package/test/test_ffi_struct.ts +798 -0
  107. package/test/test_ffi_struct_layout.js +495 -0
  108. package/test/test_ffi_struct_layout.ts +543 -0
  109. package/test/test_ffi_user32.js +103 -0
  110. package/test/test_ffi_user32.ts +113 -0
  111. package/test/test_ffi_windows.js +101 -0
  112. package/test/test_ffi_windows.ts +113 -0
  113. package/test/test_helper.js +8 -1
  114. package/test/test_helper.ts +9 -1
  115. package/test/test_http_import.js +18 -0
  116. package/test/test_http_import.ts +19 -0
  117. package/test/test_http_server.js +73 -1
  118. package/test/test_http_server.ts +75 -1
  119. package/test/test_ipv6.js +2 -2
  120. package/test/test_ipv6.ts +2 -2
  121. package/test/test_net_fetch.js +70 -8
  122. package/test/test_net_fetch.ts +69 -7
  123. package/test/test_net_websocket.js +1 -1
  124. package/test/test_net_websocket.ts +1 -1
  125. package/test/test_react_complex.js +1465 -510
  126. package/test/test_react_counter.js +1455 -500
  127. package/test/test_react_flex.js +1475 -513
  128. package/test/test_react_hidden.js +1458 -503
  129. package/test/test_react_listview_link.js +1455 -500
  130. package/test/test_react_notify.js +18534 -0
  131. package/test/test_react_render.js +1455 -500
  132. package/test/test_wolfssl_read.js +3 -2
  133. package/test/test_wolfssl_read.ts +3 -1
  134. package/test/test_worker_net.js +35 -17
  135. package/test/test_worker_net.ts +27 -15
  136. package/tsconfig.json +1 -1
  137. package/examples/exec_server.ts +0 -80
@@ -1,8 +1,13 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { NULL } from '../../ffi/ctype.js';
2
3
  import { forwardRef, useRef, useEffect } from 'react';
3
4
  import * as gui from 'gui';
4
5
  import * as ffi from 'ffi';
5
- import * as win from 'win';
6
+ import { WCHAR } from '../../ffi/bind.js';
7
+ import { nmCode } from '../nmhdr.js';
8
+ import { LoadCursor, SetCursor, ScreenToClient, GetCursorPos } from '../../windows/user32.js';
9
+ import { CreateFontIndirect, DeleteObject, GetObject, SelectObject } from '../../windows/gdi32.js';
10
+ import { LOGFONTW, POINT, NMCUSTOMDRAW, NMLVCUSTOMDRAW, NMLISTVIEW, LVHITTESTINFO, LVITEMW, LVCOLUMNW, } from '../../windows/structs.js';
6
11
  export function makeColorBlock(size, bgra) {
7
12
  const n = size * size;
8
13
  const buf = new ArrayBuffer(n * 4);
@@ -15,76 +20,19 @@ export function makeColorBlock(size, bgra) {
15
20
  }
16
21
  return buf;
17
22
  }
18
- function textToUtf16(s) {
19
- const buf = new ArrayBuffer((s.length + 1) * 2);
20
- const dv = new DataView(buf);
21
- for (let i = 0; i < s.length; i++)
22
- dv.setUint16(i * 2, s.charCodeAt(i), true);
23
- return buf;
24
- }
25
- function readI32(ptr, offset) {
26
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
27
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
28
- }
29
- function readU32(ptr, offset) {
30
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
31
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24) >>> 0;
32
- }
33
- function readU64(ptr, offset) {
34
- const lo = readU32(ptr, offset);
35
- const hi = readU32(ptr, offset + 4);
36
- return lo + hi * 0x100000000;
37
- }
23
+ // 自绘时要把颜色写回通知结构指向的原生内存;struct 的 decode 能读原生指针、encode 只写 ArrayBuffer,
24
+ // 所以这两处写入保留逐字节写(偏移由 struct 的 offsetOf 给出)。
38
25
  function writeU32(ptr, offset, v) {
39
26
  ffi.writeByte(ptr + offset, v & 0xFF);
40
27
  ffi.writeByte(ptr + offset + 1, (v >> 8) & 0xFF);
41
28
  ffi.writeByte(ptr + offset + 2, (v >> 16) & 0xFF);
42
29
  ffi.writeByte(ptr + offset + 3, (v >> 24) & 0xFF);
43
30
  }
44
- function bufPtr(buf) {
45
- return ffi.bufferPtr(buf);
46
- }
47
31
  const LV_WS = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */ | 2097152 /* gui.WindowStyle.VSCROLL */ | 1048576 /* gui.WindowStyle.HSCROLL */
48
32
  | 1 /* gui.ListViewStyle.REPORT */ | 4 /* gui.ListViewStyle.SINGLESEL */;
49
- // x64 结构体偏移(NMHDR 为 24 字节)
50
- const CD_STAGE = 24; // NMCUSTOMDRAW.dwDrawStage
51
- const CD_HDC = 32; // NMCUSTOMDRAW.hdc
52
- const CD_ITEM = 56; // NMCUSTOMDRAW.dwItemSpec(行索引)
53
- const CD_CLRTEXT = 80; // NMLVCUSTOMDRAW.clrText
54
- const CD_CLRTEXTBK = 84; // NMLVCUSTOMDRAW.clrTextBk
55
- const CD_SUBITEM = 88; // NMLVCUSTOMDRAW.iSubItem
56
- const NMIA_ITEM = 24; // NMITEMACTIVATE.iItem
57
- const NMIA_SUBITEM = 28; // NMITEMACTIVATE.iSubItem
58
- const NM_CODE = 16; // NMHDR.code
33
+ const CD_CLRTEXT = NMLVCUSTOMDRAW.offsetOf('clrText');
34
+ const CD_CLRTEXTBK = NMLVCUSTOMDRAW.offsetOf('clrTextBk');
59
35
  const fontCache = new Map();
60
- let gdi32 = null;
61
- let createFontIndirectW = null;
62
- let selectObjectFn = null;
63
- let getObjectW = null;
64
- function ensureGdi() {
65
- if (gdi32 !== null)
66
- return;
67
- gdi32 = win.LoadLibrary('gdi32.dll');
68
- if (!gdi32)
69
- return;
70
- createFontIndirectW = win.GetProcAddress(gdi32, 'CreateFontIndirectW');
71
- selectObjectFn = win.GetProcAddress(gdi32, 'SelectObject');
72
- getObjectW = win.GetProcAddress(gdi32, 'GetObjectW');
73
- }
74
- let user32 = null;
75
- let loadCursorW = null;
76
- let setCursorFn = null;
77
- let screenToClient = null;
78
- function ensureUser32() {
79
- if (user32 !== null)
80
- return;
81
- user32 = win.LoadLibrary('user32.dll');
82
- if (!user32)
83
- return;
84
- loadCursorW = win.GetProcAddress(user32, 'LoadCursorW');
85
- setCursorFn = win.GetProcAddress(user32, 'SetCursor');
86
- screenToClient = win.GetProcAddress(user32, 'ScreenToClient');
87
- }
88
36
  function getCellFont(hwnd, style) {
89
37
  const key = (style.bold ? 'b' : '') + (style.italic ? 'i' : '') + (style.underline ? 'u' : '');
90
38
  if (key === '')
@@ -92,40 +40,36 @@ function getCellFont(hwnd, style) {
92
40
  const cached = fontCache.get(key);
93
41
  if (cached !== undefined)
94
42
  return cached === 0 ? null : cached;
95
- ensureGdi();
96
- if (!createFontIndirectW || !getObjectW || !hwnd)
43
+ if (!hwnd)
97
44
  return null;
98
- const lf = new ArrayBuffer(92);
99
- const dv = new DataView(lf);
45
+ // 以控件当前字体为底(取不到则用 -13 高度的默认字体),再叠加粗 / 斜 / 下划线
46
+ let base = { lfHeight: -13 };
100
47
  const cur = gui.SendMessage(hwnd, 49 /* gui.WmMsg.GETFONT */, 0, 0);
101
48
  if (cur) {
102
- const got = ffi.ffiCall(getObjectW, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_SINT32, ffi.FFI_TYPE_POINTER], [cur, 92, lf], ffi.FFI_TYPE_SINT32);
103
- if (!got)
49
+ const lf = LOGFONTW.alloc();
50
+ if (!GetObject(cur, LOGFONTW.size, lf))
104
51
  return null;
52
+ base = LOGFONTW.decode(lf);
105
53
  }
106
- else {
107
- dv.setInt32(0, -13, true);
108
- }
109
- if (style.bold)
110
- dv.setInt32(16, 700 /* gui.FontWeight.BOLD */, true);
111
- if (style.italic)
112
- dv.setUint8(20, 1);
113
- if (style.underline)
114
- dv.setUint8(21, 1);
115
- const h = ffi.ffiCall(createFontIndirectW, [ffi.FFI_TYPE_POINTER], [lf], ffi.FFI_TYPE_UINT64);
116
- fontCache.set(key, h === 0 ? 0 : h);
117
- return h === 0 ? null : h;
54
+ const h = CreateFontIndirect({
55
+ ...base,
56
+ ...(style.bold ? { lfWeight: 700 /* gui.FontWeight.BOLD */ } : {}),
57
+ ...(style.italic ? { lfItalic: 1 } : {}),
58
+ ...(style.underline ? { lfUnderline: 1 } : {}),
59
+ });
60
+ fontCache.set(key, h ? h : 0);
61
+ return h ? h : null;
118
62
  }
119
63
  function handleCustomDraw(lParam, columns, data, hwnd) {
120
- const stage = readU32(lParam, CD_STAGE);
64
+ const { dwDrawStage: stage } = NMCUSTOMDRAW.decode(lParam);
121
65
  if (stage === 1 /* gui.CustomDrawStage.PREPAINT */)
122
66
  return 32 /* gui.CustomDrawFlag.NOTIFYITEMDRAW */;
123
67
  if (stage === 65537 /* gui.CustomDrawStage.ITEMPREPAINT */)
124
68
  return 32 /* gui.CustomDrawFlag.NOTIFYSUBITEMDRAW */;
125
69
  if (stage === 196609 /* gui.CustomDrawStage.SUBITEMPREPAINT */) {
126
- const colIndex = readI32(lParam, CD_SUBITEM);
127
- const row = readI32(lParam, CD_ITEM);
128
- const style = resolveCellStyle(columns, data, row, colIndex);
70
+ // 只有子项阶段才按 NMLVCUSTOMDRAW(更大的结构)读,前面的阶段只读了 NMCUSTOMDRAW 的前缀
71
+ const cd = NMLVCUSTOMDRAW.decode(lParam);
72
+ const style = resolveCellStyle(columns, data, cd.dwItemSpec, cd.iSubItem);
129
73
  if (!style)
130
74
  return 0 /* gui.CustomDrawFlag.DODEFAULT */;
131
75
  if (style.color !== undefined)
@@ -133,12 +77,9 @@ function handleCustomDraw(lParam, columns, data, hwnd) {
133
77
  if (style.background !== undefined)
134
78
  writeU32(lParam, CD_CLRTEXTBK, style.background);
135
79
  const hfont = getCellFont(hwnd, style);
136
- if (hfont && selectObjectFn) {
137
- const hdc = readU64(lParam, CD_HDC);
138
- if (hdc) {
139
- ffi.ffiCall(selectObjectFn, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [hdc, hfont], ffi.FFI_TYPE_UINT64);
140
- return 2 /* gui.CustomDrawFlag.NEWFONT */;
141
- }
80
+ if (hfont && cd.hdc) {
81
+ SelectObject(cd.hdc, hfont);
82
+ return 2 /* gui.CustomDrawFlag.NEWFONT */;
142
83
  }
143
84
  }
144
85
  return 0 /* gui.CustomDrawFlag.DODEFAULT */;
@@ -169,18 +110,24 @@ function resolveCellStyle(columns, data, row, colIndex) {
169
110
  return style || undefined;
170
111
  }
171
112
  function makeLVItem(i, sub, text, image) {
172
- const b = new ArrayBuffer(84);
173
- const dv = new DataView(b);
174
- dv.setInt32(4, i, true);
175
- dv.setInt32(8, sub, true);
176
- let mask = 1 /* LvItemFlag.TEXT */;
177
- if (image !== undefined) {
178
- mask |= 2 /* LvItemFlag.IMAGE */;
179
- dv.setInt32(36, image, true);
180
- }
181
- dv.setUint32(0, mask, true);
182
- const textBuf = textToUtf16(text);
183
- dv.setBigUint64(24, BigInt(bufPtr(textBuf)), true);
113
+ const textBuf = WCHAR.encode(text);
114
+ const b = LVITEMW.encode({
115
+ mask: 1 /* LvItemFlag.TEXT */ | (image !== undefined ? 2 /* LvItemFlag.IMAGE */ : 0),
116
+ iItem: i,
117
+ iSubItem: sub,
118
+ state: 0,
119
+ stateMask: 0,
120
+ pszText: textBuf.ptr,
121
+ cchTextMax: 0,
122
+ iImage: image ?? 0,
123
+ lParam: 0,
124
+ iIndent: 0,
125
+ iGroupId: 0,
126
+ cColumns: 0,
127
+ puColumns: 0,
128
+ piColFmt: 0,
129
+ iGroup: 0,
130
+ });
184
131
  b.__textBuf = textBuf;
185
132
  return b;
186
133
  }
@@ -207,7 +154,7 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
207
154
  const hbm = gui.CreateBitmapFromPixels(size, size, p);
208
155
  if (hbm) {
209
156
  gui.ImageListAdd(img, hbm);
210
- gui.DeleteObject(hbm);
157
+ DeleteObject(hbm);
211
158
  }
212
159
  }
213
160
  gui.SendMessage(h, 4099 /* gui.LvMsg.SETIMAGELIST */, 1 /* gui.LvImageList.SMALL */, img);
@@ -227,16 +174,22 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
227
174
  gui.SendMessage(h, 4150 /* gui.LvMsg.SETEXTENDEDLISTVIEWSTYLE */, 0, 32 /* gui.LvExStyle.FULLROWSELECT */ | 1 /* gui.LvExStyle.GRIDLINES */ | 65536 /* gui.LvExStyle.DOUBLEBUFFER */);
228
175
  const n = columns.length;
229
176
  for (let j = 0; j < n; j++) {
230
- const titleBuf = textToUtf16(columns[j].name);
231
- const lvc = new ArrayBuffer(52);
232
- const dv = new DataView(lvc);
233
- dv.setUint32(0, 4 /* LvColumnMask.TEXT */ | 2 /* LvColumnMask.WIDTH */ | 1 /* LvColumnMask.FORMAT */, true);
234
- dv.setInt32(4, alignToFmt(columns[j].align), true);
235
- dv.setInt32(8, columns[j].width ?? 100, true);
236
- dv.setBigUint64(16, BigInt(bufPtr(titleBuf)), true);
237
- dv.setInt32(28, j, true);
177
+ const titleBuf = WCHAR.encode(columns[j].name);
178
+ const lvc = LVCOLUMNW.encode({
179
+ mask: 4 /* LvColumnMask.TEXT */ | 2 /* LvColumnMask.WIDTH */ | 1 /* LvColumnMask.FORMAT */,
180
+ fmt: alignToFmt(columns[j].align),
181
+ cx: columns[j].width ?? 100,
182
+ pszText: titleBuf.ptr,
183
+ cchTextMax: 0,
184
+ iSubItem: j,
185
+ iImage: 0,
186
+ iOrder: 0,
187
+ cxMin: 0,
188
+ cxDefault: 0,
189
+ cxIdeal: 0,
190
+ });
238
191
  lvc.__titleBuf = titleBuf;
239
- gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j, bufPtr(lvc));
192
+ gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j, lvc.ptr);
240
193
  }
241
194
  }, [columns]);
242
195
  useEffect(() => {
@@ -249,10 +202,10 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
249
202
  const record = data[i];
250
203
  const img = getIcon ? getIcon(record, i) : undefined;
251
204
  const itemBuf = makeLVItem(i, 0, cellText(record, columns[0], i), img);
252
- gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0, bufPtr(itemBuf));
205
+ gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0, itemBuf.ptr);
253
206
  for (let j = 1; j < nCols; j++) {
254
207
  const subBuf = makeLVItem(i, j, cellText(record, columns[j], i));
255
- gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0, bufPtr(subBuf));
208
+ gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0, subBuf.ptr);
256
209
  }
257
210
  }
258
211
  for (let j = 0; j < columns.length; j++) {
@@ -263,19 +216,17 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
263
216
  }, [data, columns, getIcon]);
264
217
  return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
265
218
  if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
266
- const code = readI32(e.lParam, NM_CODE);
219
+ const code = nmCode(e.lParam);
267
220
  if (code === -12 /* gui.LvNotifyCode.CUSTOMDRAW */) {
268
221
  return handleCustomDraw(e.lParam, columns, data, lvRef.current);
269
222
  }
270
223
  if (code === -100 /* gui.LvNotifyCode.ITEMCHANGING */) {
271
- const uNewState = readU32(e.lParam, 32);
272
- const uOldState = readU32(e.lParam, 36);
224
+ const { uNewState, uOldState } = NMLISTVIEW.decode(e.lParam);
273
225
  if ((uNewState & 2 /* LvItemState.SELECTED */) !== (uOldState & 2 /* LvItemState.SELECTED */))
274
226
  return 1;
275
227
  }
276
228
  if (code === -2 /* gui.LvNotifyCode.CLICK */) {
277
- const iItem = readI32(e.lParam, NMIA_ITEM);
278
- const iSubItem = readI32(e.lParam, NMIA_SUBITEM);
229
+ const { iItem, iSubItem } = NMLISTVIEW.decode(e.lParam);
279
230
  const col = columns[iSubItem];
280
231
  const record = data[iItem];
281
232
  if (col?.onCellClick && record !== undefined)
@@ -293,33 +244,21 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
293
244
  const h = lvRef.current;
294
245
  if (!h)
295
246
  return;
296
- ensureUser32();
297
- if (!loadCursorW || !setCursorFn || !screenToClient)
298
- return;
299
- const sp = gui.GetCursorPos();
300
- if (!sp)
247
+ // GetCursorPos 出参写入初值 → ScreenToClient 就地更新 → decode 读回
248
+ const pt = POINT.alloc();
249
+ if (!GetCursorPos(pt.ptr))
301
250
  return;
302
- const sbuf = new ArrayBuffer(8);
303
- const sdv = new DataView(sbuf);
304
- sdv.setInt32(0, sp[0], true);
305
- sdv.setInt32(4, sp[1], true);
306
- ffi.ffiCall(screenToClient, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_POINTER], [h, sbuf], ffi.FFI_TYPE_SINT32);
307
- const lvhi = new ArrayBuffer(24);
308
- const lvd = new DataView(lvhi);
309
- lvd.setInt32(0, sdv.getInt32(0, true), true);
310
- lvd.setInt32(4, sdv.getInt32(4, true), true);
311
- lvd.setInt32(12, -1, true);
312
- lvd.setInt32(16, -1, true);
313
- const lvhiPtr = bufPtr(lvhi);
314
- gui.SendMessage(h, 4153 /* gui.LvMsg.SUBITEMHITTEST */, 0, lvhiPtr);
315
- const iItem = readI32(lvhiPtr, 12);
316
- const iSubItem = readI32(lvhiPtr, 16);
251
+ ScreenToClient(h, pt.ptr);
252
+ const { x: sx, y: sy } = POINT.decode(pt);
253
+ const hit = LVHITTESTINFO.encode({ pt: [sx, sy], iItem: -1, iSubItem: -1 });
254
+ gui.SendMessage(h, 4153 /* gui.LvMsg.SUBITEMHITTEST */, 0, hit.ptr);
255
+ const { iItem, iSubItem } = LVHITTESTINFO.decode(hit);
317
256
  const style = resolveCellStyle(columns, data, iItem, iSubItem);
318
257
  if (!style || style.cursor === undefined)
319
258
  return;
320
- const hc = ffi.ffiCall(loadCursorW, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [0, style.cursor], ffi.FFI_TYPE_UINT64);
259
+ const hc = LoadCursor(NULL, style.cursor);
321
260
  if (hc) {
322
- ffi.ffiCall(setCursorFn, [ffi.FFI_TYPE_UINT64], [hc], ffi.FFI_TYPE_UINT64);
261
+ SetCursor(hc);
323
262
  return 1;
324
263
  }
325
264
  return;
@@ -1,98 +1,54 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useState, useRef } from 'react';
3
- import * as ffi from 'ffi';
4
- import * as win from 'win';
5
- const FFI_PTR = ffi.FFI_TYPE_POINTER;
6
- const FFI_U32 = ffi.FFI_TYPE_UINT32;
7
- let _GetOpenFileNameW = 0;
8
- let _SHBrowseForFolderW = 0;
9
- let _SHGetPathFromIDListW = 0;
10
- let _CoTaskMemFree = 0;
11
- function ensureDlls() {
12
- if (_GetOpenFileNameW)
13
- return true;
14
- const comdlg32 = win.LoadLibrary('comdlg32.dll');
15
- const shell32 = win.LoadLibrary('shell32.dll');
16
- const ole32 = win.LoadLibrary('ole32.dll');
17
- if (!comdlg32 || !shell32 || !ole32)
18
- return false;
19
- const load = (lib, name) => win.GetProcAddress(lib, name) || 0;
20
- _GetOpenFileNameW = load(comdlg32, 'GetOpenFileNameW');
21
- _SHBrowseForFolderW = load(shell32, 'SHBrowseForFolderW');
22
- _SHGetPathFromIDListW = load(shell32, 'SHGetPathFromIDListW');
23
- _CoTaskMemFree = load(ole32, 'CoTaskMemFree');
24
- return !!_GetOpenFileNameW && !!_SHBrowseForFolderW && !!_SHGetPathFromIDListW && !!_CoTaskMemFree;
25
- }
26
- function strToWide(s) {
27
- return new TextEncoder('utf-16le').encode(s + '\0').buffer;
28
- }
29
- const _decoder = new TextDecoder('utf-16le');
30
- function wideToStr(buf, offset = 0) {
31
- const str = _decoder.decode(new Uint8Array(buf, offset));
32
- const nullIdx = str.indexOf('\0');
33
- return nullIdx >= 0 ? str.substring(0, nullIdx) : str;
34
- }
35
- function setPtr(dv, off, ptr) {
36
- dv.setUint32(off, ptr & 0xFFFFFFFF, true);
37
- dv.setUint32(off + 4, Math.floor(ptr / 0x100000000), true);
38
- }
3
+ import { WCHAR } from '../../ffi/bind.js';
4
+ import { OPENFILENAMEW, BROWSEINFOW } from '../../windows/structs.js';
5
+ import { GetOpenFileName } from '../../windows/comdlg32.js';
6
+ import { SHBrowseForFolder, SHGetPathFromIDList } from '../../windows/shell32.js';
7
+ import { CoTaskMemFree } from '../../windows/ole32.js';
39
8
  function openFileDialog(owner, filter, title, multiple) {
40
- if (!ensureDlls())
41
- return null;
42
- const structBuf = new ArrayBuffer(152);
43
- const sv = new DataView(structBuf);
44
- const fileBuf = new ArrayBuffer(260 * 2);
45
- const filterWide = strToWide(filter);
46
- const titleWide = title ? strToWide(title) : null;
47
- sv.setUint32(0, 152, true);
48
- setPtr(sv, 8, owner);
49
- setPtr(sv, 24, ffi.bufferPtr(filterWide));
50
- setPtr(sv, 48, ffi.bufferPtr(fileBuf));
51
- sv.setUint32(56, 260, true);
9
+ const fileBuf = WCHAR.alloc(260);
10
+ const filterWide = WCHAR.encode(filter);
11
+ const titleWide = title ? WCHAR.encode(title) : null;
52
12
  let flags = 0x1000 | 0x0800 | 0x0008;
53
13
  if (multiple)
54
14
  flags |= 0x0200;
55
15
  flags |= 0x80000;
56
- sv.setUint32(96, flags, true);
57
- if (titleWide)
58
- setPtr(sv, 88, ffi.bufferPtr(titleWide));
59
- const ret = ffi.ffiCall(_GetOpenFileNameW, [FFI_PTR], [structBuf], FFI_U32);
16
+ // 缺省字段跳过 = fresh buffer 上留 0
17
+ const ofn = OPENFILENAMEW.encode({
18
+ lStructSize: OPENFILENAMEW.size,
19
+ hwndOwner: owner,
20
+ lpstrFilter: filterWide.ptr,
21
+ lpstrFile: fileBuf.ptr,
22
+ nMaxFile: 260,
23
+ lpstrTitle: titleWide?.ptr ?? 0,
24
+ Flags: flags,
25
+ });
26
+ const ret = GetOpenFileName(ofn);
60
27
  if (!ret)
61
28
  return null;
62
29
  if (!multiple) {
63
- return wideToStr(fileBuf);
64
- }
65
- const dir = wideToStr(fileBuf);
66
- let pos = (dir.length + 1) * 2;
67
- const files = [];
68
- while (pos < fileBuf.byteLength) {
69
- const f = wideToStr(fileBuf, pos);
70
- if (f.length === 0)
71
- break;
72
- files.push(dir + '\\' + f);
73
- pos += (f.length + 1) * 2;
30
+ return WCHAR.decode(fileBuf);
74
31
  }
75
- if (files.length === 0)
76
- return [dir];
77
- return files;
32
+ // 多选缓冲:dir\0 file1\0 file2\0 \0 —— 整缓冲一次解码按 NUL 拆分
33
+ const strs = new TextDecoder('utf-16le').decode(new Uint8Array(fileBuf)).split('\0');
34
+ const dir = strs[0] ?? '';
35
+ const files = strs.slice(1).filter((f) => f.length > 0).map((f) => dir + '\\' + f);
36
+ return files.length > 0 ? files : [dir];
78
37
  }
79
38
  function openFolderDialog(owner, title) {
80
- if (!ensureDlls())
81
- return null;
82
- const structBuf = new ArrayBuffer(64);
83
- const sv = new DataView(structBuf);
84
- const titleWide = title ? strToWide(title) : null;
85
- setPtr(sv, 0, owner);
86
- if (titleWide)
87
- setPtr(sv, 24, ffi.bufferPtr(titleWide));
88
- sv.setUint32(32, 0x00000041, true);
89
- const pidl = ffi.ffiCall(_SHBrowseForFolderW, [FFI_PTR], [structBuf], FFI_PTR);
39
+ const titleWide = title ? WCHAR.encode(title) : null;
40
+ const bi = BROWSEINFOW.encode({
41
+ hwndOwner: owner,
42
+ lpszTitle: titleWide?.ptr ?? 0,
43
+ ulFlags: 0x00000041,
44
+ });
45
+ const pidl = SHBrowseForFolder(bi);
90
46
  if (!pidl)
91
47
  return null;
92
- const pathBuf = new ArrayBuffer(260 * 2);
93
- const ok = ffi.ffiCall(_SHGetPathFromIDListW, [ffi.FFI_TYPE_UINT64, FFI_PTR], [pidl, pathBuf], FFI_U32);
94
- ffi.ffiCall(_CoTaskMemFree, [ffi.FFI_TYPE_UINT64], [pidl], ffi.FFI_TYPE_VOID);
95
- return ok ? wideToStr(pathBuf) : null;
48
+ const pathBuf = WCHAR.alloc(260);
49
+ const ok = SHGetPathFromIDList(pidl, pathBuf);
50
+ CoTaskMemFree(pidl);
51
+ return ok ? WCHAR.decode(pathBuf) : null;
96
52
  }
97
53
  export const PathPicker = forwardRef((props, ref) => {
98
54
  const { type = 'file', filter = 'All Files\0*.*\0\0', title, disabled, style } = props;
@@ -1,6 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef, useLayoutEffect, useRef } from 'react';
3
3
  import * as gui from 'gui';
4
+ import { DefWindowProc, SetWindowPos, ShowScrollBar, GetDC, ReleaseDC } from '../../windows/user32.js';
5
+ import { NULL } from '../../ffi/ctype.js';
6
+ import { GetDeviceCaps } from '../../windows/gdi32.js';
4
7
  import { forceFlexLayout } from '../reconciler.js';
5
8
  export const ScrollView = forwardRef(({ children, style, contentWidth, contentHeight }, ref) => {
6
9
  const svRef = useRef(null);
@@ -18,28 +21,32 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
18
21
  return;
19
22
  const svW = svRect.right - svRect.left;
20
23
  const svH = svRect.bottom - svRect.top;
21
- const scale = gui.GetScaleFactor();
24
+ // GetScaleFactor 等价内联:GetDC → GetDeviceCaps(LOGPIXELSX=88) → ReleaseDC 配对
25
+ const hdc = GetDC(NULL);
26
+ const scale = hdc ? GetDeviceCaps(hdc, 88) / 96 : 1;
27
+ if (hdc)
28
+ ReleaseDC(NULL, hdc);
22
29
  const natW = Math.round((contentWidth ?? (svW / scale)) * scale);
23
30
  const natH = Math.round((contentHeight ?? (svH / scale)) * scale);
24
31
  scrollXRef.current = Math.max(0, Math.min(scrollXRef.current, natW - svW));
25
32
  scrollYRef.current = Math.max(0, Math.min(scrollYRef.current, natH - svH));
26
- gui.SetWindowPos(content, 0 /* gui.SetWindowPosHwnd.TOP */, -scrollXRef.current, -scrollYRef.current, natW, natH, 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
33
+ SetWindowPos(content, 0 /* gui.SetWindowPosHwnd.TOP */, -scrollXRef.current, -scrollYRef.current, natW, natH, 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
27
34
  forceFlexLayout(content);
28
35
  if (natW > svW) {
29
36
  gui.SetScrollInfo(sv, 0 /* gui.ScrollBar.HORZ */, { min: 0, max: natW - 1, page: svW, pos: scrollXRef.current }, true);
30
- gui.ShowScrollBar(sv, 0 /* gui.ScrollBar.HORZ */, true);
37
+ ShowScrollBar(sv, 0 /* gui.ScrollBar.HORZ */, 1);
31
38
  }
32
39
  else {
33
40
  scrollXRef.current = 0;
34
- gui.ShowScrollBar(sv, 0 /* gui.ScrollBar.HORZ */, false);
41
+ ShowScrollBar(sv, 0 /* gui.ScrollBar.HORZ */, 0);
35
42
  }
36
43
  if (natH > svH) {
37
44
  gui.SetScrollInfo(sv, 1 /* gui.ScrollBar.VERT */, { min: 0, max: natH - 1, page: svH, pos: scrollYRef.current }, true);
38
- gui.ShowScrollBar(sv, 1 /* gui.ScrollBar.VERT */, true);
45
+ ShowScrollBar(sv, 1 /* gui.ScrollBar.VERT */, 1);
39
46
  }
40
47
  else {
41
48
  scrollYRef.current = 0;
42
- gui.ShowScrollBar(sv, 1 /* gui.ScrollBar.VERT */, false);
49
+ ShowScrollBar(sv, 1 /* gui.ScrollBar.VERT */, 0);
43
50
  }
44
51
  }
45
52
  useLayoutEffect(() => {
@@ -56,7 +63,7 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
56
63
  else {
57
64
  scrollXRef.current = newPos;
58
65
  }
59
- gui.SetWindowPos(content, 0 /* gui.SetWindowPosHwnd.TOP */, -scrollXRef.current, -scrollYRef.current, 0, 0, 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
66
+ SetWindowPos(content, 0 /* gui.SetWindowPosHwnd.TOP */, -scrollXRef.current, -scrollYRef.current, 0, 0, 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
60
67
  gui.SetScrollInfo(sv, bar, { pos: newPos }, true);
61
68
  }
62
69
  function handleScroll(e, bar) {
@@ -119,7 +126,7 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
119
126
  fn: (e) => {
120
127
  if (e.msg === 132 /* gui.WmMsg.NCHITTEST */ ||
121
128
  e.msg === 161 /* gui.WmMsg.NCLBUTTONDOWN */) {
122
- return gui.DefWindowProc(e.hwnd, e.msg, e.wParam, e.lParam);
129
+ return DefWindowProc(e.hwnd, e.msg, e.wParam, e.lParam);
123
130
  }
124
131
  if (e.msg === 277 /* gui.WmMsg.VSCROLL */) {
125
132
  handleScroll(e, 1 /* gui.ScrollBar.VERT */);
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef, useRef, useEffect } from 'react';
3
3
  import * as gui from 'gui';
4
+ import { DefWindowProc } from '../../windows/user32.js';
4
5
  export const Slider = forwardRef(({ value, onChange, min = 0, max = 100, vertical = false, disabled, style }, ref) => {
5
6
  const wrapperRef = useRef(null);
6
7
  const sliderRef = useRef(null);
@@ -30,7 +31,7 @@ export const Slider = forwardRef(({ value, onChange, min = 0, max = 100, vertica
30
31
  }, onEvent: (e) => {
31
32
  if (e.msg === 132 /* gui.WmMsg.NCHITTEST */ ||
32
33
  e.msg === 161 /* gui.WmMsg.NCLBUTTONDOWN */) {
33
- return gui.DefWindowProc(e.hwnd, e.msg, e.wParam, e.lParam);
34
+ return DefWindowProc(e.hwnd, e.msg, e.wParam, e.lParam);
34
35
  }
35
36
  if (e.msg === 276 /* gui.WmMsg.HSCROLL */ || e.msg === 277 /* gui.WmMsg.VSCROLL */) {
36
37
  const h = sliderRef.current;
@@ -1,18 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useRef, useEffect, useState } from 'react';
3
3
  import * as gui from 'gui';
4
- import * as ffi from 'ffi';
5
- function textToUtf16(s) {
6
- const buf = new ArrayBuffer((s.length + 1) * 2);
7
- const dv = new DataView(buf);
8
- for (let i = 0; i < s.length; i++)
9
- dv.setUint16(i * 2, s.charCodeAt(i), true);
10
- return buf;
11
- }
12
- function readI32(ptr, offset) {
13
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
14
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
15
- }
4
+ import { WCHAR } from '../../ffi/bind.js';
5
+ import { TCITEMW } from '../../windows/structs.js';
6
+ import { nmCode } from '../nmhdr.js';
16
7
  export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSelectedIndex = 0, onChange, style }, ref) => {
17
8
  const [internalIndex, setInternalIndex] = useState(defaultSelectedIndex);
18
9
  const isControlled = controlledIndex !== undefined;
@@ -24,13 +15,17 @@ export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSe
24
15
  return;
25
16
  gui.SendMessage(h, 4873 /* gui.TcMsg.DELETEALLITEMS */, 0, 0);
26
17
  for (let i = 0; i < tabs.length; i++) {
27
- const titleBuf = textToUtf16(tabs[i].title);
28
- const tci = new ArrayBuffer(40);
29
- const dv = new DataView(tci);
30
- dv.setUint32(0, 1 /* gui.TcItemFlag.TEXT */, true);
31
- dv.setBigUint64(16, BigInt(ffi.bufferPtr(titleBuf)), true);
32
- dv.setInt32(24, tabs[i].title.length + 1, true);
33
- const tciPtr = ffi.bufferPtr(tci);
18
+ const titleBuf = WCHAR.encode(tabs[i].title);
19
+ const tci = TCITEMW.encode({
20
+ mask: 1 /* gui.TcItemFlag.TEXT */,
21
+ dwState: 0,
22
+ dwStateMask: 0,
23
+ pszText: titleBuf.ptr,
24
+ cchTextMax: tabs[i].title.length + 1,
25
+ iImage: 0,
26
+ lParam: 0,
27
+ });
28
+ const tciPtr = tci.ptr;
34
29
  gui.SendMessage(h, 4926 /* gui.TcMsg.INSERTITEMW */, i, tciPtr);
35
30
  }
36
31
  gui.SendMessage(h, 4876 /* gui.TcMsg.SETCURSEL */, sel, 0);
@@ -45,7 +40,7 @@ export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSe
45
40
  const h = tabHwnd.current;
46
41
  if (!h)
47
42
  return;
48
- const code = readI32(e.lParam, 16);
43
+ const code = nmCode(e.lParam);
49
44
  // SysTabControl32 在 comctl32 v6 下不发标准的 TCN_SELCHANGE (-550),
50
45
  // 收到 NM_CLICK (-2) 或 TCN_SELCHANGING (-551) 时读实际选中项
51
46
  if (code === -552 /* gui.TcNotifyCode.SELCHANGING */ || code === -2 /* gui.SysLinkNotifyCode.CLICK */) {