quickwin 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.en.md +9 -9
  2. package/README.md +9 -9
  3. package/constants.ts +1063 -0
  4. package/enum_map.d.ts +129 -0
  5. package/examples/PdfCanvas.js +2 -2
  6. package/examples/PdfCanvas.tsx +3 -2
  7. package/examples/{exec_server.js → execd/main.js} +1114 -287
  8. package/examples/execd/tray.js +192 -0
  9. package/examples/execd/worker.js +122 -0
  10. package/examples/pdf_preview2.js +46 -46
  11. package/examples/pdf_preview2.ts +55 -54
  12. package/examples/pdf_viewer.js +134 -130
  13. package/examples/pdf_viewer.tsx +6 -5
  14. package/examples/test_gallery.js +135 -131
  15. package/examples/test_gallery.tsx +9 -8
  16. package/examples/test_react_lazy.js +130 -128
  17. package/examples/test_react_lazy.tsx +7 -6
  18. package/examples/test_react_listbox.js +130 -128
  19. package/examples/test_react_listbox.tsx +8 -7
  20. package/examples/test_root_window.js +130 -128
  21. package/examples/test_root_window.tsx +5 -5
  22. package/examples/test_tab.js +130 -128
  23. package/examples/test_tab.tsx +7 -6
  24. package/examples/tray_demo.js +8 -8
  25. package/examples/tray_demo.tsx +9 -8
  26. package/lib/dumpRects.js +2 -2
  27. package/lib/dumpRects.ts +3 -2
  28. package/lib/fetch.js +15 -15
  29. package/lib/fetch.ts +16 -15
  30. package/lib/ffi/bind.js +3 -3
  31. package/lib/ffi/bind.ts +3 -3
  32. package/lib/ffi/ctype.ts +3 -1
  33. package/lib/ffi/struct.js +23 -7
  34. package/lib/ffi/struct.ts +50 -23
  35. package/lib/http-server.js +5 -5
  36. package/lib/http-server.ts +6 -5
  37. package/lib/react-qw/components/Button.js +2 -2
  38. package/lib/react-qw/components/CheckBox.js +4 -4
  39. package/lib/react-qw/components/ComboBox.js +10 -10
  40. package/lib/react-qw/components/DateTimePicker.js +12 -12
  41. package/lib/react-qw/components/Input.js +7 -7
  42. package/lib/react-qw/components/Link.js +4 -4
  43. package/lib/react-qw/components/ListBox.js +12 -12
  44. package/lib/react-qw/components/ListView.js +35 -35
  45. package/lib/react-qw/components/PathPicker.js +3 -3
  46. package/lib/react-qw/components/ProgressBar.js +4 -4
  47. package/lib/react-qw/components/RadioButton.js +4 -4
  48. package/lib/react-qw/components/ScrollView.js +28 -28
  49. package/lib/react-qw/components/Slider.js +11 -11
  50. package/lib/react-qw/components/Tab.js +10 -10
  51. package/lib/react-qw/components/Tooltip.js +8 -8
  52. package/lib/react-qw/components/TreeView.js +20 -20
  53. package/lib/react-qw/index.js +5 -5
  54. package/lib/react-qw/index.ts +6 -5
  55. package/lib/react-qw/props.js +4 -4
  56. package/lib/react-qw/props.ts +5 -4
  57. package/lib/react-qw/reconciler.js +10 -10
  58. package/lib/react-qw/reconciler.ts +11 -10
  59. package/lib/text-measure.js +3 -3
  60. package/lib/text-measure.ts +4 -3
  61. package/lib/websocket.js +17 -17
  62. package/lib/websocket.ts +18 -17
  63. package/lib/windows/advapi32.js +7 -7
  64. package/lib/windows/advapi32.ts +7 -7
  65. package/lib/windows/gdi32.js +17 -17
  66. package/lib/windows/gdi32.ts +17 -17
  67. package/lib/windows/kernel32.js +41 -2
  68. package/lib/windows/kernel32.ts +45 -2
  69. package/lib/windows/shell32.js +24 -3
  70. package/lib/windows/shell32.ts +27 -4
  71. package/lib/windows/structs.js +230 -92
  72. package/lib/windows/structs.ts +278 -92
  73. package/lib/windows/user32.js +18 -6
  74. package/lib/windows/user32.ts +18 -6
  75. package/lib/windows/winspool.js +3 -3
  76. package/lib/windows/winspool.ts +3 -3
  77. package/package.json +4 -3
  78. package/quickwin.d.ts +32 -10
  79. package/qwin-nowasm-x86.exe +0 -0
  80. package/qwin-nowasm.exe +0 -0
  81. package/qwin-x86.exe +0 -0
  82. package/qwin.exe +0 -0
  83. package/test/test_ffi_bind.js +12 -10
  84. package/test/test_ffi_bind.ts +14 -12
  85. package/test/test_ffi_struct.js +60 -26
  86. package/test/test_ffi_struct.ts +58 -28
  87. package/test/test_ffi_windows.js +1 -1
  88. package/test/test_ffi_windows.ts +1 -1
  89. package/test/test_files.js +7 -7
  90. package/test/test_files.ts +8 -8
  91. package/test/test_gdi_draw.js +51 -51
  92. package/test/test_gdi_draw.ts +57 -57
  93. package/test/test_http_server.js +3 -3
  94. package/test/test_http_server.ts +4 -3
  95. package/test/test_ipv6.js +3 -3
  96. package/test/test_ipv6.ts +4 -3
  97. package/test/test_net_event.js +3 -3
  98. package/test/test_net_event.ts +4 -3
  99. package/test/test_printer.js +4 -4
  100. package/test/test_printer.ts +5 -5
  101. package/test/test_react_complex.js +132 -130
  102. package/test/test_react_counter.js +130 -128
  103. package/test/test_react_flex.js +132 -130
  104. package/test/test_react_hidden.js +130 -128
  105. package/test/test_react_listview_link.js +131 -129
  106. package/test/test_react_notify.js +148 -146
  107. package/test/test_react_render.js +130 -128
  108. package/test/test_registry.js +6 -6
  109. package/test/test_registry.ts +8 -8
  110. package/test/test_wolfssl_read.js +8 -8
  111. package/test/test_wolfssl_read.ts +9 -8
  112. package/examples/exec_server.tsx +0 -488
  113. package/examples/exec_server_worker.js +0 -395
  114. package/examples/exec_server_worker.ts +0 -417
  115. package/examples/setres.js +0 -79
  116. package/examples/setres.ts +0 -86
  117. package/quickwin_const.d.ts +0 -1037
