quickwin 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.en.md +2 -1
  2. package/README.md +2 -1
  3. package/examples/PdfCanvas.js +3 -3
  4. package/examples/PdfCanvas.tsx +3 -3
  5. package/examples/pdf_preview2.js +34 -19
  6. package/examples/pdf_preview2.ts +34 -20
  7. package/examples/pdf_viewer.js +959 -219
  8. package/examples/pdf_viewer.tsx +29 -19
  9. package/examples/test_gallery.js +925 -198
  10. package/examples/test_react_lazy.js +908 -181
  11. package/examples/test_react_listbox.js +908 -181
  12. package/examples/test_root_window.js +908 -181
  13. package/examples/test_tab.js +908 -181
  14. package/lib/ffi-bind.js +85 -0
  15. package/lib/ffi-bind.ts +170 -0
  16. package/lib/ffi-struct.js +224 -0
  17. package/lib/ffi-struct.ts +239 -0
  18. package/lib/react-qw/components/DateTimePicker.js +21 -15
  19. package/lib/react-qw/components/Link.js +4 -6
  20. package/lib/react-qw/components/ListView.js +84 -40
  21. package/lib/react-qw/components/PathPicker.js +40 -24
  22. package/lib/react-qw/components/Tab.js +21 -10
  23. package/lib/react-qw/components/Tooltip.js +27 -15
  24. package/lib/react-qw/components/TreeView.js +23 -33
  25. package/lib/react-qw/nmhdr.js +26 -0
  26. package/lib/react-qw/nmhdr.ts +32 -0
  27. package/lib/text-measure.js +14 -26
  28. package/lib/text-measure.ts +14 -26
  29. package/lib/win-common-structs.js +39 -0
  30. package/lib/win-common-structs.ts +41 -0
  31. package/package.json +1 -1
  32. package/quickwin.d.ts +6 -2
  33. package/qwin-nowasm-x86.exe +0 -0
  34. package/qwin-nowasm.exe +0 -0
  35. package/qwin-x86.exe +0 -0
  36. package/qwin.exe +0 -0
  37. package/test/run.js +2 -0
  38. package/test/run.ts +2 -0
  39. package/test/test_ffi.js +12 -8
  40. package/test/test_ffi.ts +12 -12
  41. package/test/test_ffi_bind.js +42 -0
  42. package/test/test_ffi_bind.ts +46 -0
  43. package/test/test_ffi_struct.js +201 -0
  44. package/test/test_ffi_struct.ts +223 -0
  45. package/test/test_react_complex.js +911 -184
  46. package/test/test_react_counter.js +908 -181
  47. package/test/test_react_flex.js +908 -181
  48. package/test/test_react_hidden.js +910 -183
  49. package/test/test_react_listview_link.js +908 -181
  50. package/test/test_react_render.js +908 -181
