quickwin 1.3.0 → 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 (143) hide show
  1. package/README.en.md +1 -2
  2. package/README.md +19 -23
  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 +25 -82
  10. package/examples/pdf_preview2.ts +27 -84
  11. package/examples/pdf_viewer.js +1318 -1178
  12. package/examples/pdf_viewer.tsx +16 -67
  13. package/examples/setres.js +22 -47
  14. package/examples/setres.ts +23 -57
  15. package/examples/test_gallery.js +1313 -1074
  16. package/examples/test_react_lazy.js +1284 -1056
  17. package/examples/test_react_listbox.js +1283 -1055
  18. package/examples/test_root_window.js +1279 -1051
  19. package/examples/test_tab.js +1284 -1056
  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 +18 -37
  41. package/lib/react-qw/components/Input.js +3 -10
  42. package/lib/react-qw/components/Link.js +6 -19
  43. package/lib/react-qw/components/ListView.js +49 -154
  44. package/lib/react-qw/components/PathPicker.js +28 -88
  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 +6 -22
  48. package/lib/react-qw/components/Tooltip.js +10 -21
  49. package/lib/react-qw/components/TreeView.js +8 -31
  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 +3 -24
  53. package/lib/react-qw/nmhdr.ts +5 -30
  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 -23
  59. package/lib/text-measure.ts +20 -25
  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 -37
  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 +12 -4
  96. package/test/run.ts +13 -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 +25 -43
  100. package/test/test_ffi.ts +33 -52
  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 +538 -8
  104. package/test/test_ffi_bind.ts +559 -8
  105. package/test/test_ffi_struct.js +632 -88
  106. package/test/test_ffi_struct.ts +666 -91
  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 +1294 -1066
  126. package/test/test_react_counter.js +1284 -1056
  127. package/test/test_react_flex.js +1304 -1069
  128. package/test/test_react_hidden.js +1287 -1059
  129. package/test/test_react_listview_link.js +1284 -1056
  130. package/test/test_react_notify.js +18534 -0
  131. package/test/test_react_render.js +1284 -1056
  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
  138. package/lib/ffi-bind.js +0 -85
  139. package/lib/ffi-bind.ts +0 -170
  140. package/lib/ffi-struct.js +0 -224
  141. package/lib/ffi-struct.ts +0 -239
  142. package/lib/win-common-structs.js +0 -39
  143. package/lib/win-common-structs.ts +0 -41
@@ -1,10 +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 { struct } from '../../ffi-struct.js';
7
- import { NMHDR, PTR_SIZE, nmCode } from '../nmhdr.js';
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';
8
11
  export function makeColorBlock(size, bgra) {
9
12
  const n = size * size;
10
13
  const buf = new ArrayBuffer(n * 4);
@@ -17,106 +20,19 @@ export function makeColorBlock(size, bgra) {
17
20
  }
18
21
  return buf;
19
22
  }
20
- function textToUtf16(s) {
21
- const buf = new ArrayBuffer((s.length + 1) * 2);
22
- const dv = new DataView(buf);
23
- for (let i = 0; i < s.length; i++)
24
- dv.setUint16(i * 2, s.charCodeAt(i), true);
25
- return buf;
26
- }
27
- function readI32(ptr, offset) {
28
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
29
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
30
- }
31
- function readU32(ptr, offset) {
32
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
33
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24) >>> 0;
34
- }
35
- function readU64(ptr, offset) {
36
- const lo = readU32(ptr, offset);
37
- const hi = readU32(ptr, offset + 4);
38
- return lo + hi * 0x100000000;
39
- }
23
+ // 自绘时要把颜色写回通知结构指向的原生内存;struct 的 decode 能读原生指针、encode 只写 ArrayBuffer,
24
+ // 所以这两处写入保留逐字节写(偏移由 struct 的 offsetOf 给出)。
40
25
  function writeU32(ptr, offset, v) {
41
26
  ffi.writeByte(ptr + offset, v & 0xFF);
42
27
  ffi.writeByte(ptr + offset + 1, (v >> 8) & 0xFF);
43
28
  ffi.writeByte(ptr + offset + 2, (v >> 16) & 0xFF);
44
29
  ffi.writeByte(ptr + offset + 3, (v >> 24) & 0xFF);
45
30
  }