@@ -13,7 +13,8 @@ const RECT = /* @__PURE__ */ struct('RECT', {
13
13
  right: 'i32',
14
14
  bottom: 'i32',
15
15
  });
16
- const TVITEM = /* @__PURE__ */ struct('TVITEM', {
16
+ const TVITEM_DECL = {
17
+ '#': 'struct',
17
18
  mask: 'u32',
18
19
  hItem: '<>ptr',
19
20
  state: 'u32',
@@ -24,11 +25,12 @@ const TVITEM = /* @__PURE__ */ struct('TVITEM', {
24
25
  iSelectedImage: 'i32',
25
26
  cChildren: 'i32',
26
27
  lParam: '<>ptr',
27
- });
28
+ };
29
+ const TVITEM = /* @__PURE__ */ struct('TVITEM', TVITEM_DECL);
28
30
  const TVINSERTSTRUCT = /* @__PURE__ */ struct('TVINSERTSTRUCT', {
29
31
  hParent: '<>ptr',
30
32
  hInsertAfter: '<>ptr',
31
- item: TVITEM.__struct,
33
+ item: TVITEM_DECL,
32
34
  });
33
35
  export const suite = {
34
36
  name: 'ffi-struct',
@@ -228,13 +230,15 @@ export const suite = {
228
230
  t.check('length 1', 1, one.v.length);
229
231
  t.check('[0]', 42, one.v[0]);
230
232
  t.section('array of struct roundtrip (stride = child size)');
231
- const PT = struct('PT', {
233
+ const PT_DECL = {
234
+ '#': 'struct',
232
235
  x: 'i32',
233
236
  y: 'i32',
234
- });
237
+ };
238
+ const PT = /* @__PURE__ */ struct('PT', PT_DECL);
235
239
  const POLY = struct('POLY', {
236
240
  count: 'u32',
237
- pts: { '#': 'array', element: PT.__struct, length: 3 },
241
+ pts: { '#': 'array', element: PT_DECL, length: 3 },
238
242
  });
239
243
  t.check('POLY.size == 28', 28, POLY.size);
240
244
  t.check('POLY.offsetOf pts', 4, POLY.offsetOf('pts'));
@@ -274,9 +278,28 @@ export const suite = {
274
278
  rejectedMap = true;
275
279
  }
276
280
  t.check('bare nested map rejected(漏写 #)', true, rejectedMap);
277
- // 顶层 '#' 由构造器写入:字段表混入的 '#' 指令键被剔除(运行时兜底,类型层已禁)
278
- const H = struct({ '#': 'union', a: 'u8', b: 'u32' });
279
- t.check('top-level # stripped(仍按函数名 struct 布局)', 8, H.size);
281
+ // 顶层 '#' 与函数名 kind 不符 → throw(union DECL 误传 struct() 防线);
282
+ // 一致('#': 'struct' 的 DECL 直传形态)→ 按 kind 正常布局
283
+ let rejectedKind = false;
284
+ try {
285
+ struct({ '#': 'union', a: 'u8', b: 'u32' });
286
+ }
287
+ catch {
288
+ rejectedKind = true;
289
+ }
290
+ t.check('top-level # kind mismatch rejected', true, rejectedKind);
291
+ const H = struct('H', { '#': 'struct', a: 'u8', b: 'u32' });
292
+ t.check('top-level # matching kind accepted(DECL 直传)', 8, H.size);
293
+ // 类型层 kind 锁(参数交叉 `{ '#'?: 'struct' }` / `{ '#'?: 'union' }`):DECL 的
294
+ // '#' 字面与函数名 kind 不符 → 交叉 never,编译期报错。@ts-expect-error 断言该
295
+ // 报错存在(锁失效则指令未使用、tsc 报错);仅类型期检查,函数不调用
296
+ const _kindLock = () => {
297
+ // @ts-expect-error —— union DECL 传 struct()
298
+ struct('BAD_U', { '#': 'union', a: 'u8', b: 'u32' });
299
+ // @ts-expect-error —— struct DECL 传 union()
300
+ union('BAD_S', { '#': 'struct', a: 'u8', b: 'u32' });
301
+ };
302
+ void _kindLock;
280
303
  t.section("pointer member '<>ptr' raw vs '<T>ptr' branded");
281
304
  const PTR = struct('PTR', {
282
305
  raw: '<>ptr',
@@ -376,16 +399,18 @@ export const suite = {
376
399
  t.check('BROWSEINFOW.offsetOf lpszTitle', is64 ? 24 : 12, BROWSEINFOW.offsetOf('lpszTitle'));
377
400
  t.check('BROWSEINFOW.offsetOf ulFlags', is64 ? 32 : 16, BROWSEINFOW.offsetOf('ulFlags'));
378
401
  t.section('NMHDR(嵌套时 MSVC 尾 padding 传染)');
379
- const NMHDR = struct('NMHDR', {
402
+ const NMHDR_DECL = {
403
+ '#': 'struct',
380
404
  hwndFrom: '<>ptr',
381
405
  idFrom: '<>ptr',
382
406
  code: 'i32',
383
- });
407
+ };
408
+ const NMHDR = /* @__PURE__ */ struct('NMHDR', NMHDR_DECL);
384
409
  t.check('NMHDR.size', is64 ? 24 : 12, NMHDR.size);
385
410
  t.check('NMHDR.offsetOf code', is64 ? 16 : 8, NMHDR.offsetOf('code'));
386
411
  t.section('NMCUSTOMDRAW / NMLVCUSTOMDRAW (ListView custom draw)');
387
412
  const NMCUSTOMDRAW = struct('NMCUSTOMDRAW', {
388
- hdr: NMHDR.__struct,
413
+ hdr: NMHDR_DECL,
389
414
  dwDrawStage: 'u32',
390
415
  hdc: '<>ptr',
391
416
  rc: 'i32[4]',
@@ -398,7 +423,7 @@ export const suite = {
398
423
  t.check('NMCUSTOMDRAW.offsetOf hdc', is64 ? 32 : 16, NMCUSTOMDRAW.offsetOf('hdc'));
399
424
  t.check('NMCUSTOMDRAW.offsetOf dwItemSpec', is64 ? 56 : 36, NMCUSTOMDRAW.offsetOf('dwItemSpec'));
400
425
  const NMLVCUSTOMDRAW = struct('NMLVCUSTOMDRAW', {
401
- hdr: NMHDR.__struct,
426
+ hdr: NMHDR_DECL,
402
427
  dwDrawStage: 'u32',
403
428
  hdc: '<>ptr',
404
429
  rc: 'i32[4]',
@@ -416,7 +441,7 @@ export const suite = {
416
441
  t.check('NMLVCUSTOMDRAW.offsetOf iSubItem', is64 ? 88 : 56, NMLVCUSTOMDRAW.offsetOf('iSubItem'));
417
442
  t.section('NMLISTVIEW / NMITEMACTIVATE');
418
443
  const NMLISTVIEW = struct('NMLISTVIEW', {
419
- hdr: NMHDR.__struct,
444
+ hdr: NMHDR_DECL,
420
445
  iItem: 'i32',
421
446
  iSubItem: 'i32',
422
447
  uNewState: 'u32',
@@ -471,7 +496,8 @@ export const suite = {
471
496
  t.check('LVCOLUMNW.offsetOf iSubItem', is64 ? 28 : 20, LVCOLUMNW.offsetOf('iSubItem'));
472
497
  t.check('LVCOLUMNW.offsetOf iOrder', is64 ? 36 : 28, LVCOLUMNW.offsetOf('iOrder'));
473
498
  t.section('NMDATETIMECHANGE');
474
- const SYSTEMTIME = struct('SYSTEMTIME', {
499
+ const SYSTEMTIME_DECL = {
500
+ '#': 'struct',
475
501
  wYear: 'u16',
476
502
  wMonth: 'u16',
477
503
  wDayOfWeek: 'u16',
@@ -480,26 +506,28 @@ export const suite = {
480
506
  wMinute: 'u16',
481
507
  wSecond: 'u16',
482
508
  wMilliseconds: 'u16',
483
- });
509
+ };
484
510
  const NMDATETIMECHANGE = struct('NMDATETIMECHANGE', {
485
- hdr: NMHDR.__struct,
511
+ hdr: NMHDR_DECL,
486
512
  dwFlags: 'u32',
487
- st: SYSTEMTIME.__struct,
513
+ st: SYSTEMTIME_DECL,
488
514
  });
489
515
  t.check('NMDATETIMECHANGE.size', is64 ? 48 : 32, NMDATETIMECHANGE.size);
490
516
  t.check('NMDATETIMECHANGE.offsetOf dwFlags', is64 ? 24 : 12, NMDATETIMECHANGE.offsetOf('dwFlags'));
491
517
  t.check('NMDATETIMECHANGE.offsetOf st', is64 ? 28 : 16, NMDATETIMECHANGE.offsetOf('st'));
492
518
  t.section('NMLINK szUrl offset');
493
- const LITEM = struct('LITEM', {
519
+ const LITEM_DECL = {
520
+ '#': 'struct',
494
521
  mask: 'u32',
495
522
  iLink: 'i32',
496
523
  state: 'u32',
497
524
  stateMask: 'u32',
498
525
  szID: 'u16[48]@utf-16le',
499
- });
526
+ };
527
+ const LITEM = /* @__PURE__ */ struct('LITEM', LITEM_DECL);
500
528
  const NMLINK = struct('NMLINK', {
501
- hdr: NMHDR.__struct,
502
- item: LITEM.__struct,
529
+ hdr: NMHDR_DECL,
530
+ item: LITEM_DECL,
503
531
  szUrl: 'u16[2084]@utf-16le',
504
532
  });
505
533
  t.check('LITEM.offsetOf szID', 16, LITEM.offsetOf('szID'));
@@ -696,14 +724,14 @@ export const suite = {
696
724
  t.check('数字位 @ 字段归一往返', 4, dev.key);
697
725
  // encode 必填判定:0 ∉ 值域的 @ 字段必填(省略 = 新槽写 0 = 静默违约);
698
726
  // 0 ∈ 值域可省。下面 { key } 行能编译即 msg/hp 可省的断言。
699
- EV.encode({ key: 4 /* gui.MouseKeyFlag.MK_SHIFT */ });
727
+ EV.encode({ key: 4 /* MouseKeyFlag.MK_SHIFT */ });
700
728
  // @ts-expect-error key 必填:省略即编译错(0 ∉ MouseKeyFlag)
701
729
  EV.encode({ msg: 0 });
702
730
  if (false) {
703
731
  // @ts-expect-error 域外字面量(3 不是任何 MK_ 标志)
704
732
  EV.encode({ key: 3 });
705
733
  // @ts-expect-error 跨枚举误用(WmMsg 成员不是 MouseKeyFlag)
706
- EV.encode({ key: 1 /* gui.WmMsg.CREATE */ });
734
+ EV.encode({ key: 1 /* WmMsg.CREATE */ });
707
735
  }
708
736
  // 数组糖元素带 @:lower 递归归一(normToken 剥 @)+ 元素值域
709
737
  const EA = struct('EA', { msgs: 'u32@WmMsg[4]' });
@@ -715,8 +743,14 @@ export const suite = {
715
743
  t.section('EncodeIn DFS 后序:子树含必填 ⇒ 键必填;值后序合成(非必填兄弟可省)');
716
744
  {
717
745
  // 嵌套子结构含 '!'(内层强制)+ 标量/字符串糖(内层可省:''/0 是合法零值)
718
- const NSUB = struct('NSUB', { hp: '<X>ptr!', rest: 'u32', s: 'u16[4]@utf-8' });
719
- const NOUT = struct('NOUT', { top: 'u32', sub: NSUB.__struct });
746
+ const NSUB_DECL = {
747
+ '#': 'struct',
748
+ hp: '<X>ptr!',
749
+ rest: 'u32',
750
+ s: 'u16[4]@utf-8',
751
+ };
752
+ const NSUB = /* @__PURE__ */ struct('NSUB', NSUB_DECL);
753
+ const NOUT = struct('NOUT', { top: 'u32', sub: NSUB_DECL });
720
754
  // decode 造全量对象(hp 槽预填非零,绕 '!' 的 decode 0 断言)
721
755
  const nbuf = new PtrArrayBuffer(NOUT.size);
722
756
  new DataView(nbuf).setUint32(NOUT.offsetOf('sub') + NSUB.offsetOf('hp'), 0x1234, true);
@@ -1,10 +1,10 @@
1
1
  import * as os from 'os'
2
- import * as gui from 'gui'
3
2
  import { Tester } from './test_helper.js'
4
3
  import { struct, union } from '../lib/ffi/struct.js'
5
4
  import { bind, } from '../lib/ffi/bind.js'
6
5
  import { type MaybePtr, Ptr, PtrArrayBuffer } from '../lib/ffi/ctype.js'
7
- import type { C_Union } from '../lib/ffi/ctype.js'
6
+ import type { C_Struct, C_Union } from '../lib/ffi/ctype.js'
7
+ import { MouseKeyFlag, WmMsg } from '../constants'
8
8
 
9
9
  // 编译期断言工具(仅类型层,运行时无开销)
10
10
  type Equal<A, B> = (<G>() => G extends A ? 1 : 2) extends (<G>() => G extends B ? 1 : 2) ? true : false
@@ -22,7 +22,8 @@ const RECT = /* @__PURE__ */ struct('RECT', {
22
22
  bottom: 'i32',
23
23
  })
24
24
 
25
- const TVITEM = /* @__PURE__ */ struct('TVITEM', {
25
+ const TVITEM_DECL = {
26
+ '#': 'struct',
26
27
  mask: 'u32',
27
28
  hItem: '<>ptr',
28
29
  state: 'u32',
@@ -33,11 +34,12 @@ const TVITEM = /* @__PURE__ */ struct('TVITEM', {
33
34
  iSelectedImage: 'i32',
34
35
  cChildren: 'i32',
35
36
  lParam: '<>ptr',
36
- })
37
+ } satisfies C_Struct
38
+ const TVITEM = /* @__PURE__ */ struct('TVITEM', TVITEM_DECL)
37
39
  const TVINSERTSTRUCT = /* @__PURE__ */ struct('TVINSERTSTRUCT', {
38
40
  hParent: '<>ptr',
39
41
  hInsertAfter: '<>ptr',
40
- item: TVITEM.__struct,
42
+ item: TVITEM_DECL,
41
43
  })
42
44
 
43
45
  export const suite = {
@@ -260,13 +262,15 @@ export const suite = {
260
262
  t.check('[0]', 42, one.v[0])
261
263
 
262
264
  t.section('array of struct roundtrip (stride = child size)')
263
- const PT = struct('PT', {
265
+ const PT_DECL = {
266
+ '#': 'struct',
264
267
  x: 'i32',
265
268
  y: 'i32',
266
- })
269
+ } satisfies C_Struct
270
+ const PT = /* @__PURE__ */ struct('PT', PT_DECL)
267
271
  const POLY = struct('POLY', {
268
272
  count: 'u32',
269
- pts: { '#': 'array', element: PT.__struct, length: 3 },
273
+ pts: { '#': 'array', element: PT_DECL, length: 3 },
270
274
  })
271
275
  t.check('POLY.size == 28', 28, POLY.size)
272
276
  t.check('POLY.offsetOf pts', 4, POLY.offsetOf('pts'))
@@ -296,9 +300,24 @@ export const suite = {
296
300
  try { (struct as any)({ outer: { inner: 'u32' } }) } catch { rejectedMap = true }
297
301
  t.check('bare nested map rejected(漏写 #)', true, rejectedMap)
298
302
 
299
- // 顶层 '#' 由构造器写入:字段表混入的 '#' 指令键被剔除(运行时兜底,类型层已禁)
300
- const H = (struct as any)({ '#': 'union', a: 'u8', b: 'u32' })
301
- t.check('top-level # stripped(仍按函数名 struct 布局)', 8, H.size)
303
+ // 顶层 '#' 与函数名 kind 不符 → throw(union DECL 误传 struct() 防线);
304
+ // 一致('#': 'struct' 的 DECL 直传形态)→ 按 kind 正常布局
305
+ let rejectedKind = false
306
+ try { (struct as any)({ '#': 'union', a: 'u8', b: 'u32' }) } catch { rejectedKind = true }
307
+ t.check('top-level # kind mismatch rejected', true, rejectedKind)
308
+ const H = (struct as any)('H', { '#': 'struct', a: 'u8', b: 'u32' })
309
+ t.check('top-level # matching kind accepted(DECL 直传)', 8, H.size)
310
+
311
+ // 类型层 kind 锁(参数交叉 `{ '#'?: 'struct' }` / `{ '#'?: 'union' }`):DECL 的
312
+ // '#' 字面与函数名 kind 不符 → 交叉 never,编译期报错。@ts-expect-error 断言该
313
+ // 报错存在(锁失效则指令未使用、tsc 报错);仅类型期检查,函数不调用
314
+ const _kindLock = () => {
315
+ // @ts-expect-error —— union DECL 传 struct()
316
+ struct('BAD_U', { '#': 'union', a: 'u8', b: 'u32' })
317
+ // @ts-expect-error —— struct DECL 传 union()
318
+ union('BAD_S', { '#': 'struct', a: 'u8', b: 'u32' })
319
+ }
320
+ void _kindLock
302
321
 
303
322
  t.section("pointer member '<>ptr' raw vs '<T>ptr' branded")
304
323
  const PTR = struct('PTR', {
@@ -405,17 +424,19 @@ export const suite = {
405
424
  t.check('BROWSEINFOW.offsetOf ulFlags', is64 ? 32 : 16, BROWSEINFOW.offsetOf('ulFlags'))
406
425
 
407
426
  t.section('NMHDR(嵌套时 MSVC 尾 padding 传染)')
408
- const NMHDR = struct('NMHDR', {
427
+ const NMHDR_DECL = {
428
+ '#': 'struct',
409
429
  hwndFrom: '<>ptr',
410
430
  idFrom: '<>ptr',
411
431
  code: 'i32',
412
- })
432
+ } satisfies C_Struct
433
+ const NMHDR = /* @__PURE__ */ struct('NMHDR', NMHDR_DECL)
413
434
  t.check('NMHDR.size', is64 ? 24 : 12, NMHDR.size)
414
435
  t.check('NMHDR.offsetOf code', is64 ? 16 : 8, NMHDR.offsetOf('code'))
415
436
 
416
437
  t.section('NMCUSTOMDRAW / NMLVCUSTOMDRAW (ListView custom draw)')
417
438
  const NMCUSTOMDRAW = struct('NMCUSTOMDRAW', {
418
- hdr: NMHDR.__struct,
439
+ hdr: NMHDR_DECL,
419
440
  dwDrawStage: 'u32',
420
441
  hdc: '<>ptr',
421
442
  rc: 'i32[4]',
@@ -428,7 +449,7 @@ export const suite = {
428
449
  t.check('NMCUSTOMDRAW.offsetOf hdc', is64 ? 32 : 16, NMCUSTOMDRAW.offsetOf('hdc'))
429
450
  t.check('NMCUSTOMDRAW.offsetOf dwItemSpec', is64 ? 56 : 36, NMCUSTOMDRAW.offsetOf('dwItemSpec'))
430
451
  const NMLVCUSTOMDRAW = struct('NMLVCUSTOMDRAW', {
431
- hdr: NMHDR.__struct,
452
+ hdr: NMHDR_DECL,
432
453
  dwDrawStage: 'u32',
433
454
  hdc: '<>ptr',
434
455
  rc: 'i32[4]',
@@ -447,7 +468,7 @@ export const suite = {
447
468
 
448
469
  t.section('NMLISTVIEW / NMITEMACTIVATE')
449
470
  const NMLISTVIEW = struct('NMLISTVIEW', {
450
- hdr: NMHDR.__struct,
471
+ hdr: NMHDR_DECL,
451
472
  iItem: 'i32',
452
473
  iSubItem: 'i32',
453
474
  uNewState: 'u32',
@@ -504,7 +525,8 @@ export const suite = {
504
525
  t.check('LVCOLUMNW.offsetOf iOrder', is64 ? 36 : 28, LVCOLUMNW.offsetOf('iOrder'))
505
526
 
506
527
  t.section('NMDATETIMECHANGE')
507
- const SYSTEMTIME = struct('SYSTEMTIME', {
528
+ const SYSTEMTIME_DECL = {
529
+ '#': 'struct',
508
530
  wYear: 'u16',
509
531
  wMonth: 'u16',
510
532
  wDayOfWeek: 'u16',
@@ -513,27 +535,29 @@ export const suite = {
513
535
  wMinute: 'u16',
514
536
  wSecond: 'u16',
515
537
  wMilliseconds: 'u16',
516
- })
538
+ } satisfies C_Struct
517
539
  const NMDATETIMECHANGE = struct('NMDATETIMECHANGE', {
518
- hdr: NMHDR.__struct,
540
+ hdr: NMHDR_DECL,
519
541
  dwFlags: 'u32',
520
- st: SYSTEMTIME.__struct,
542
+ st: SYSTEMTIME_DECL,
521
543
  })
522
544
  t.check('NMDATETIMECHANGE.size', is64 ? 48 : 32, NMDATETIMECHANGE.size)
523
545
  t.check('NMDATETIMECHANGE.offsetOf dwFlags', is64 ? 24 : 12, NMDATETIMECHANGE.offsetOf('dwFlags'))
524
546
  t.check('NMDATETIMECHANGE.offsetOf st', is64 ? 28 : 16, NMDATETIMECHANGE.offsetOf('st'))
525
547
 
526
548
  t.section('NMLINK szUrl offset')
527
- const LITEM = struct('LITEM', {
549
+ const LITEM_DECL = {
550
+ '#': 'struct',
528
551
  mask: 'u32',
529
552
  iLink: 'i32',
530
553
  state: 'u32',
531
554
  stateMask: 'u32',
532
555
  szID: 'u16[48]@utf-16le',
533
- })
556
+ } satisfies C_Struct
557
+ const LITEM = /* @__PURE__ */ struct('LITEM', LITEM_DECL)
534
558
  const NMLINK = struct('NMLINK', {
535
- hdr: NMHDR.__struct,
536
- item: LITEM.__struct,
559
+ hdr: NMHDR_DECL,
560
+ item: LITEM_DECL,
537
561
  szUrl: 'u16[2084]@utf-16le',
538
562
  })
539
563
  t.check('LITEM.offsetOf szID', 16, LITEM.offsetOf('szID'))
@@ -745,14 +769,14 @@ export const suite = {
745
769
 
746
770
  // encode 必填判定:0 ∉ 值域的 @ 字段必填(省略 = 新槽写 0 = 静默违约);
747
771
  // 0 ∈ 值域可省。下面 { key } 行能编译即 msg/hp 可省的断言。
748
- EV.encode({ key: gui.MouseKeyFlag.MK_SHIFT })
772
+ EV.encode({ key: MouseKeyFlag.MK_SHIFT })
749
773
  // @ts-expect-error key 必填:省略即编译错(0 ∉ MouseKeyFlag)
750
774
  EV.encode({ msg: 0 })
751
775
  if (false) {
752
776
  // @ts-expect-error 域外字面量(3 不是任何 MK_ 标志)
753
777
  EV.encode({ key: 3 })
754
778
  // @ts-expect-error 跨枚举误用(WmMsg 成员不是 MouseKeyFlag)
755
- EV.encode({ key: gui.WmMsg.CREATE })
779
+ EV.encode({ key: WmMsg.CREATE })
756
780
  }
757
781
 
758
782
  // 数组糖元素带 @:lower 递归归一(normToken 剥 @)+ 元素值域
@@ -766,8 +790,14 @@ export const suite = {
766
790
  t.section('EncodeIn DFS 后序:子树含必填 ⇒ 键必填;值后序合成(非必填兄弟可省)')
767
791
  {
768
792
  // 嵌套子结构含 '!'(内层强制)+ 标量/字符串糖(内层可省:''/0 是合法零值)
769
- const NSUB = struct('NSUB', { hp: '<X>ptr!', rest: 'u32', s: 'u16[4]@utf-8' })
770
- const NOUT = struct('NOUT', { top: 'u32', sub: NSUB.__struct })
793
+ const NSUB_DECL = {
794
+ '#': 'struct',
795
+ hp: '<X>ptr!',
796
+ rest: 'u32',
797
+ s: 'u16[4]@utf-8',
798
+ } satisfies C_Struct
799
+ const NSUB = /* @__PURE__ */ struct('NSUB', NSUB_DECL)
800
+ const NOUT = struct('NOUT', { top: 'u32', sub: NSUB_DECL })
771
801
  // decode 造全量对象(hp 槽预填非零,绕 '!' 的 decode 0 断言)
772
802
  const nbuf = new PtrArrayBuffer(NOUT.size)
773
803
  new DataView(nbuf).setUint32(NOUT.offsetOf('sub') + NSUB.offsetOf('hp'), 0x1234, true)
@@ -74,7 +74,7 @@ export const suite = {
74
74
  if (himl !== 0)
75
75
  t.checkTrue('ImageListDestroy(himl) 成功', ImageListDestroy(himl) !== 0);
76
76
  t.section('user32: LoadImage 双变体(string 自动编码 / ordinal MAKEINTRESOURCE)');
77
- // Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 gui.ImageType d.ts 当前值与此不一致)
77
+ // Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 ImageType d.ts 当前值与此不一致)
78
78
  t.checkTrue('LoadImageOrdinal(NULL, IDI_APPLICATION, IMAGE_ICON, LR_SHARED) 非 0', LoadImageOrdinal(NULL, 32512, 2, 0, 0, 0x8000) !== 0);
79
79
  t.check('LoadImage(LOADFROMFILE, 不存在文件) → 0', 0, LoadImage(NULL, 'qw_no_such_icon_xyz.ico', 2, 0, 0, 0x10));
80
80
  t.section('user32: 滚动条(入参对象直传 / 出参 encode → call → decode)');
@@ -83,7 +83,7 @@ export const suite = {
83
83
  if (himl !== 0) t.checkTrue('ImageListDestroy(himl) 成功', ImageListDestroy(himl) !== 0)
84
84
 
85
85
  t.section('user32: LoadImage 双变体(string 自动编码 / ordinal MAKEINTRESOURCE)')
86
- // Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 gui.ImageType d.ts 当前值与此不一致)
86
+ // Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 ImageType d.ts 当前值与此不一致)
87
87
  t.checkTrue('LoadImageOrdinal(NULL, IDI_APPLICATION, IMAGE_ICON, LR_SHARED) 非 0',
88
88
  LoadImageOrdinal(NULL, 32512, 2, 0, 0, 0x8000) !== 0)
89
89
  t.check('LoadImage(LOADFROMFILE, 不存在文件) → 0', 0,
@@ -40,7 +40,7 @@ export const suite = {
40
40
  const pathC = `${dir}\\c.txt`;
41
41
  const pathCn = `${dir}\\中文文件.txt`;
42
42
  t.section('CreateFile + WriteFile: write payload');
43
- const hW = CreateFile(pathA, GENERIC_WRITE, 0, NULL, 2 /* gui.FileCreation.CREATE_ALWAYS */, 0x80 /* FILE_ATTRIBUTE_NORMAL */, NULL);
43
+ const hW = CreateFile(pathA, GENERIC_WRITE, 0, NULL, 2 /* FileCreation.CREATE_ALWAYS */, 0x80 /* FILE_ATTRIBUTE_NORMAL */, NULL);
44
44
  t.checkTrue(`write handle valid (${hW})`, hW !== -1); // 失败 = INVALID_HANDLE_VALUE(-1)
45
45
  const payload = 'hello files';
46
46
  const wData = toWide(payload);
@@ -49,34 +49,34 @@ export const suite = {
49
49
  t.check('bytes written', payload.length * 2, new DataView(pWr).getUint32(0, true));
50
50
  t.checkTrue('CloseHandle (writer)', CloseHandle(hW) !== 0);
51
51
  t.section('CreateFile(READ) + GetFileSize + SetFilePointer + ReadFile');
52
- const hR = CreateFile(pathA, GENERIC_READ, 0, NULL, 3 /* gui.FileCreation.OPEN_EXISTING */, 0x80, NULL);
52
+ const hR = CreateFile(pathA, GENERIC_READ, 0, NULL, 3 /* FileCreation.OPEN_EXISTING */, 0x80, NULL);
53
53
  t.checkTrue(`read handle valid (${hR})`, hR !== -1);
54
54
  t.check('GetFileSize', payload.length * 2, GetFileSize(hR, NULL));
55
- t.check('SetFilePointer BEGIN → 0', 0, SetFilePointer(hR, 0, NULL, 0 /* gui.FileSeekFrom.BEGIN */));
55
+ t.check('SetFilePointer BEGIN → 0', 0, SetFilePointer(hR, 0, NULL, 0 /* FileSeekFrom.BEGIN */));
56
56
  const rData = new PtrArrayBuffer(payload.length * 2);
57
57
  const pRd = new PtrArrayBuffer(4);
58
58
  t.checkTrue('ReadFile ok', ReadFile(hR, rData, payload.length * 2, pRd, NULL) !== 0);
59
59
  t.check('read roundtrip', payload, fromWide(rData, payload.length));
60
60
  // seek 到 END-4 验证负偏移 + 读尾 4 字节
61
- t.check('SetFilePointer END-4', payload.length * 2 - 4, SetFilePointer(hR, -4, NULL, 2 /* gui.FileSeekFrom.END */));
61
+ t.check('SetFilePointer END-4', payload.length * 2 - 4, SetFilePointer(hR, -4, NULL, 2 /* FileSeekFrom.END */));
62
62
  const rTail = new PtrArrayBuffer(8);
63
63
  ReadFile(hR, rTail, 4, pRd, NULL);
64
64
  t.check('seeked tail', payload.slice(-2), fromWide(rTail, 2));
65
65
  CloseHandle(hR);
66
66
  t.section('CopyFile + MoveFileEx + GetFileAttributes');
67
- const hB = CreateFile(pathB, GENERIC_WRITE, 0, NULL, 2 /* gui.FileCreation.CREATE_ALWAYS */, 0x80, NULL);
67
+ const hB = CreateFile(pathB, GENERIC_WRITE, 0, NULL, 2 /* FileCreation.CREATE_ALWAYS */, 0x80, NULL);
68
68
  const bData = toWide('bb');
69
69
  WriteFile(hB, bData, 4, pWr, NULL);
70
70
  CloseHandle(hB);
71
71
  t.checkTrue('CopyFile ok', CopyFile(pathB, pathC, 0) !== 0);
72
- const hC = CreateFile(pathC, GENERIC_READ, 0, NULL, 3 /* gui.FileCreation.OPEN_EXISTING */, 0x80, NULL);
72
+ const hC = CreateFile(pathC, GENERIC_READ, 0, NULL, 3 /* FileCreation.OPEN_EXISTING */, 0x80, NULL);
73
73
  t.check('copied size', 4, GetFileSize(hC, NULL));
74
74
  t.checkTrue('CloseHandle (copy reader)', CloseHandle(hC) !== 0);
75
75
  t.checkTrue('MoveFileEx replace ok', MoveFileEx(pathB, pathC, 0x1 /* MOVEFILE_REPLACE_EXISTING */) !== 0);
76
76
  t.check('moved: source gone', INVALID32, GetFileAttributes(pathB));
77
77
  t.checkTrue('dest is file (not dir)', (GetFileAttributes(pathC) & FILE_ATTRIBUTE_DIRECTORY) === 0);
78
78
  t.section('FindFirstFile/FindNextFile: enumerate *.txt incl. CJK name');
79
- const hCn = CreateFile(pathCn, GENERIC_WRITE, 0, NULL, 2 /* gui.FileCreation.CREATE_ALWAYS */, 0x80, NULL);
79
+ const hCn = CreateFile(pathCn, GENERIC_WRITE, 0, NULL, 2 /* FileCreation.CREATE_ALWAYS */, 0x80, NULL);
80
80
  CloseHandle(hCn); // 0 字节中文名文件
81
81
  const fd = WIN32_FIND_DATAW.alloc();
82
82
  const hFind = FindFirstFile(`${dir}\\a.txt`, fd.ptr);
@@ -1,4 +1,3 @@
1
- import * as gui from 'gui'
2
1
  import { WCHAR } from '../lib/ffi/bind.js'
3
2
  import { NULL, PtrArrayBuffer } from '../lib/ffi/ctype.js'
4
3
  import {
@@ -8,6 +7,7 @@ import {
8
7
  } from '../lib/windows/kernel32.js'
9
8
  import { WIN32_FIND_DATAW } from '../lib/windows/structs.js'
10
9
  import { Tester } from './test_helper.js'
10
+ import { FileCreation, FileSeekFrom } from '../constants'
11
11
 
12
12
  const GENERIC_READ = 0x80000000
13
13
  const GENERIC_WRITE = 0x40000000
@@ -49,7 +49,7 @@ export const suite = {
49
49
 
50
50
  t.section('CreateFile + WriteFile: write payload')
51
51
  const hW = CreateFile(pathA, GENERIC_WRITE, 0, NULL,
52
- gui.FileCreation.CREATE_ALWAYS, 0x80 /* FILE_ATTRIBUTE_NORMAL */, NULL)
52
+ FileCreation.CREATE_ALWAYS, 0x80 /* FILE_ATTRIBUTE_NORMAL */, NULL)
53
53
  t.checkTrue(`write handle valid (${hW})`, hW !== -1) // 失败 = INVALID_HANDLE_VALUE(-1)
54
54
  const payload = 'hello files'
55
55
  const wData = toWide(payload)
@@ -60,17 +60,17 @@ export const suite = {
60
60
 
61
61
  t.section('CreateFile(READ) + GetFileSize + SetFilePointer + ReadFile')
62
62
  const hR = CreateFile(pathA, GENERIC_READ, 0, NULL,
63
- gui.FileCreation.OPEN_EXISTING, 0x80, NULL)
63
+ FileCreation.OPEN_EXISTING, 0x80, NULL)
64
64
  t.checkTrue(`read handle valid (${hR})`, hR !== -1)
65
65
  t.check('GetFileSize', payload.length * 2, GetFileSize(hR, NULL))
66
- t.check('SetFilePointer BEGIN → 0', 0, SetFilePointer(hR, 0, NULL, gui.FileSeekFrom.BEGIN))
66
+ t.check('SetFilePointer BEGIN → 0', 0, SetFilePointer(hR, 0, NULL, FileSeekFrom.BEGIN))
67
67
  const rData = new PtrArrayBuffer(payload.length * 2)
68
68
  const pRd = new PtrArrayBuffer(4)
69
69
  t.checkTrue('ReadFile ok', ReadFile(hR, rData, payload.length * 2, pRd, NULL) !== 0)
70
70
  t.check('read roundtrip', payload, fromWide(rData, payload.length))
71
71
  // seek 到 END-4 验证负偏移 + 读尾 4 字节
72
72
  t.check('SetFilePointer END-4', payload.length * 2 - 4,
73
- SetFilePointer(hR, -4, NULL, gui.FileSeekFrom.END))
73
+ SetFilePointer(hR, -4, NULL, FileSeekFrom.END))
74
74
  const rTail = new PtrArrayBuffer(8)
75
75
  ReadFile(hR, rTail, 4, pRd, NULL)
76
76
  t.check('seeked tail', payload.slice(-2), fromWide(rTail, 2))
@@ -78,13 +78,13 @@ export const suite = {
78
78
 
79
79
  t.section('CopyFile + MoveFileEx + GetFileAttributes')
80
80
  const hB = CreateFile(pathB, GENERIC_WRITE, 0, NULL,
81
- gui.FileCreation.CREATE_ALWAYS, 0x80, NULL)
81
+ FileCreation.CREATE_ALWAYS, 0x80, NULL)
82
82
  const bData = toWide('bb')
83
83
  WriteFile(hB, bData, 4, pWr, NULL)
84
84
  CloseHandle(hB)
85
85
  t.checkTrue('CopyFile ok', CopyFile(pathB, pathC, 0) !== 0)
86
86
  const hC = CreateFile(pathC, GENERIC_READ, 0, NULL,
87
- gui.FileCreation.OPEN_EXISTING, 0x80, NULL)
87
+ FileCreation.OPEN_EXISTING, 0x80, NULL)
88
88
  t.check('copied size', 4, GetFileSize(hC, NULL))
89
89
  t.checkTrue('CloseHandle (copy reader)', CloseHandle(hC) !== 0)
90
90
  t.checkTrue('MoveFileEx replace ok',
@@ -95,7 +95,7 @@ export const suite = {
95
95
 
96
96
  t.section('FindFirstFile/FindNextFile: enumerate *.txt incl. CJK name')
97
97
  const hCn = CreateFile(pathCn, GENERIC_WRITE, 0, NULL,
98
- gui.FileCreation.CREATE_ALWAYS, 0x80, NULL)
98
+ FileCreation.CREATE_ALWAYS, 0x80, NULL)
99
99
  CloseHandle(hCn) // 0 字节中文名文件
100
100
  const fd = WIN32_FIND_DATAW.alloc()
101
101
  const hFind = FindFirstFile(`${dir}\\a.txt`, fd.ptr)