quickwin 1.3.0 → 2.1.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 (167) hide show
  1. package/README.en.md +18 -24
  2. package/README.md +17 -23
  3. package/examples/PdfCanvas.js +8 -37
  4. package/examples/PdfCanvas.tsx +11 -41
  5. package/examples/exec_server.js +20155 -1420
  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 +1424 -1180
  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 +1418 -1075
  16. package/examples/test_react_lazy.js +1391 -1059
  17. package/examples/test_react_listbox.js +1390 -1058
  18. package/examples/test_root_window.js +1384 -1052
  19. package/examples/test_tab.js +1391 -1059
  20. package/lib/abort.js +70 -0
  21. package/lib/abort.ts +69 -0
  22. package/lib/certs.js +219 -0
  23. package/lib/certs.ts +229 -0
  24. package/lib/exec-log.js +15 -0
  25. package/lib/exec-log.ts +30 -0
  26. package/lib/fetch.js +88 -7
  27. package/lib/fetch.ts +73 -7
  28. package/lib/ffi/bind.js +441 -0
  29. package/lib/ffi/bind.ts +539 -0
  30. package/lib/ffi/ctype.js +194 -0
  31. package/lib/ffi/ctype.ts +369 -0
  32. package/lib/ffi/index.js +6 -0
  33. package/lib/ffi/index.ts +6 -0
  34. package/lib/ffi/struct.js +490 -0
  35. package/lib/ffi/struct.ts +753 -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 +50 -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 +5 -3
  51. package/lib/react-qw/index.ts +8 -3
  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 +20 -23
  59. package/lib/text-measure.ts +22 -26
  60. package/lib/websocket.js +44 -4
  61. package/lib/websocket.ts +40 -4
  62. package/lib/windows/advapi32.js +53 -0
  63. package/lib/windows/advapi32.ts +64 -0
  64. package/lib/windows/comctl32.js +18 -0
  65. package/lib/windows/comctl32.ts +19 -0
  66. package/lib/windows/comdlg32.js +8 -0
  67. package/lib/windows/comdlg32.ts +10 -0
  68. package/lib/windows/gdi32.js +392 -0
  69. package/lib/windows/gdi32.ts +420 -0
  70. package/lib/windows/kernel32.js +262 -0
  71. package/lib/windows/kernel32.ts +291 -0
  72. package/lib/windows/msimg32.js +38 -0
  73. package/lib/windows/msimg32.ts +45 -0
  74. package/lib/windows/ntdll.js +9 -0
  75. package/lib/windows/ntdll.ts +11 -0
  76. package/lib/windows/ole32.js +7 -0
  77. package/lib/windows/ole32.ts +9 -0
  78. package/lib/windows/psapi.js +32 -0
  79. package/lib/windows/psapi.ts +37 -0
  80. package/lib/windows/shell32.js +13 -0
  81. package/lib/windows/shell32.ts +16 -0
  82. package/lib/windows/structs.js +519 -0
  83. package/lib/windows/structs.ts +566 -0
  84. package/lib/windows/user32.js +572 -0
  85. package/lib/windows/user32.ts +607 -0
  86. package/lib/windows/version.js +29 -0
  87. package/lib/windows/version.ts +35 -0
  88. package/lib/windows/winspool.js +71 -0
  89. package/lib/windows/winspool.ts +80 -0
  90. package/package.json +1 -1
  91. package/quickwin.d.ts +31 -40
  92. package/quickwin_const.d.ts +137 -0
  93. package/qwin-nowasm-x86.exe +0 -0
  94. package/qwin-nowasm.exe +0 -0
  95. package/qwin-x86.exe +0 -0
  96. package/qwin.exe +0 -0
  97. package/test/bench_ffi_callraw.js +236 -0
  98. package/test/bench_ffi_callraw.ts +236 -0
  99. package/test/bench_ffi_overhead.js +139 -0
  100. package/test/bench_ffi_overhead.ts +141 -0
  101. package/test/bench_sendmessage.js +104 -0
  102. package/test/bench_sendmessage.ts +100 -0
  103. package/test/bench_sendmessage_nonnull.js +187 -0
  104. package/test/bench_sendmessage_nonnull.ts +188 -0
  105. package/test/run.js +19 -4
  106. package/test/run.ts +20 -5
  107. package/test/test_basic_fetch.js +29 -0
  108. package/test/test_basic_fetch.ts +26 -0
  109. package/test/test_ffi.js +27 -45
  110. package/test/test_ffi.ts +35 -54
  111. package/test/test_ffi_abi.js +290 -0
  112. package/test/test_ffi_abi.ts +274 -0
  113. package/test/test_ffi_array.js +116 -0
  114. package/test/test_ffi_array.ts +141 -0
  115. package/test/test_ffi_bind.js +620 -8
  116. package/test/test_ffi_bind.ts +630 -8
  117. package/test/test_ffi_struct.js +632 -88
  118. package/test/test_ffi_struct.ts +666 -91
  119. package/test/test_ffi_struct_layout.js +495 -0
  120. package/test/test_ffi_struct_layout.ts +543 -0
  121. package/test/test_ffi_user32.js +103 -0
  122. package/test/test_ffi_user32.ts +113 -0
  123. package/test/test_ffi_windows.js +101 -0
  124. package/test/test_ffi_windows.ts +113 -0
  125. package/test/test_files.js +102 -0
  126. package/test/test_files.ts +121 -0
  127. package/test/test_gdi_draw.js +681 -0
  128. package/test/test_gdi_draw.ts +725 -0
  129. package/test/test_helper.js +8 -1
  130. package/test/test_helper.ts +9 -1
  131. package/test/test_http_import.js +18 -0
  132. package/test/test_http_import.ts +19 -0
  133. package/test/test_http_server.js +73 -1
  134. package/test/test_http_server.ts +75 -1
  135. package/test/test_ipv6.js +2 -2
  136. package/test/test_ipv6.ts +2 -2
  137. package/test/test_net_fetch.js +74 -11
  138. package/test/test_net_fetch.ts +73 -10
  139. package/test/test_net_websocket.js +1 -1
  140. package/test/test_net_websocket.ts +1 -1
  141. package/test/test_printer.js +137 -0
  142. package/test/test_printer.ts +148 -0
  143. package/test/test_proc.js +141 -0
  144. package/test/test_proc.ts +163 -0
  145. package/test/test_react_complex.js +1401 -1069
  146. package/test/test_react_counter.js +1391 -1059
  147. package/test/test_react_flex.js +1411 -1072
  148. package/test/test_react_hidden.js +1394 -1062
  149. package/test/test_react_listview_link.js +1391 -1059
  150. package/test/test_react_notify.js +18638 -0
  151. package/test/test_react_render.js +1391 -1059
  152. package/test/test_registry.js +74 -0
  153. package/test/test_registry.ts +95 -0
  154. package/test/test_sysinfo.js +122 -0
  155. package/test/test_sysinfo.ts +141 -0
  156. package/test/test_wolfssl_read.js +45 -43
  157. package/test/test_wolfssl_read.ts +42 -40
  158. package/test/test_worker_net.js +35 -17
  159. package/test/test_worker_net.ts +27 -15
  160. package/tsconfig.json +1 -1
  161. package/examples/exec_server.ts +0 -80
  162. package/lib/ffi-bind.js +0 -85
  163. package/lib/ffi-bind.ts +0 -170
  164. package/lib/ffi-struct.js +0 -224
  165. package/lib/ffi-struct.ts +0 -239
  166. package/lib/win-common-structs.js +0 -39
  167. 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,37 @@ 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