46
- function bufPtr(buf) {
47
- return ffi.bufferPtr(buf);
48
- }
49
31
  const LV_WS = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */ | 2097152 /* gui.WindowStyle.VSCROLL */ | 1048576 /* gui.WindowStyle.HSCROLL */
50
32
  | 1 /* gui.ListViewStyle.REPORT */ | 4 /* gui.ListViewStyle.SINGLESEL */;
51
- // ListView 通知结构(嵌套 NMHDR 以复现 MSVC 尾 padding 传染,offsetOf 保证 ia32/x64 均正确)。
52
- const NMCUSTOMDRAW = struct({
53
- hdr: NMHDR,
54
- dwDrawStage: 'u32', hdc: 'ptr', rc: 'i32[4]',
55
- dwItemSpec: 'ptr', uItemState: 'u32', lItemlParam: 'ptr',
56
- });
57
- const NMLVCUSTOMDRAW = struct({
58
- hdr: NMHDR,
59
- dwDrawStage: 'u32', hdc: 'ptr', rc: 'i32[4]',
60
- dwItemSpec: 'ptr', uItemState: 'u32', lItemlParam: 'ptr',
61
- clrText: 'u32', clrTextBk: 'u32', iSubItem: 'i32', dwItemType: 'u32',
62
- });
63
- // NMLISTVIEW / NMITEMACTIVATE 前缀字段布局一致(iItem..uChanged)
64
- const NMLISTVIEW = struct({
65
- hdr: NMHDR,
66
- iItem: 'i32', iSubItem: 'i32', uNewState: 'u32', uOldState: 'u32', uChanged: 'u32',
67
- ptAction: 'i32[2]', lParam: 'ptr',
68
- });
69
- const CD_STAGE = NMCUSTOMDRAW.offsetOf('dwDrawStage');
70
- const CD_HDC = NMCUSTOMDRAW.offsetOf('hdc');
71
- const CD_ITEM = NMCUSTOMDRAW.offsetOf('dwItemSpec');
72
33
  const CD_CLRTEXT = NMLVCUSTOMDRAW.offsetOf('clrText');
73
34
  const CD_CLRTEXTBK = NMLVCUSTOMDRAW.offsetOf('clrTextBk');
74
- const CD_SUBITEM = NMLVCUSTOMDRAW.offsetOf('iSubItem');
75
- const NMIA_ITEM = NMLISTVIEW.offsetOf('iItem');
76
- const NMIA_SUBITEM = NMLISTVIEW.offsetOf('iSubItem');
77
- const NMLV_UNEW = NMLISTVIEW.offsetOf('uNewState');
78
- const NMLV_UOLD = NMLISTVIEW.offsetOf('uOldState');
79
- const LVITEMW = struct({
80
- mask: 'u32', iItem: 'i32', iSubItem: 'i32',
81
- state: 'u32', stateMask: 'u32',
82
- pszText: 'ptr', cchTextMax: 'i32', iImage: 'i32', lParam: 'ptr',
83
- iIndent: 'i32', iGroupId: 'i32', cColumns: 'u32',
84
- puColumns: 'ptr', piColFmt: 'ptr', iGroup: 'i32',
85
- });
86
- const LVCOLUMNW = struct({
87
- mask: 'u32', fmt: 'i32', cx: 'i32',
88
- pszText: 'ptr', cchTextMax: 'i32', iSubItem: 'i32', iImage: 'i32', iOrder: 'i32',
89
- cxMin: 'i32', cxDefault: 'i32', cxIdeal: 'i32',
90
- });
91
35
  const fontCache = new Map();