@@ -0,0 +1,239 @@
1
+ import * as os from 'os'
2
+ import { normKind, type Norm } from './ffi-bind.js'
3
+
4
+ // 声明式结构体定义:struct({ field: 'i32', p: 'ptr', ws: 'wstr[32]', child: SubStruct })
5
+ // layout 采用 MSVC 对齐语义:
6
+ // i8/u8→1 i16/u16→2 i32/u32/f32→4 i64/u64/f64→8 ptr→arch 宽(4/8)
7
+ // struct 对齐 = 最大字段对齐,总尺寸末尾补齐
8
+ // 字段类型:
9
+ // 标量 kind(含 ffi-bind 的 C typedef 别名:int/DWORD/LPARAM/HANDLE...)
10
+ // 定长数组 'u16[8]' / 'i32[4]' / 'wstr[32]'(wstr 读 string、写 string,自动截断+补 0)
11
+ // 嵌套 struct:字段值直接传子 struct 定义对象
12
+
13
+ type FieldKind = 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'u64' | 'i64' | 'f32' | 'f64' | 'ptr' | 'wstr'
14
+
15
+ const PTR = os.arch === 'x64' ? 8 : 4
16
+
17
+ // kind → [align, size]
18
+ const AS: Record<FieldKind, [number, number]> = {
19
+ u8: [1, 1], i8: [1, 1],
20
+ u16: [2, 2], i16: [2, 2],
21
+ u32: [4, 4], i32: [4, 4],
22
+ u64: [8, 8], i64: [8, 8],
23
+ f32: [4, 4], f64: [8, 8],
24
+ ptr: [PTR, PTR],
25
+ wstr: [2, 2], // 仅定长数组元素用(wchar)
26
+ }
27
+
28
+ type KnownScalar = 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'u64' | 'i64' | 'f32' | 'f64' | 'ptr'
29
+
30
+ type ValOf<S extends string> = S extends `${infer B}[${number}]`
31
+ ? (B extends 'wstr' ? string : Norm<B> extends KnownScalar ? number[] : never)
32
+ : S extends KnownScalar ? number
33
+ : Norm<S> extends KnownScalar ? number : never
34
+
35
+ export type StructShape<T extends Record<string, unknown> = Record<string, unknown>> = {
36
+ readonly __fields: T
37
+ readonly size: number
38
+ readonly structAlign: number
39
+ }
40
+
41
+ type ShapeOf<T extends Record<string, unknown>> = {
42
+ [K in keyof T]: T[K] extends string ? ValOf<T[K]> : T[K] extends StructShape ? ShapeOf<T[K]['__fields']> : never
43
+ }
44
+
45
+ export type StructDef<F extends Record<string, string | StructShape>> = StructShape<F> & {
46
+ read(buf: ArrayBuffer, offset?: number): ShapeOf<F>
47
+ write(v: ShapeOf<F>, buf?: ArrayBuffer, offset?: number): ArrayBuffer
48
+ offsetOf(name: keyof F): number
49
+ }
50
+
51
+ type Field = {
52
+ name: string
53
+ base: FieldKind
54
+ count: number
55
+ offset: number
56
+ child?: StructShape
57
+ }
58
+
59
+ function alignUp(v: number, a: number): number {
60
+ return (v + a - 1) & ~(a - 1)
61
+ }
62
+
63
+ function parseField(f: string): { base: FieldKind; count: number } {
64
+ const arr = /^(.+)\[(\d+)\]$/.exec(f)
65
+ if (arr) {
66
+ const b = normKind(arr[1]!)
67
+ if (b === 'void') throw new Error(`ffi-struct: "void" cannot be an array base`)
68
+ return { base: b as FieldKind, count: Number(arr[2]) }
69
+ }
70
+ const b = normKind(f)
71
+ if (b === 'void' || b === 'wstr') throw new Error(`ffi-struct: invalid scalar field type "${f}"`)
72
+ return { base: b as FieldKind, count: 1 }
73
+ }
74
+
75
+ function layout<F extends Record<string, string | StructShape>>(def: F): { fields: Field[]; size: number; structAlign: number } {
76
+ const fields: Field[] = []
77
+ let cursor = 0
78
+ let maxAlign = 1
79
+ for (const key of Object.keys(def) as (keyof F)[]) {
80
+ const v = def[key]
81
+ let a: number
82
+ let w: number
83
+ if (typeof v === 'string') {
84
+ const { base, count } = parseField(v)
85
+ const [align, elem] = AS[base]
86
+ a = align
87
+ w = count >= 1 ? elem * count : elem
88
+ fields.push({ name: key as string, base, count, offset: 0 })
89
+ } else {
90
+ const child = v as StructShape
91
+ a = child.structAlign
92
+ w = child.size
93
+ fields.push({ name: key as string, base: 'u8', count: 0, offset: 0, child })
94
+ }
95
+ const off = alignUp(cursor, a)
96
+ const f = fields[fields.length - 1]!
97
+ f.offset = off
98
+ cursor = off + w
99
+ if (a > maxAlign) maxAlign = a
100
+ }
101
+ return { fields, size: alignUp(cursor, maxAlign), structAlign: maxAlign }
102
+ }
103
+
104
+ const _layoutCache = new WeakMap<object, { fields: Field[]; size: number; structAlign: number }>()
105
+
106
+ function buildLayout<F extends Record<string, string | StructShape>>(def: F): { fields: Field[]; size: number; structAlign: number } {
107
+ let hit = _layoutCache.get(def)
108
+ if (hit) return hit
109
+ hit = layout(def)
110
+ _layoutCache.set(def, hit)
111
+ return hit
112
+ }
113
+
114
+ function readScalar(dv: DataView, off: number, k: FieldKind): number {
115
+ switch (k) {
116
+ case 'u8': return dv.getUint8(off)
117
+ case 'i8': return dv.getInt8(off)
118
+ case 'u16': return dv.getUint16(off, true)
119
+ case 'i16': return dv.getInt16(off, true)
120
+ case 'u32': return dv.getUint32(off, true)
121
+ case 'i32': return dv.getInt32(off, true)
122
+ case 'u64': return Number(dv.getBigUint64(off, true))
123
+ case 'i64': return Number(dv.getBigInt64(off, true))
124
+ case 'f32': return dv.getFloat32(off, true)
125
+ case 'f64': return dv.getFloat64(off, true)
126
+ case 'ptr': return readPtr(dv, off)
127
+ case 'wstr': return dv.getUint16(off, true)
128
+ }
129
+ }
130
+
131
+ function readPtr(dv: DataView, off: number): number {
132
+ if (PTR === 8) return Number(dv.getBigUint64(off, true))
133
+ return dv.getUint32(off, true)
134
+ }
135
+
136
+ function writeScalar(dv: DataView, off: number, k: FieldKind, val: number): void {
137
+ switch (k) {
138
+ case 'u8': dv.setUint8(off, val); break
139
+ case 'i8': dv.setInt8(off, val); break
140
+ case 'u16': dv.setUint16(off, val, true); break
141
+ case 'i16': dv.setInt16(off, val, true); break
142
+ case 'u32': dv.setUint32(off, val >>> 0, true); break
143
+ case 'i32': dv.setInt32(off, val | 0, true); break
144
+ case 'u64': dv.setBigUint64(off, BigInt(Math.trunc(val)), true); break
145
+ case 'i64': dv.setBigInt64(off, BigInt(Math.trunc(val)), true); break
146
+ case 'f32': dv.setFloat32(off, val, true); break
147
+ case 'f64': dv.setFloat64(off, val, true); break
148
+ case 'ptr': writePtr(dv, off, val); break
149
+ case 'wstr': dv.setUint16(off, val, true); break
150
+ }
151
+ }
152
+
153
+ function writePtr(dv: DataView, off: number, val: number): void {
154
+ if (PTR === 8) dv.setBigUint64(off, BigInt(Math.trunc(val)), true)
155
+ else dv.setUint32(off, val >>> 0, true)
156
+ }
157
+
158
+ function readField(dv: DataView, base: number, f: Field): unknown {
159
+ const off = base + f.offset
160
+ if (f.child) return readInto(f.child, dv.buffer as ArrayBuffer, off)
161
+ if (f.count > 1) {
162
+ if (f.base === 'wstr') {
163
+ const arr = new Uint16Array(dv.buffer, dv.byteOffset + off, f.count)
164
+ let end = 0
165
+ while (end < arr.length && arr[end] !== 0) end++
166
+ let s = ''
167
+ for (let i = 0; i < end; i++) s += String.fromCharCode(arr[i]!)
168
+ return s
169
+ }
170
+ const out: number[] = []
171
+ const step = AS[f.base]![1]
172
+ for (let i = 0; i < f.count; i++) out.push(readScalar(dv, off + i * step, f.base))
173
+ return out
174
+ }
175
+ return readScalar(dv, off, f.base)
176
+ }
177
+
178
+ function writeField(dv: DataView, base: number, f: Field, v: unknown): void {
179
+ const off = base + f.offset
180
+ if (f.child) { writeInto(f.child, v as Record<string, unknown>, dv.buffer as ArrayBuffer, off); return }
181
+ if (f.count > 1) {
182
+ if (f.base === 'wstr') {
183
+ const s = String(v ?? '')
184
+ const arr = new Uint16Array(dv.buffer, dv.byteOffset + off, f.count)
185
+ for (let i = 0; i < arr.length; i++) arr[i] = 0
186
+ const n = Math.min(s.length, f.count - 1)
187
+ for (let i = 0; i < n; i++) arr[i] = s.charCodeAt(i)
188
+ return
189
+ }
190
+ const step = AS[f.base]![1]
191
+ const list = (v ?? []) as number[]
192
+ for (let i = 0; i < f.count; i++) writeScalar(dv, off + i * step, f.base, list[i] ?? 0)
193
+ return
194
+ }
195
+ writeScalar(dv, off, f.base, Number(v))
196
+ }
197
+
198
+ function doSize<F extends Record<string, string | StructShape>>(def: F): number {
199
+ return buildLayout(def).size
200
+ }
201
+
202
+ function readInto<S>(child: StructShape, buf: ArrayBuffer, offset: number): S {
203
+ const r = child as unknown as { read(b: ArrayBuffer, o: number): S }
204
+ return r.read(buf, offset)
205
+ }
206
+
207
+ function doRead<F extends Record<string, string | StructShape>>(_def: F, fields: Field[], buf: ArrayBuffer, offset: number): ShapeOf<F> {
208
+ const out: Record<string, unknown> = {}
209
+ const dv = new DataView(buf)
210
+ for (const f of fields) out[f.name] = readField(dv, offset, f)
211
+ return out as ShapeOf<F>
212
+ }
213
+
214
+ function doWrite<F extends Record<string, string | StructShape>>(def: F, fields: Field[], v: ShapeOf<F>, buf?: ArrayBuffer, offset = 0): ArrayBuffer {
215
+ buf = buf ?? new ArrayBuffer(doSize(def))
216
+ const dv = new DataView(buf)
217
+ for (const f of fields) writeField(dv, offset, f, (v as Record<string, unknown>)[f.name])
218
+ return buf
219
+ }
220
+
221
+ function writeInto<W>(child: StructShape, v: Record<string, unknown>, buf: ArrayBuffer, offset: number): void {
222
+ const w = child as unknown as { write(i: W, b: ArrayBuffer, o: number): unknown }
223
+ w.write(v as W, buf, offset)
224
+ }
225
+
226
+ export function struct<const F extends Record<string, string | StructShape>>(def: F): StructDef<F> {
227
+ const { fields, size, structAlign } = buildLayout(def)
228
+ return {
229
+ __fields: def,
230
+ size,
231
+ structAlign,
232
+ read: (buf: ArrayBuffer, offset = 0) => doRead(def, fields, buf, offset),
233
+ write: (v, buf?: ArrayBuffer, offset = 0) => doWrite(def, fields, v, buf, offset),
234
+ offsetOf: (name: keyof F) => {
235
+ for (const f of fields) if (f.name === name) return f.offset
236
+ throw new Error(`ffi-struct: no field "${String(name)}"`)
237
+ },
238
+ }
239
+ }
@@ -2,13 +2,17 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef, useRef, useEffect, useState } from 'react';
3
3
  import * as gui from 'gui';