+ // GETFONT 的 LRESULT 断言为 HFONT(gdi32 <HGDIOBJ>ptr 位)
51
+ if (!GetObject(cur, LOGFONTW.size, lf))
136
52
  return null;
53
+ base = LOGFONTW.decode(lf);
137
54
  }
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);
55
+ const h = CreateFontIndirect({
56
+ ...base,
57
+ ...(style.bold ? { lfWeight: 700 /* gui.FontWeight.BOLD */ } : {}),
58
+ ...(style.italic ? { lfItalic: 1 } : {}),
59
+ ...(style.underline ? { lfUnderline: 1 } : {}),
60
+ });
148
61
  fontCache.set(key, h ? h : 0);
149
62
  return h ? h : null;
150
63
  }
151
64
  function handleCustomDraw(lParam, columns, data, hwnd) {
152
- const stage = readU32(lParam, CD_STAGE);
65
+ const { dwDrawStage: stage } = NMCUSTOMDRAW.decode(lParam);
153
66
  if (stage === 1 /* gui.CustomDrawStage.PREPAINT */)
154
67
  return 32 /* gui.CustomDrawFlag.NOTIFYITEMDRAW */;
155
68
  if (stage === 65537 /* gui.CustomDrawStage.ITEMPREPAINT */)
156
69
  return 32 /* gui.CustomDrawFlag.NOTIFYSUBITEMDRAW */;
157
70
  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);