92
- let gdi32 = null;
93
- let createFontIndirectW = null;
94
- let selectObjectFn = null;
95
- let getObjectW = null;
96
- function ensureGdi() {
97
- if (gdi32 !== null)
98
- return;
99
- gdi32 = win.LoadLibrary('gdi32.dll');
100
- if (!gdi32)
101
- return;
102
- createFontIndirectW = win.GetProcAddress(gdi32, 'CreateFontIndirectW');
103
- selectObjectFn = win.GetProcAddress(gdi32, 'SelectObject');
104
- getObjectW = win.GetProcAddress(gdi32, 'GetObjectW');
105
- }
106
- let user32 = null;
107
- let loadCursorW = null;
108
- let setCursorFn = null;
109
- let screenToClient = null;
110
- function ensureUser32() {
111
- if (user32 !== null)
112
- return;
113
- user32 = win.LoadLibrary('user32.dll');
114
- if (!user32)
115
- return;
116
- loadCursorW = win.GetProcAddress(user32, 'LoadCursorW');
117
- setCursorFn = win.GetProcAddress(user32, 'SetCursor');
118
- screenToClient = win.GetProcAddress(user32, 'ScreenToClient');
119
- }
120
36
  function getCellFont(hwnd, style) {
121
37
  const key = (style.bold ? 'b' : '') + (style.italic ? 'i' : '') + (style.underline ? 'u' : '');
122
38
  if (key === '')
@@ -124,40 +40,36 @@ function getCellFont(hwnd, style) {
124
40
  const cached = fontCache.get(key);
125
41
  if (cached !== undefined)
126
42
  return cached === 0 ? null : cached;
127
- ensureGdi();
128
- if (!createFontIndirectW || !getObjectW || !hwnd)
43
+ if (!hwnd)
129
44
  return null;
130
- const lf = new ArrayBuffer(92);
131
- const dv = new DataView(lf);
45
+ // 以控件当前字体为底(取不到则用 -13 高度的默认字体),再叠加粗 / 斜 / 下划线
46
+ let base = { lfHeight: -13 };
132
47
  const cur = gui.SendMessage(hwnd, 49 /* gui.WmMsg.GETFONT */, 0, 0);
133
48
  if (cur) {
134
- const got = ffi.ffiCall(getObjectW, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_SINT32, ffi.FFI_TYPE_POINTER], [cur, 92, lf], ffi.FFI_TYPE_SINT32);
135
- if (!got)
49
+ const lf = LOGFONTW.alloc();
50
+ if (!GetObject(cur, LOGFONTW.size, lf))
136
51
  return null;
52
+ base = LOGFONTW.decode(lf);
137
53
  }
138
- else {
139
- dv.setInt32(0, -13, true);
140
- }
141
- if (style.bold)
142
- dv.setInt32(16, 700 /* gui.FontWeight.BOLD */, true);
143
- if (style.italic)
144
- dv.setUint8(20, 1);
145
- if (style.underline)
146
- dv.setUint8(21, 1);
147
- const h = ffi.ffiCall(createFontIndirectW, [ffi.FFI_TYPE_POINTER], [lf], ffi.FFI_TYPE_POINTER);
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
+ });
148
60
  fontCache.set(key, h ? h : 0);
149
61
  return h ? h : null;
150
62
  }