4
4
  import * as ffi from 'ffi';
5
- function readI16(ptr, offset) {
6
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8);
7
- }
8
- function readI32(ptr, offset) {
9
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
10
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
11
- }
5
+ import { struct } from '../../ffi-struct.js';
6
+ import { NMHDR, nmCode, readI32At, readU16At } from '../nmhdr.js';
7
+ const SYSTEMTIME = struct({
8
+ wYear: 'u16', wMonth: 'u16', wDayOfWeek: 'u16', wDay: 'u16',
9
+ wHour: 'u16', wMinute: 'u16', wSecond: 'u16', wMilliseconds: 'u16',
10
+ });
11
+ const NMDATETIMECHANGE = struct({
12
+ hdr: NMHDR,
13
+ dwFlags: 'u32',
14
+ st: SYSTEMTIME,
15
+ });
12
16
  function bufPtr(buf) {
13
17
  return ffi.bufferPtr(buf);
14
18
  }
@@ -69,21 +73,23 @@ const DateTimePicker = forwardRef(function DateTimePicker({ value, onChange, def
69
73
  }, []);
70
74
  return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
71
75
  if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
72
- const code = readI32(e.lParam, 16);
76
+ const code = nmCode(e.lParam);
73
77
  if (code === -759 /* gui.DtNotifyCode.DATETIMECHANGE */) {
74
- const dwFlags = readI32(e.lParam, 24);
78
+ // NMDATETIMECHANGE: NMHDR + DWORD dwFlags + SYSTEMTIME st
79
+ const st = NMDATETIMECHANGE.offsetOf('st');
80
+ const dwFlags = readI32At(e.lParam, NMDATETIMECHANGE.offsetOf('dwFlags'));
75
81
  if (dwFlags === 1 /* gui.DtFlag.GDT_NONE */) {
76
82
  if (!isControlled)
77
83
  setInternalDate(null);
78
84
  onChangeRef.current?.(null);
79
85
  }
80
86
  else {
81
- const year = readI16(e.lParam, 28);
82
- const month = readI16(e.lParam, 30);
83
- const day = readI16(e.lParam, 34);
84
- const hour = readI16(e.lParam, 36);
85
- const min = readI16(e.lParam, 38);
86
- const sec = readI16(e.lParam, 40);
87
+ const year = readU16At(e.lParam, st + 0);
88
+ const month = readU16At(e.lParam, st + 2);
89
+ const day = readU16At(e.lParam, st + 6);
90
+ const hour = readU16At(e.lParam, st + 8);
91
+ const min = readU16At(e.lParam, st + 10);
92
+ const sec = readU16At(e.lParam, st + 12);
87
93
  const d = new Date(year, month - 1, day, hour, min, sec);
88
94
  if (!isControlled)
89
95
  setInternalDate(d);
@@ -2,10 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef, useRef, useEffect } from 'react';
3
3
  import * as gui from 'gui';
4
4
  import * as ffi from 'ffi';
5
- function readI32(ptr, offset) {
6
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
7
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
8
- }
5
+ import { nmCode, NMHDR_SIZE } from '../nmhdr.js';
9
6
  function readUtf16(ptr, offset, maxWords) {
10
7
  const chars = [];
11
8
  for (let i = 0; i < maxWords; i++) {
@@ -32,9 +29,10 @@ const Link = forwardRef(function Link({ href, children, onClick, style }, ref) {
32
29
  }, [linkText]);
33
30
  return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
34
31
  if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
35
- const code = readI32(e.lParam, 16);
32
+ const code = nmCode(e.lParam);
36
33
  if (code === -2 /* gui.SysLinkNotifyCode.CLICK */ || code === -4 /* gui.SysLinkNotifyCode.RETURN */) {
37
- const url = readUtf16(e.lParam, 136, 2048);
34
+ // NMLINK: NMHDR(12/20) + LITEM{ mask,iLink,state,stateMask (16B) + szID[48] (96B) } → szUrl
35
+ const url = readUtf16(e.lParam, NMHDR_SIZE + 112, 2048);
38
36
  onClickRef.current?.(url);
39
37
  }
40
38
  }
@@ -3,6 +3,8 @@ import { forwardRef, useRef, useEffect } from 'react';
3
3
  import * as gui from 'gui';
4
4
  import * as ffi from 'ffi';
5
5
  import * as win from 'win';
6
+ import { struct } from '../../ffi-struct.js';
7
+ import { NMHDR, PTR_SIZE, nmCode } from '../nmhdr.js';
6
8
  export function makeColorBlock(size, bgra) {
7
9
  const n = size * size;
8
10
  const buf = new ArrayBuffer(n * 4);
@@ -46,16 +48,46 @@ function bufPtr(buf) {
46
48
  }
47
49
  const LV_WS = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */ | 2097152 /* gui.WindowStyle.VSCROLL */ | 1048576 /* gui.WindowStyle.HSCROLL */
48
50
  | 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
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
+ const CD_CLRTEXT = NMLVCUSTOMDRAW.offsetOf('clrText');
73
+ 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
+ });
59
91
  const fontCache = new Map();
60
92
  let gdi32 = null;
61
93
  let createFontIndirectW = null;
@@ -99,7 +131,7 @@ function getCellFont(hwnd, style) {
99
131
  const dv = new DataView(lf);
100
132
  const cur = gui.SendMessage(hwnd, 49 /* gui.WmMsg.GETFONT */, 0, 0);
101
133
  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);
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);
103
135
  if (!got)
104
136
  return null;
105
137
  }