71
+ // 只有子项阶段才按 NMLVCUSTOMDRAW(更大的结构)读,前面的阶段只读了 NMCUSTOMDRAW 的前缀
72
+ const cd = NMLVCUSTOMDRAW.decode(lParam);
73
+ const style = resolveCellStyle(columns, data, cd.dwItemSpec, cd.iSubItem);
161
74
  if (!style)
162
75
  return 0 /* gui.CustomDrawFlag.DODEFAULT */;
163
76
  if (style.color !== undefined)
@@ -165,12 +78,9 @@ function handleCustomDraw(lParam, columns, data, hwnd) {
165
78
  if (style.background !== undefined)
166
79
  writeU32(lParam, CD_CLRTEXTBK, style.background);
167
80
  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
- }
81
+ if (hfont && cd.hdc) {
82
+ SelectObject(cd.hdc, hfont);
83
+ return 2 /* gui.CustomDrawFlag.NEWFONT */;
174
84
  }
175
85
  }
176
86
  return 0 /* gui.CustomDrawFlag.DODEFAULT */;
@@ -201,14 +111,14 @@ function resolveCellStyle(columns, data, row, colIndex) {
201
111
  return style || undefined;
202
112
  }
203
113
  function makeLVItem(i, sub, text, image) {
204
- const textBuf = textToUtf16(text);
205
- const b = LVITEMW.write({
114
+ const textBuf = WCHAR.encode(text);
115
+ const b = LVITEMW.encode({
206
116
  mask: 1 /* LvItemFlag.TEXT */ | (image !== undefined ? 2 /* LvItemFlag.IMAGE */ : 0),
207
117
  iItem: i,
208
118
  iSubItem: sub,
209
119
  state: 0,
210
120
  stateMask: 0,
211
- pszText: bufPtr(textBuf),
121
+ pszText: textBuf.ptr,
212
122
  cchTextMax: 0,
213
123
  iImage: image ?? 0,
214
124
  lParam: 0,
@@ -245,7 +155,7 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
245
155
  const hbm = gui.CreateBitmapFromPixels(size, size, p);
246
156
  if (hbm) {
247
157
  gui.ImageListAdd(img, hbm);
248
- gui.DeleteObject(hbm);
158
+ DeleteObject(hbm);
249
159
  }
250
160
  }
251
161
  gui.SendMessage(h, 4099 /* gui.LvMsg.SETIMAGELIST */, 1 /* gui.LvImageList.SMALL */, img);
@@ -265,12 +175,12 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
265
175
  gui.SendMessage(h, 4150 /* gui.LvMsg.SETEXTENDEDLISTVIEWSTYLE */, 0, 32 /* gui.LvExStyle.FULLROWSELECT */ | 1 /* gui.LvExStyle.GRIDLINES */ | 65536 /* gui.LvExStyle.DOUBLEBUFFER */);
266
176
  const n = columns.length;
267
177
  for (let j = 0; j < n; j++) {
268
- const titleBuf = textToUtf16(columns[j].name);
269
- const lvc = LVCOLUMNW.write({
178
+ const titleBuf = WCHAR.encode(columns[j].name);
179
+ const lvc = LVCOLUMNW.encode({
270
180
  mask: 4 /* LvColumnMask.TEXT */ | 2 /* LvColumnMask.WIDTH */ | 1 /* LvColumnMask.FORMAT */,
271
181
  fmt: alignToFmt(columns[j].align),
272
182
  cx: columns[j].width ?? 100,
273
- pszText: bufPtr(titleBuf),
183
+ pszText: titleBuf.ptr,
274
184
  cchTextMax: 0,
275
185
  iSubItem: j,
276
186
  iImage: 0,
@@ -280,7 +190,7 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
280
190
  cxIdeal: 0,
281
191
  });
282
192
  lvc.__titleBuf = titleBuf;
283
- gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j, bufPtr(lvc));
193
+ gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j, lvc.ptr);
284
194
  }
285
195
  }, [columns]);
286
196
  useEffect(() => {
@@ -293,10 +203,10 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
293
203
  const record = data[i];
294
204
  const img = getIcon ? getIcon(record, i) : undefined;
295
205
  const itemBuf = makeLVItem(i, 0, cellText(record, columns[0], i), img);
296
- gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0, bufPtr(itemBuf));
206
+ gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0, itemBuf.ptr);
297
207
  for (let j = 1; j < nCols; j++) {
298
208
  const subBuf = makeLVItem(i, j, cellText(record, columns[j], i));
299
- gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0, bufPtr(subBuf));
209
+ gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0, subBuf.ptr);
300
210
  }