151
63
  function handleCustomDraw(lParam, columns, data, hwnd) {
152
- const stage = readU32(lParam, CD_STAGE);
64
+ const { dwDrawStage: stage } = NMCUSTOMDRAW.decode(lParam);
153
65
  if (stage === 1 /* gui.CustomDrawStage.PREPAINT */)
154
66
  return 32 /* gui.CustomDrawFlag.NOTIFYITEMDRAW */;
155
67
  if (stage === 65537 /* gui.CustomDrawStage.ITEMPREPAINT */)
156
68
  return 32 /* gui.CustomDrawFlag.NOTIFYSUBITEMDRAW */;
157
69
  if (stage === 196609 /* gui.CustomDrawStage.SUBITEMPREPAINT */) {
158
- const colIndex = readI32(lParam, CD_SUBITEM);
159
- const row = readI32(lParam, CD_ITEM);
160
- 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);
161
73
  if (!style)
162
74
  return 0 /* gui.CustomDrawFlag.DODEFAULT */;
163
75
  if (style.color !== undefined)
@@ -165,12 +77,9 @@ function handleCustomDraw(lParam, columns, data, hwnd) {
165
77
  if (style.background !== undefined)
166
78
  writeU32(lParam, CD_CLRTEXTBK, style.background);
167
79
  const hfont = getCellFont(hwnd, style);
168
- if (hfont && selectObjectFn) {
169
- const hdc = PTR_SIZE === 8 ? readU64(lParam, CD_HDC) : readU32(lParam, CD_HDC);
170
- if (hdc) {
171
- ffi.ffiCall(selectObjectFn, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_POINTER], [hdc, hfont], ffi.FFI_TYPE_POINTER);
172
- return 2 /* gui.CustomDrawFlag.NEWFONT */;
173
- }
80
+ if (hfont && cd.hdc) {
81
+ SelectObject(cd.hdc, hfont);
82
+ return 2 /* gui.CustomDrawFlag.NEWFONT */;
174
83
  }
175
84
  }
176
85
  return 0 /* gui.CustomDrawFlag.DODEFAULT */;
@@ -201,14 +110,14 @@ function resolveCellStyle(columns, data, row, colIndex) {
201
110
  return style || undefined;
202
111
  }
203
112
  function makeLVItem(i, sub, text, image) {
204
- const textBuf = textToUtf16(text);
205
- const b = LVITEMW.write({
113
+ const textBuf = WCHAR.encode(text);
114
+ const b = LVITEMW.encode({
206
115
  mask: 1 /* LvItemFlag.TEXT */ | (image !== undefined ? 2 /* LvItemFlag.IMAGE */ : 0),
207
116
  iItem: i,
208
117
  iSubItem: sub,
209
118
  state: 0,
210
119
  stateMask: 0,
211
- pszText: bufPtr(textBuf),
120
+ pszText: textBuf.ptr,
212
121
  cchTextMax: 0,
213
122
  iImage: image ?? 0,
214
123
  lParam: 0,
@@ -245,7 +154,7 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
245
154
  const hbm = gui.CreateBitmapFromPixels(size, size, p);
246
155
  if (hbm) {
247
156
  gui.ImageListAdd(img, hbm);
248
- gui.DeleteObject(hbm);
157
+ DeleteObject(hbm);
249
158
  }
250
159
  }
251
160
  gui.SendMessage(h, 4099 /* gui.LvMsg.SETIMAGELIST */, 1 /* gui.LvImageList.SMALL */, img);
@@ -265,12 +174,12 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
265
174
  gui.SendMessage(h, 4150 /* gui.LvMsg.SETEXTENDEDLISTVIEWSTYLE */, 0, 32 /* gui.LvExStyle.FULLROWSELECT */ | 1 /* gui.LvExStyle.GRIDLINES */ | 65536 /* gui.LvExStyle.DOUBLEBUFFER */);
266
175
  const n = columns.length;
267
176
  for (let j = 0; j < n; j++) {
268
- const titleBuf = textToUtf16(columns[j].name);
269
- const lvc = LVCOLUMNW.write({
177
+ const titleBuf = WCHAR.encode(columns[j].name);
178
+ const lvc = LVCOLUMNW.encode({
270
179
  mask: 4 /* LvColumnMask.TEXT */ | 2 /* LvColumnMask.WIDTH */ | 1 /* LvColumnMask.FORMAT */,
271
180
  fmt: alignToFmt(columns[j].align),
272
181
  cx: columns[j].width ?? 100,
273
- pszText: bufPtr(titleBuf),
182
+ pszText: titleBuf.ptr,
274
183
  cchTextMax: 0,
275
184
  iSubItem: j,
276
185
  iImage: 0,
@@ -280,7 +189,7 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
280
189
  cxIdeal: 0,
281
190
  });
282
191
  lvc.__titleBuf = titleBuf;
283
- gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j, bufPtr(lvc));
192
+ gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j, lvc.ptr);
284
193
  }
285
194
  }, [columns]);
286
195
  useEffect(() => {
@@ -293,10 +202,10 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
293
202
  const record = data[i];
294
203
  const img = getIcon ? getIcon(record, i) : undefined;
295
204
  const itemBuf = makeLVItem(i, 0, cellText(record, columns[0], i), img);
296
- gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0, bufPtr(itemBuf));
205
+ gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0, itemBuf.ptr);
297
206
  for (let j = 1; j < nCols; j++) {
298
207
  const subBuf = makeLVItem(i, j, cellText(record, columns[j], i));
299
- gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0, bufPtr(subBuf));
208
+ gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0, subBuf.ptr);
300
209
  }
301
210
  }