@@ -112,9 +144,9 @@ function getCellFont(hwnd, style) {
112
144
  dv.setUint8(20, 1);
113
145
  if (style.underline)
114
146
  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;
147
+ const h = ffi.ffiCall(createFontIndirectW, [ffi.FFI_TYPE_POINTER], [lf], ffi.FFI_TYPE_POINTER);
148
+ fontCache.set(key, h ? h : 0);
149
+ return h ? h : null;
118
150
  }
119
151
  function handleCustomDraw(lParam, columns, data, hwnd) {
120
152
  const stage = readU32(lParam, CD_STAGE);
@@ -134,9 +166,9 @@ function handleCustomDraw(lParam, columns, data, hwnd) {
134
166
  writeU32(lParam, CD_CLRTEXTBK, style.background);
135
167
  const hfont = getCellFont(hwnd, style);
136
168
  if (hfont && selectObjectFn) {
137
- const hdc = readU64(lParam, CD_HDC);
169
+ const hdc = PTR_SIZE === 8 ? readU64(lParam, CD_HDC) : readU32(lParam, CD_HDC);
138
170
  if (hdc) {
139
- ffi.ffiCall(selectObjectFn, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [hdc, hfont], ffi.FFI_TYPE_UINT64);
171
+ ffi.ffiCall(selectObjectFn, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_POINTER], [hdc, hfont], ffi.FFI_TYPE_POINTER);
140
172
  return 2 /* gui.CustomDrawFlag.NEWFONT */;
141
173
  }
142
174
  }
@@ -169,18 +201,24 @@ function resolveCellStyle(columns, data, row, colIndex) {
169
201
  return style || undefined;
170
202
  }
171
203
  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
204
  const textBuf = textToUtf16(text);
183
- dv.setBigUint64(24, BigInt(bufPtr(textBuf)), true);
205
+ const b = LVITEMW.write({
206
+ mask: 1 /* LvItemFlag.TEXT */ | (image !== undefined ? 2 /* LvItemFlag.IMAGE */ : 0),
207
+ iItem: i,
208
+ iSubItem: sub,
209
+ state: 0,
210
+ stateMask: 0,
211
+ pszText: bufPtr(textBuf),
212
+ cchTextMax: 0,
213
+ iImage: image ?? 0,
214
+ lParam: 0,
215
+ iIndent: 0,
216
+ iGroupId: 0,
217
+ cColumns: 0,
218
+ puColumns: 0,
219
+ piColFmt: 0,
220
+ iGroup: 0,
221
+ });
184
222
  b.__textBuf = textBuf;
185
223
  return b;
186
224
  }