301
211
  }
302
212
  for (let j = 0; j < columns.length; j++) {
@@ -312,14 +222,12 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
312
222
  return handleCustomDraw(e.lParam, columns, data, lvRef.current);
313
223
  }
314
224
  if (code === -100 /* gui.LvNotifyCode.ITEMCHANGING */) {
315
- const uNewState = readU32(e.lParam, NMLV_UNEW);
316
- const uOldState = readU32(e.lParam, NMLV_UOLD);
225
+ const { uNewState, uOldState } = NMLISTVIEW.decode(e.lParam);
317
226
  if ((uNewState & 2 /* LvItemState.SELECTED */) !== (uOldState & 2 /* LvItemState.SELECTED */))
318
227
  return 1;
319
228
  }
320
229
  if (code === -2 /* gui.LvNotifyCode.CLICK */) {
321
- const iItem = readI32(e.lParam, NMIA_ITEM);
322
- const iSubItem = readI32(e.lParam, NMIA_SUBITEM);
230
+ const { iItem, iSubItem } = NMLISTVIEW.decode(e.lParam);
323
231
  const col = columns[iSubItem];
324
232
  const record = data[iItem];
325
233
  if (col?.onCellClick && record !== undefined)
@@ -337,33 +245,21 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
337
245
  const h = lvRef.current;
338
246
  if (!h)
339
247
  return;
340
- ensureUser32();
341
- if (!loadCursorW || !setCursorFn || !screenToClient)
342
- return;
343
- const sp = gui.GetCursorPos();
344
- if (!sp)
248
+ // GetCursorPos 出参写入初值 → ScreenToClient 就地更新 → decode 读回
249
+ const pt = POINT.alloc();
250
+ if (!GetCursorPos(pt.ptr))
345
251
  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);
252
+ ScreenToClient(h, pt.ptr);
253
+ const { x: sx, y: sy } = POINT.decode(pt);
254
+ const hit = LVHITTESTINFO.encode({ pt: [sx, sy], iItem: -1, iSubItem: -1 });
255
+ gui.SendMessage(h, 4153 /* gui.LvMsg.SUBITEMHITTEST */, 0, hit.ptr);
256
+ const { iItem, iSubItem } = LVHITTESTINFO.decode(hit);
361
257
  const style = resolveCellStyle(columns, data, iItem, iSubItem);
362
258
  if (!style || style.cursor === undefined)
363
259
  return;
364
- const hc = ffi.ffiCall(loadCursorW, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_POINTER], [0, style.cursor], ffi.FFI_TYPE_POINTER);
260
+ const hc = LoadCursor(NULL, style.cursor);
365
261
  if (hc) {
366
- ffi.ffiCall(setCursorFn, [ffi.FFI_TYPE_POINTER], [hc], ffi.FFI_TYPE_POINTER);
262
+ SetCursor(hc);
367
263
  return 1;
368
264
  }
369
265
  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.ptr);
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.ptr);
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);