302
211
  for (let j = 0; j < columns.length; j++) {
@@ -312,14 +221,12 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
312
221
  return handleCustomDraw(e.lParam, columns, data, lvRef.current);
313
222
  }
314
223
  if (code === -100 /* gui.LvNotifyCode.ITEMCHANGING */) {
315
- const uNewState = readU32(e.lParam, NMLV_UNEW);
316
- const uOldState = readU32(e.lParam, NMLV_UOLD);
224
+ const { uNewState, uOldState } = NMLISTVIEW.decode(e.lParam);
317
225
  if ((uNewState & 2 /* LvItemState.SELECTED */) !== (uOldState & 2 /* LvItemState.SELECTED */))
318
226
  return 1;
319
227
  }
320
228
  if (code === -2 /* gui.LvNotifyCode.CLICK */) {
321
- const iItem = readI32(e.lParam, NMIA_ITEM);
322
- const iSubItem = readI32(e.lParam, NMIA_SUBITEM);
229
+ const { iItem, iSubItem } = NMLISTVIEW.decode(e.lParam);
323
230
  const col = columns[iSubItem];
324
231
  const record = data[iItem];
325
232
  if (col?.onCellClick && record !== undefined)
@@ -337,33 +244,21 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
337
244
  const h = lvRef.current;
338
245
  if (!h)
339
246
  return;
340
- ensureUser32();
341
- if (!loadCursorW || !setCursorFn || !screenToClient)
342
- return;
343
- const sp = gui.GetCursorPos();
344
- if (!sp)
247
+ // GetCursorPos 出参写入初值 → ScreenToClient 就地更新 → decode 读回
248
+ const pt = POINT.alloc();
249
+ if (!GetCursorPos(pt.ptr))
345
250
  return;
346
- const sbuf = new ArrayBuffer(8);
347
- const sdv = new DataView(sbuf);
348
- sdv.setInt32(0, sp[0], true);
349
- sdv.setInt32(4, sp[1], true);
350
- ffi.ffiCall(screenToClient, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_POINTER], [h, sbuf], ffi.FFI_TYPE_SINT32);
351
- const lvhi = new ArrayBuffer(24);
352
- const lvd = new DataView(lvhi);
353
- lvd.setInt32(0, sdv.getInt32(0, true), true);
354
- lvd.setInt32(4, sdv.getInt32(4, true), true);
355
- lvd.setInt32(12, -1, true);
356
- lvd.setInt32(16, -1, true);
357
- const lvhiPtr = bufPtr(lvhi);
358
- gui.SendMessage(h, 4153 /* gui.LvMsg.SUBITEMHITTEST */, 0, lvhiPtr);
359
- const iItem = readI32(lvhiPtr, 12);
360
- 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);
361
256
  const style = resolveCellStyle(columns, data, iItem, iSubItem);
362
257
  if (!style || style.cursor === undefined)
363
258
  return;
364
- const hc = ffi.ffiCall(loadCursorW, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_POINTER], [0, style.cursor], ffi.FFI_TYPE_POINTER);
259
+ const hc = LoadCursor(NULL, style.cursor);
365
260
  if (hc) {
366
- ffi.ffiCall(setCursorFn, [ffi.FFI_TYPE_POINTER], [hc], ffi.FFI_TYPE_POINTER);
261
+ SetCursor(hc);
367
262
  return 1;
368
263
  }
369
264
  return;