@@ -228,13 +266,19 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
228
266
  const n = columns.length;
229
267
  for (let j = 0; j < n; j++) {
230
268
  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);
269
+ const lvc = LVCOLUMNW.write({
270
+ mask: 4 /* LvColumnMask.TEXT */ | 2 /* LvColumnMask.WIDTH */ | 1 /* LvColumnMask.FORMAT */,
271
+ fmt: alignToFmt(columns[j].align),
272
+ cx: columns[j].width ?? 100,
273
+ pszText: bufPtr(titleBuf),
274
+ cchTextMax: 0,
275
+ iSubItem: j,
276
+ iImage: 0,
277
+ iOrder: 0,
278
+ cxMin: 0,
279
+ cxDefault: 0,
280
+ cxIdeal: 0,
281
+ });
238
282
  lvc.__titleBuf = titleBuf;
239
283
  gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j, bufPtr(lvc));
240
284
  }
@@ -263,13 +307,13 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
263
307
  }, [data, columns, getIcon]);
264
308
  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
309
  if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
266
- const code = readI32(e.lParam, NM_CODE);
310
+ const code = nmCode(e.lParam);
267
311
  if (code === -12 /* gui.LvNotifyCode.CUSTOMDRAW */) {
268
312
  return handleCustomDraw(e.lParam, columns, data, lvRef.current);
269
313
  }
270
314
  if (code === -100 /* gui.LvNotifyCode.ITEMCHANGING */) {
271
- const uNewState = readU32(e.lParam, 32);
272
- const uOldState = readU32(e.lParam, 36);
315
+ const uNewState = readU32(e.lParam, NMLV_UNEW);
316
+ const uOldState = readU32(e.lParam, NMLV_UOLD);
273
317
  if ((uNewState & 2 /* LvItemState.SELECTED */) !== (uOldState & 2 /* LvItemState.SELECTED */))
274
318
  return 1;
275
319
  }
@@ -303,7 +347,7 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
303
347
  const sdv = new DataView(sbuf);
304
348
  sdv.setInt32(0, sp[0], true);
305
349
  sdv.setInt32(4, sp[1], true);
306
- ffi.ffiCall(screenToClient, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_POINTER], [h, sbuf], ffi.FFI_TYPE_SINT32);
350
+ ffi.ffiCall(screenToClient, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_POINTER], [h, sbuf], ffi.FFI_TYPE_SINT32);
307
351
  const lvhi = new ArrayBuffer(24);
308
352
  const lvd = new DataView(lvhi);
309
353
  lvd.setInt32(0, sdv.getInt32(0, true), true);
@@ -317,9 +361,9 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
317
361
  const style = resolveCellStyle(columns, data, iItem, iSubItem);