@@ -1,114 +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
- import { OPENFILENAMEW, BROWSEINFOW } from '../../win-common-structs.js';
6
- const FFI_PTR = ffi.FFI_TYPE_POINTER;
7
- const FFI_U32 = ffi.FFI_TYPE_UINT32;
8
- let _GetOpenFileNameW = 0;
9
- let _SHBrowseForFolderW = 0;
10
- let _SHGetPathFromIDListW = 0;
11
- let _CoTaskMemFree = 0;
12
- function ensureDlls() {
13
- if (_GetOpenFileNameW)
14
- return true;
15
- const comdlg32 = win.LoadLibrary('comdlg32.dll');
16
- const shell32 = win.LoadLibrary('shell32.dll');
17
- const ole32 = win.LoadLibrary('ole32.dll');
18
- if (!comdlg32 || !shell32 || !ole32)
19
- return false;
20
- const load = (lib, name) => win.GetProcAddress(lib, name) || 0;
21
- _GetOpenFileNameW = load(comdlg32, 'GetOpenFileNameW');
22
- _SHBrowseForFolderW = load(shell32, 'SHBrowseForFolderW');
23
- _SHGetPathFromIDListW = load(shell32, 'SHGetPathFromIDListW');
24
- _CoTaskMemFree = load(ole32, 'CoTaskMemFree');
25
- return !!_GetOpenFileNameW && !!_SHBrowseForFolderW && !!_SHGetPathFromIDListW && !!_CoTaskMemFree;
26
- }
27
- function strToWide(s) {
28
- return new TextEncoder('utf-16le').encode(s + '\0').buffer;
29
- }
30
- const _decoder = new TextDecoder('utf-16le');
31
- function wideToStr(buf, offset = 0) {
32
- const str = _decoder.decode(new Uint8Array(buf, offset));
33
- const nullIdx = str.indexOf('\0');
34
- return nullIdx >= 0 ? str.substring(0, nullIdx) : str;
35
- }
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';
36
8
  function openFileDialog(owner, filter, title, multiple) {
37
- if (!ensureDlls())
38
- return null;
39
- const fileBuf = new ArrayBuffer(260 * 2);
40
- const filterWide = strToWide(filter);
41
- const titleWide = title ? strToWide(title) : null;
9
+ const fileBuf = WCHAR.alloc(260);
10
+ const filterWide = WCHAR.encode(filter);
11
+ const titleWide = title ? WCHAR.encode(title) : null;
42
12
  let flags = 0x1000 | 0x0800 | 0x0008;
43
13
  if (multiple)
44
14
  flags |= 0x0200;
45
15
  flags |= 0x80000;
46
- const ofn = OPENFILENAMEW.write({
16
+ // 缺省字段跳过 = fresh buffer 上留 0
17
+ const ofn = OPENFILENAMEW.encode({
47
18
  lStructSize: OPENFILENAMEW.size,
48
19
  hwndOwner: owner,
49
- hInstance: 0,
50
- lpstrFilter: ffi.bufferPtr(filterWide),
51
- lpstrCustomFilter: 0,
52
- nMaxCustFilter: 0,
53
- nFilterIndex: 0,
54
- lpstrFile: ffi.bufferPtr(fileBuf),
20
+ lpstrFilter: filterWide.ptr,
21
+ lpstrFile: fileBuf.ptr,
55
22
  nMaxFile: 260,
56
- lpstrFileTitle: 0,
57
- nMaxFileTitle: 0,
58
- lpstrInitialDir: 0,
59
- lpstrTitle: titleWide ? ffi.bufferPtr(titleWide) : 0,
23
+ lpstrTitle: titleWide?.ptr ?? 0,
60
24
  Flags: flags,
61
- nFileOffset: 0,
62
- nFileExtension: 0,
63
- lpstrDefExt: 0,
64
- lCustData: 0,
65
- lpfnHook: 0,
66
- lpTemplateName: 0,
67
- pvReserved: 0,
68
- dwReserved: 0,
69
- FlagsEx: 0,
70
25
  });
71
- const ret = ffi.ffiCall(_GetOpenFileNameW, [FFI_PTR], [ofn], FFI_U32);
26
+ const ret = GetOpenFileName(ofn);
72
27
  if (!ret)
73
28
  return null;
74
29
  if (!multiple) {
75
- return wideToStr(fileBuf);
76
- }
77
- const dir = wideToStr(fileBuf);
78
- let pos = (dir.length + 1) * 2;
79
- const files = [];
80
- while (pos < fileBuf.byteLength) {
81
- const f = wideToStr(fileBuf, pos);
82
- if (f.length === 0)
83
- break;
84
- files.push(dir + '\\' + f);
85
- pos += (f.length + 1) * 2;
30
+ return WCHAR.decode(fileBuf);
86
31
  }
87
- if (files.length === 0)
88
- return [dir];
89
- 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];
90
37
  }
91
38
  function openFolderDialog(owner, title) {
92
- if (!ensureDlls())
93
- return null;
94
- const titleWide = title ? strToWide(title) : null;
95
- const bi = BROWSEINFOW.write({
39
+ const titleWide = title ? WCHAR.encode(title) : null;
40
+ const bi = BROWSEINFOW.encode({
96
41
  hwndOwner: owner,
97
- pidlRoot: 0,
98
- pszDisplayName: 0,
99
- lpszTitle: titleWide ? ffi.bufferPtr(titleWide) : 0,
42
+ lpszTitle: titleWide?.ptr ?? 0,
100
43
  ulFlags: 0x00000041,
101
- lpfn: 0,
102
- lParam: 0,
103
- iImage: 0,
104
44
  });
105
- const pidl = ffi.ffiCall(_SHBrowseForFolderW, [FFI_PTR], [bi], FFI_PTR);
45
+ const pidl = SHBrowseForFolder(bi);
106
46
  if (!pidl)
107
47
  return null;
108
- const pathBuf = new ArrayBuffer(260 * 2);
109
- const ok = ffi.ffiCall(_SHGetPathFromIDListW, [ffi.FFI_TYPE_POINTER, FFI_PTR], [pidl, pathBuf], FFI_U32);
110
- ffi.ffiCall(_CoTaskMemFree, [ffi.FFI_TYPE_POINTER], [pidl], ffi.FFI_TYPE_VOID);
111
- 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;
112
52
  }
113
53
  export const PathPicker = forwardRef((props, ref) => {
114
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,25 +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
- import { struct } from '../../ffi-struct.js';
4
+ import { WCHAR } from '../../ffi/bind.js';
5
+ import { TCITEMW } from '../../windows/structs.js';
6
6
  import { nmCode } from '../nmhdr.js';
7
- const TCITEMW = struct({
8
- mask: 'u32',
9
- dwState: 'u32',
10
- dwStateMask: 'u32',
11
- pszText: 'ptr',
12
- cchTextMax: 'i32',
13
- iImage: 'i32',
14
- lParam: 'ptr',
15
- });
16
- function textToUtf16(s) {
17
- const buf = new ArrayBuffer((s.length + 1) * 2);
18
- const dv = new DataView(buf);
19
- for (let i = 0; i < s.length; i++)
20
- dv.setUint16(i * 2, s.charCodeAt(i), true);
21
- return buf;
22
- }
23
7
  export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSelectedIndex = 0, onChange, style }, ref) => {
24
8
  const [internalIndex, setInternalIndex] = useState(defaultSelectedIndex);
25
9
  const isControlled = controlledIndex !== undefined;
@@ -31,17 +15,17 @@ export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSe
31
15
  return;
32
16
  gui.SendMessage(h, 4873 /* gui.TcMsg.DELETEALLITEMS */, 0, 0);
33
17
  for (let i = 0; i < tabs.length; i++) {
34
- const titleBuf = textToUtf16(tabs[i].title);
35
- const tci = TCITEMW.write({
18
+ const titleBuf = WCHAR.encode(tabs[i].title);
19
+ const tci = TCITEMW.encode({
36
20
  mask: 1 /* gui.TcItemFlag.TEXT */,
37
21
  dwState: 0,
38
22
  dwStateMask: 0,
39
- pszText: ffi.bufferPtr(titleBuf),
23
+ pszText: titleBuf.ptr,
40
24
  cchTextMax: tabs[i].title.length + 1,
41
25
  iImage: 0,
42
26
  lParam: 0,
43
27
  });
44
- const tciPtr = ffi.bufferPtr(tci);
28
+ const tciPtr = tci.ptr;
45
29
  gui.SendMessage(h, 4926 /* gui.TcMsg.INSERTITEMW */, i, tciPtr);
46
30
  }
47
31
  gui.SendMessage(h, 4876 /* gui.TcMsg.SETCURSEL */, sel, 0);