318
362
  if (!style || style.cursor === undefined)
319
363
  return;
320
- const hc = ffi.ffiCall(loadCursorW, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [0, style.cursor], ffi.FFI_TYPE_UINT64);
364
+ const hc = ffi.ffiCall(loadCursorW, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_POINTER], [0, style.cursor], ffi.FFI_TYPE_POINTER);
321
365
  if (hc) {
322
- ffi.ffiCall(setCursorFn, [ffi.FFI_TYPE_UINT64], [hc], ffi.FFI_TYPE_UINT64);
366
+ ffi.ffiCall(setCursorFn, [ffi.FFI_TYPE_POINTER], [hc], ffi.FFI_TYPE_POINTER);
323
367
  return 1;
324
368
  }
325
369
  return;
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useState, useRef } from 'react';
3
3
  import * as ffi from 'ffi';
4
4
  import * as win from 'win';
5
+ import { OPENFILENAMEW, BROWSEINFOW } from '../../win-common-structs.js';
5
6
  const FFI_PTR = ffi.FFI_TYPE_POINTER;
6
7
  const FFI_U32 = ffi.FFI_TYPE_UINT32;
7
8
  let _GetOpenFileNameW = 0;
@@ -32,31 +33,42 @@ function wideToStr(buf, offset = 0) {
32
33
  const nullIdx = str.indexOf('\0');
33
34
  return nullIdx >= 0 ? str.substring(0, nullIdx) : str;
34
35
  }
35
- function setPtr(dv, off, ptr) {
36
- dv.setUint32(off, ptr & 0xFFFFFFFF, true);
37
- dv.setUint32(off + 4, Math.floor(ptr / 0x100000000), true);
38
- }
39
36
  function openFileDialog(owner, filter, title, multiple) {
40
37
  if (!ensureDlls())
41
38
  return null;
42
- const structBuf = new ArrayBuffer(152);
43
- const sv = new DataView(structBuf);
44
39
  const fileBuf = new ArrayBuffer(260 * 2);
45
40
  const filterWide = strToWide(filter);
46
41
  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);
52
42
  let flags = 0x1000 | 0x0800 | 0x0008;
53
43
  if (multiple)
54
44
  flags |= 0x0200;
55
45
  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);
46
+ const ofn = OPENFILENAMEW.write({
47
+ lStructSize: OPENFILENAMEW.size,
48
+ hwndOwner: owner,
49
+ hInstance: 0,
50
+ lpstrFilter: ffi.bufferPtr(filterWide),
51
+ lpstrCustomFilter: 0,
52
+ nMaxCustFilter: 0,
53
+ nFilterIndex: 0,
54
+ lpstrFile: ffi.bufferPtr(fileBuf),
55
+ nMaxFile: 260,
56
+ lpstrFileTitle: 0,
57
+ nMaxFileTitle: 0,
58
+ lpstrInitialDir: 0,
59
+ lpstrTitle: titleWide ? ffi.bufferPtr(titleWide) : 0,
60
+ 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
+ });
71
+ const ret = ffi.ffiCall(_GetOpenFileNameW, [FFI_PTR], [ofn], FFI_U32);
60
72
  if (!ret)
61
73
  return null;
62
74
  if (!multiple) {
@@ -79,19 +91,23 @@ function openFileDialog(owner, filter, title, multiple) {
79
91
  function openFolderDialog(owner, title) {
80
92
  if (!ensureDlls())
81
93
  return null;
82
- const structBuf = new ArrayBuffer(64);
83
- const sv = new DataView(structBuf);
84
94
  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);
95
+ const bi = BROWSEINFOW.write({
96
+ hwndOwner: owner,
97
+ pidlRoot: 0,
98
+ pszDisplayName: 0,
99
+ lpszTitle: titleWide ? ffi.bufferPtr(titleWide) : 0,
100
+ ulFlags: 0x00000041,
101
+ lpfn: 0,
102
+ lParam: 0,
103
+ iImage: 0,
104
+ });
105
+ const pidl = ffi.ffiCall(_SHBrowseForFolderW, [FFI_PTR], [bi], FFI_PTR);
90
106
  if (!pidl)
91
107
  return null;
92
108
  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);
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);
95
111
  return ok ? wideToStr(pathBuf) : null;
96
112
  }
97
113
  export const PathPicker = forwardRef((props, ref) => {