quickwin 3.0.0 → 3.0.2

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 (253) hide show
  1. package/README.en.md +3 -0
  2. package/README.md +2 -0
  3. package/lib/abort.d.ts +21 -0
  4. package/lib/assert.d.ts +2 -0
  5. package/lib/bitmap.d.ts +7 -0
  6. package/lib/cache-utils.d.ts +6 -0
  7. package/lib/certs.d.ts +1 -0
  8. package/lib/dpi.d.ts +9 -0
  9. package/lib/dumpRects.d.ts +2 -0
  10. package/lib/exec-log.d.ts +23 -0
  11. package/lib/fetch.d.ts +4 -0
  12. package/lib/ffi/bind.d.ts +70 -0
  13. package/lib/ffi/ctype.d.ts +117 -0
  14. package/lib/ffi/index.d.ts +2 -0
  15. package/lib/ffi/struct.d.ts +174 -0
  16. package/lib/http-common.d.ts +143 -0
  17. package/lib/http-server.d.ts +59 -0
  18. package/lib/polyfill.d.ts +22 -0
  19. package/lib/react-qw/components/Button.d.ts +9 -0
  20. package/lib/react-qw/components/CheckBox.d.ts +11 -0
  21. package/lib/react-qw/components/ComboBox.d.ts +11 -0
  22. package/lib/react-qw/components/DateTimePicker.d.ts +15 -0
  23. package/lib/react-qw/components/Input.d.ts +15 -0
  24. package/lib/react-qw/components/Link.d.ts +12 -0
  25. package/lib/react-qw/components/ListBox.d.ts +13 -0
  26. package/lib/react-qw/components/ListView.d.ts +37 -0
  27. package/lib/react-qw/components/PathPicker.d.ts +23 -0
  28. package/lib/react-qw/components/ProgressBar.d.ts +9 -0
  29. package/lib/react-qw/components/RadioButton.d.ts +11 -0
  30. package/lib/react-qw/components/ScrollView.d.ts +9 -0
  31. package/lib/react-qw/components/Slider.d.ts +12 -0
  32. package/lib/react-qw/components/Tab.d.ts +14 -0
  33. package/lib/react-qw/components/Tooltip.d.ts +7 -0
  34. package/lib/react-qw/components/TreeView.d.ts +18 -0
  35. package/lib/react-qw/components/index.d.ts +16 -0
  36. package/lib/react-qw/index.d.ts +24 -0
  37. package/lib/react-qw/layout.d.ts +29 -0
  38. package/lib/react-qw/nmhdr.d.ts +1 -0
  39. package/lib/react-qw/props.d.ts +2 -0
  40. package/lib/react-qw/reconciler.d.ts +21 -0
  41. package/lib/stream.d.ts +20 -0
  42. package/lib/text-codec.d.ts +39 -0
  43. package/lib/text-measure.d.ts +14 -0
  44. package/lib/url.d.ts +54 -0
  45. package/lib/websocket.d.ts +73 -0
  46. package/lib/windows/{advapi32.ts → advapi32.d.ts} +6 -21
  47. package/lib/windows/comctl32.d.ts +14 -0
  48. package/lib/windows/comdlg32.d.ts +4 -0
  49. package/lib/windows/{gdi32.ts → gdi32.d.ts} +161 -98
  50. package/lib/windows/{kernel32.ts → kernel32.d.ts} +138 -115
  51. package/lib/windows/{msimg32.ts → msimg32.d.ts} +11 -14
  52. package/lib/windows/ntdll.d.ts +5 -0
  53. package/lib/windows/ole32.d.ts +3 -0
  54. package/lib/windows/{psapi.ts → psapi.d.ts} +9 -18
  55. package/lib/windows/{shell32.ts → shell32.d.ts} +25 -24
  56. package/lib/windows/structs.d.ts +3141 -0
  57. package/lib/windows/{user32.ts → user32.d.ts} +196 -193
  58. package/lib/windows/version.d.ts +27 -0
  59. package/lib/windows/{winspool.ts → winspool.d.ts} +18 -32
  60. package/package.json +15 -8
  61. package/qwin-nowasm-x86.exe +0 -0
  62. package/qwin-nowasm.exe +0 -0
  63. package/qwin-x86.exe +0 -0
  64. package/qwin.exe +0 -0
  65. package/examples/PdfCanvas.js +0 -38
  66. package/examples/PdfCanvas.tsx +0 -54
  67. package/examples/execd/main.js +0 -23379
  68. package/examples/execd/tray.js +0 -192
  69. package/examples/execd/worker.js +0 -122
  70. package/examples/pdf_preview2.js +0 -398
  71. package/examples/pdf_preview2.ts +0 -353
  72. package/examples/pdf_viewer.js +0 -22515
  73. package/examples/pdf_viewer.tsx +0 -228
  74. package/examples/test_gallery.js +0 -18692
  75. package/examples/test_gallery.tsx +0 -291
  76. package/examples/test_react_lazy.js +0 -18560
  77. package/examples/test_react_lazy.tsx +0 -65
  78. package/examples/test_react_listbox.js +0 -18562
  79. package/examples/test_react_listbox.tsx +0 -73
  80. package/examples/test_root_window.js +0 -18532
  81. package/examples/test_root_window.tsx +0 -41
  82. package/examples/test_tab.js +0 -18563
  83. package/examples/test_tab.tsx +0 -77
  84. package/examples/tray_demo.js +0 -86
  85. package/examples/tray_demo.tsx +0 -89
  86. package/lib/abort.ts +0 -69
  87. package/lib/assert.ts +0 -7
  88. package/lib/bitmap.ts +0 -39
  89. package/lib/cache-utils.ts +0 -40
  90. package/lib/certs.ts +0 -258
  91. package/lib/dpi.ts +0 -31
  92. package/lib/dumpRects.ts +0 -34
  93. package/lib/exec-log.ts +0 -30
  94. package/lib/fetch.ts +0 -589
  95. package/lib/ffi/bind.ts +0 -540
  96. package/lib/ffi/ctype.ts +0 -371
  97. package/lib/ffi/index.ts +0 -6
  98. package/lib/ffi/struct.ts +0 -780
  99. package/lib/http-common.ts +0 -441
  100. package/lib/http-server.ts +0 -604
  101. package/lib/polyfill.ts +0 -171
  102. package/lib/react-qw/components/index.ts +0 -17
  103. package/lib/react-qw/index.ts +0 -96
  104. package/lib/react-qw/layout.ts +0 -111
  105. package/lib/react-qw/nmhdr.ts +0 -7
  106. package/lib/react-qw/props.ts +0 -41
  107. package/lib/react-qw/reconciler.ts +0 -379
  108. package/lib/stream.ts +0 -22
  109. package/lib/text-codec.ts +0 -171
  110. package/lib/text-measure.ts +0 -44
  111. package/lib/url.ts +0 -201
  112. package/lib/websocket.ts +0 -600
  113. package/lib/windows/comctl32.ts +0 -20
  114. package/lib/windows/comdlg32.ts +0 -10
  115. package/lib/windows/ntdll.ts +0 -11
  116. package/lib/windows/ole32.ts +0 -9
  117. package/lib/windows/structs.ts +0 -807
  118. package/lib/windows/version.ts +0 -35
  119. package/test/add.wasm +0 -0
  120. package/test/bench_ffi_callraw.js +0 -235
  121. package/test/bench_ffi_callraw.ts +0 -235
  122. package/test/bench_ffi_overhead.js +0 -139
  123. package/test/bench_ffi_overhead.ts +0 -141
  124. package/test/bench_sendmessage.js +0 -104
  125. package/test/bench_sendmessage.ts +0 -100
  126. package/test/bench_sendmessage_nonnull.js +0 -187
  127. package/test/bench_sendmessage_nonnull.ts +0 -188
  128. package/test/complex.wasm +0 -0
  129. package/test/complex_imports.wasm +0 -0
  130. package/test/debug/http_server_e2e.js +0 -31
  131. package/test/debug/minwasm.js +0 -52
  132. package/test/debug/mupdf_debug.js +0 -86
  133. package/test/debug/wasm.js +0 -122
  134. package/test/debug/wasm_export_debug.js +0 -54
  135. package/test/debug/wasm_names.js +0 -36
  136. package/test/example.pdf +0 -0
  137. package/test/global_imports.wasm +0 -0
  138. package/test/import_func.wasm +0 -0
  139. package/test/imports.wasm +0 -0
  140. package/test/mupdf-cdn.js +0 -40
  141. package/test/mupdf-cdn.ts +0 -43
  142. package/test/run.js +0 -111
  143. package/test/run.ts +0 -116
  144. package/test/sjlj.wasm +0 -0
  145. package/test/test_basic.js +0 -7
  146. package/test/test_basic.ts +0 -9
  147. package/test/test_basic_fetch.js +0 -63
  148. package/test/test_basic_fetch.ts +0 -59
  149. package/test/test_brotli.js +0 -48
  150. package/test/test_brotli.ts +0 -52
  151. package/test/test_fetch_cache.js +0 -169
  152. package/test/test_fetch_cache.ts +0 -187
  153. package/test/test_ffi.js +0 -157
  154. package/test/test_ffi.ts +0 -170
  155. package/test/test_ffi_abi.js +0 -290
  156. package/test/test_ffi_abi.ts +0 -274
  157. package/test/test_ffi_array.js +0 -116
  158. package/test/test_ffi_array.ts +0 -141
  159. package/test/test_ffi_bind.js +0 -656
  160. package/test/test_ffi_bind.ts +0 -670
  161. package/test/test_ffi_struct.js +0 -779
  162. package/test/test_ffi_struct.ts +0 -828
  163. package/test/test_ffi_struct_layout.js +0 -495
  164. package/test/test_ffi_struct_layout.ts +0 -543
  165. package/test/test_ffi_user32.js +0 -103
  166. package/test/test_ffi_user32.ts +0 -113
  167. package/test/test_ffi_windows.js +0 -172
  168. package/test/test_ffi_windows.ts +0 -187
  169. package/test/test_files.js +0 -102
  170. package/test/test_files.ts +0 -121
  171. package/test/test_frame_encoding.js +0 -128
  172. package/test/test_frame_encoding.ts +0 -132
  173. package/test/test_gdi_draw.js +0 -681
  174. package/test/test_gdi_draw.ts +0 -725
  175. package/test/test_helper.js +0 -91
  176. package/test/test_helper.ts +0 -88
  177. package/test/test_http_import.js +0 -96
  178. package/test/test_http_import.ts +0 -93
  179. package/test/test_http_server.js +0 -214
  180. package/test/test_http_server.ts +0 -211
  181. package/test/test_import_dir.js +0 -4
  182. package/test/test_import_dir.ts +0 -5
  183. package/test/test_ipv6.js +0 -134
  184. package/test/test_ipv6.ts +0 -130
  185. package/test/test_jsx.js +0 -1314
  186. package/test/test_meta_url.js +0 -3
  187. package/test/test_meta_url.ts +0 -4
  188. package/test/test_mupdf_render.js +0 -51
  189. package/test/test_mupdf_render.ts +0 -55
  190. package/test/test_mupdf_twice.js +0 -62
  191. package/test/test_mupdf_twice.ts +0 -65
  192. package/test/test_mupdf_wasm.js +0 -39
  193. package/test/test_mupdf_wasm.ts +0 -32
  194. package/test/test_net_event.js +0 -57
  195. package/test/test_net_event.ts +0 -56
  196. package/test/test_net_fetch.js +0 -384
  197. package/test/test_net_fetch.ts +0 -373
  198. package/test/test_net_websocket.js +0 -180
  199. package/test/test_net_websocket.ts +0 -164
  200. package/test/test_polyfill.js +0 -144
  201. package/test/test_polyfill.ts +0 -159
  202. package/test/test_printer.js +0 -137
  203. package/test/test_printer.ts +0 -148
  204. package/test/test_proc.js +0 -141
  205. package/test/test_proc.ts +0 -163
  206. package/test/test_react_complex.js +0 -18678
  207. package/test/test_react_counter.js +0 -18555
  208. package/test/test_react_flex.js +0 -18871
  209. package/test/test_react_hidden.js +0 -18560
  210. package/test/test_react_listview_link.js +0 -18590
  211. package/test/test_react_notify.js +0 -18797
  212. package/test/test_react_render.js +0 -18550
  213. package/test/test_registry.js +0 -74
  214. package/test/test_registry.ts +0 -95
  215. package/test/test_sysinfo.js +0 -122
  216. package/test/test_sysinfo.ts +0 -141
  217. package/test/test_url.js +0 -173
  218. package/test/test_url.ts +0 -183
  219. package/test/test_wasm_basic.js +0 -85
  220. package/test/test_wasm_basic.ts +0 -73
  221. package/test/test_wasm_import_global.js +0 -41
  222. package/test/test_wasm_import_global.ts +0 -45
  223. package/test/test_wasm_sjlj.js +0 -145
  224. package/test/test_wasm_sjlj.ts +0 -125
  225. package/test/test_wasm_types.js +0 -97
  226. package/test/test_wasm_types.ts +0 -124
  227. package/test/test_wolfssl_read.js +0 -203
  228. package/test/test_wolfssl_read.ts +0 -184
  229. package/test/test_worker.js +0 -21
  230. package/test/test_worker.ts +0 -24
  231. package/test/test_worker_concurrent.js +0 -45
  232. package/test/test_worker_concurrent.ts +0 -56
  233. package/test/test_worker_data_url.js +0 -46
  234. package/test/test_worker_data_url.ts +0 -51
  235. package/test/test_worker_fetch.js +0 -26
  236. package/test/test_worker_fetch.ts +0 -27
  237. package/test/test_worker_net.js +0 -69
  238. package/test/test_worker_net.ts +0 -66
  239. package/test/test_worker_wasm_concurrent.js +0 -44
  240. package/test/test_worker_wasm_concurrent.ts +0 -45
  241. package/test/types.wasm +0 -0
  242. package/test/worker_fetch.js +0 -13
  243. package/test/worker_fetch.ts +0 -13
  244. package/test/worker_helper.js +0 -1
  245. package/test/worker_helper.ts +0 -1
  246. package/test/worker_http.js +0 -18
  247. package/test/worker_http.ts +0 -20
  248. package/test/worker_import_lib.js +0 -12
  249. package/test/worker_import_lib.ts +0 -13
  250. package/test/worker_module.js +0 -16
  251. package/test/worker_module.ts +0 -17
  252. package/test/worker_wasm_concurrent.js +0 -22
  253. package/test/worker_wasm_concurrent.ts +0 -22
@@ -1,113 +0,0 @@
1
- import { Tester } from './test_helper.js'
2
- import { closure, WCHAR } from '../lib/ffi/bind.js'
3
- import { NULL, PtrArrayBuffer } from '../lib/ffi/ctype.js'
4
- import {
5
- GetDesktopWindow, IsWindow, IsWindowVisible, GetSystemMetrics, GetClientRect,
6
- GetWindowTextLength, GetClassName, FindWindow, SetTimer, KillTimer,
7
- GetKeyState, LoadCursor, SetCursor, GetMenu, DestroyMenu, GetForegroundWindow,
8
- GetDC, ReleaseDC, EnumWindows, FindWindowEx,
9
- } from '../lib/windows/user32.js'
10
- import { RECT } from '../lib/windows/structs.js'
11
-
12
- export const suite = {
13
- run(t: Tester) {
14
- t.section('user32: 窗口 / 度量')
15
- const desk = GetDesktopWindow()
16
- t.checkTrue('GetDesktopWindow 非 0', desk !== 0)
17
- if (desk === 0) return
18
- t.check('IsWindow(desktop)', 1, IsWindow(desk))
19
- t.check('IsWindowVisible(desktop)', 1, IsWindowVisible(desk))
20
- t.checkTrue('GetSystemMetrics(SM_CXSCREEN) > 0', GetSystemMetrics(0) > 0)
21
- t.checkTrue('GetForegroundWindow 形态 number',
22
- typeof GetForegroundWindow() === 'number')
23
-
24
- t.section('user32: RECT 出参(encode → call → decode 字段直读)')
25
- const rectOut = RECT.alloc()
26
- t.check('GetClientRect(desktop)', 1, GetClientRect(desk, rectOut.ptr))
27
- const rect = RECT.decode(rectOut)
28
- t.check('客户区宽 = SM_CXSCREEN', GetSystemMetrics(0), rect.right - rect.left)
29
- t.check('客户区高 = SM_CYSCREEN', GetSystemMetrics(1), rect.bottom - rect.top)
30
-
31
- t.section('user32: 字符串出参(宽字符 buffer 读回)')
32
- t.checkTrue('GetWindowTextLength ≥ 0', GetWindowTextLength(desk) >= 0)
33
- const cls = new PtrArrayBuffer(64)
34
- const n = GetClassName(desk, cls, 32)
35
- t.checkTrue('GetClassName 写入长度 > 0', n > 0)
36
- const cdv = new DataView(cls)
37
- let clsName = ''
38
- for (let i = 0; i < n; i++) clsName += String.fromCharCode(cdv.getUint16(i * 2, true))
39
- t.checkTrue('类名非空', clsName.length > 0)
40
- // JSDoc 文档化流(L4):WCHAR.alloc(n) 当 out buffer、WCHAR.decode(buf) 读回
41
- const clsH = WCHAR.alloc(32)
42
- t.checkTrue('WCHAR.alloc out 写入长度 > 0', GetClassName(desk, clsH, 32) > 0)
43
- t.check('WCHAR.decode(buf) 读回类名', clsName, WCHAR.decode(clsH))
44
-
45
- t.section('user32: FindWindow(<WCHAR>ptr 的 NULL 与 string 双形态)')
46
- const fw = FindWindow(NULL, NULL)
47
- t.checkTrue('FindWindow(NULL, NULL) 形态 number(NULL 实参不 throw)',
48
- typeof fw === 'number')
49
- t.check('FindWindow(不存在类名, NULL)', 0, FindWindow('qw_no_such_class_xyz', NULL))
50
-
51
- t.section('user32: 计时器')
52
- const tid = SetTimer(NULL, 0, 50, NULL)
53
- t.checkTrue('SetTimer 返回非 0 id', tid !== 0)
54
- if (tid !== 0) t.checkTrue('KillTimer 成功', KillTimer(NULL, tid) !== 0)
55
-
56
- t.section('user32: 输入 / 光标 / 菜单')
57
- t.checkTrue('GetKeyState 返回整数', Number.isInteger(GetKeyState(0x1b)))
58
- t.checkTrue('LoadCursor(NULL, IDC_ARROW=32512) 非 0', LoadCursor(NULL, 32512) !== 0)
59
- // 品牌流动(正例):LoadCursor 返回 MaybePtr<'HCURSOR'> 直接喂 SetCursor 形参(先收窄空位)
60
- const arrow = LoadCursor(NULL, 32512)
61
- const prevCur = arrow !== 0 ? SetCursor(arrow) : null
62
- t.checkTrue('SetCursor(LoadCursor(...)) 返回句柄形态',
63
- arrow !== 0 && (prevCur === null || typeof prevCur === 'number'))
64
- t.check('GetMenu(desktop) 无菜单 → 0', 0, GetMenu(desk))
65
-
66
- t.section('user32: GetDC / ReleaseDC 配对')
67
- const hdc = GetDC(NULL)
68
- t.checkTrue('GetDC(NULL) 非 0', hdc !== 0)
69
- t.checkTrue('ReleaseDC(NULL, hdc) 成功', hdc !== 0 && ReleaseDC(NULL, hdc) !== 0)
70
-
71
- // 仅类型层反例(不执行):异种句柄互传、<>ptr 误传品牌位都应编译不过
72
- const _typeOnly = () => {
73
- // @ts-expect-error Ptr<>(number)不是 Ptr<'HDC'>,误传桌面句柄应编译不过
74
- ReleaseDC(NULL, GetDesktopWindow())
75
- // @ts-expect-error Ptr<'HWND'> 不是 Ptr<'HCURSOR'>,窗口句柄喂 SetCursor 应编译不过
76
- SetCursor(GetDesktopWindow())
77
- // @ts-expect-error Ptr<'HWND'> 不是 Ptr<'HMENU'>,窗口句柄喂 DestroyMenu 应编译不过
78
- DestroyMenu(GetDesktopWindow())
79
- // @ts-expect-error Ptr<'HMENU'> 不是 Ptr<'HWND'>,菜单句柄喂 IsWindow 应编译不过
80
- IsWindow(GetMenu(desk))
81
- // @ts-expect-error 出参位未注册 encoder:<RECT>ptr 只收 MaybePtr<'RECT'>(NULL|Ptr),裸 ArrayBuffer 编译不过
82
- GetClientRect(desk, new ArrayBuffer(16))
83
- // @ts-expect-error 裸 number 缺 Ptr<'RECT'> 品牌
84
- GetClientRect(desk, 123)
85
- // 正例:同种品牌流动(GetMenu → DestroyMenu)通过——上面反例的成立依赖这层区分
86
- const m = GetMenu(desk)
87
- if (m !== 0) DestroyMenu(m)
88
- }
89
- void _typeOnly
90
-
91
- t.section('user32: EnumWindows(<>ptr 回调端到端)')
92
- let count = 0
93
- const enumClos = closure('<>ptr <>ptr -> i32', () => { count++; return 1 })
94
- t.check('EnumWindows 返回 1(枚举完成)', 1, EnumWindows(enumClos.ptr, NULL))
95
- t.checkTrue('回调计数 ≥ 1', count >= 1)
96
- enumClos.dispose()
97
-
98
- // EnumWindows(快照)与 FindWindowEx(NULL, prev) 链式遍历应得到同一组顶层窗口(实测两者顺序不同,
99
- // 所以只比集合,不比顺序)。
100
- // 回调只收集:回调内抛异常会被吞成返回 0(枚举静默提前结束),所以不在回调里做别的。
101
- t.section('user32: EnumWindows ≡ FindWindowEx 链遍历(顶层窗口集合)')
102
- const viaEnum: number[] = []
103
- const collect = closure('<HWND>ptr <>ptr -> i32', (h) => { viaEnum.push(h); return 1 })
104
- try { EnumWindows(collect.ptr, NULL) } finally { collect.dispose() }
105
- const viaChain: number[] = []
106
- for (let h = FindWindowEx(NULL, NULL, NULL, NULL); h && viaChain.length < 100000;
107
- h = FindWindowEx(NULL, h, NULL, NULL)) viaChain.push(h)
108
- t.checkTrue('两种遍历都非空', viaEnum.length > 0 && viaChain.length > 0)
109
- t.check('窗口数量一致', viaEnum.length, viaChain.length)
110
- const sorted = (a: number[]): string => a.slice().sort((x, y) => x - y).join(',')
111
- t.check('窗口集合一致(忽略顺序)', sorted(viaEnum), sorted(viaChain))
112
- },
113
- }
@@ -1,172 +0,0 @@
1
- import { NULL, PTR_SIZE, PtrArrayBuffer } from '../lib/ffi/ctype.js';
2
- import { GetDesktopWindow, CreatePopupMenu, DestroyMenu, GetDC, ReleaseDC, LoadImage, LoadImageOrdinal, SetScrollInfo, GetScrollInfo, } from '../lib/windows/user32.js';
3
- import { GetDeviceCaps, CreateSolidBrush, DeleteObject, CreateFontIndirect, GetObject, SelectObject, CreateCompatibleDC, CreateCompatibleBitmap, DeleteDC, GetDIBits, } from '../lib/windows/gdi32.js';
4
- import { ImageListCreate, ImageListAdd, ImageListDestroy } from '../lib/windows/comctl32.js';
5
- import { ShellNotifyIcon } from '../lib/windows/shell32.js';
6
- import { BITMAPINFOHEADER, LOGFONTW, NONCLIENTMETRICSW, NOTIFYICONDATAW, SCROLLINFO } from '../lib/windows/structs.js';
7
- import { createSystemDpiFont, getScaleFactor } from '../lib/dpi.js';
8
- import { createBitmapFromPixels } from '../lib/bitmap.js';
9
- export const suite = {
10
- run(t) {
11
- // 本文件 import 三个 dll 模块即在加载时执行全部 bind:proc 名错误 → GetProcAddress 失败 →
12
- // 模块加载 throw → run.ts 静默跳过本 suite → 总计数低于基线(判据链对比计数即暴露)
13
- t.section('gdi32: 画刷 / 字体 创建-删除往返(LOGFONTW 对象直传)');
14
- const brush = CreateSolidBrush(0x3366ff);
15
- t.checkTrue('CreateSolidBrush 非 0', brush !== 0);
16
- if (brush !== 0)
17
- t.checkTrue('DeleteObject(brush) 成功', DeleteObject(brush) !== 0);
18
- // DeepPartial 缺省字段跳过:lfHeight + lfFaceName 必给,其余标量保持 0(FW_DONTCARE 等合法零值)
19
- const font = CreateFontIndirect({ lfHeight: -16, lfWeight: 400, lfCharSet: 1, lfFaceName: 'Arial' });
20
- t.checkTrue('CreateFontIndirect(DeepPartial LOGFONTW) 非 0', font !== 0);
21
- if (font !== 0)
22
- t.checkTrue('DeleteObject(font) 成功', DeleteObject(font) !== 0);
23
- // react-qw ListView.getCellFont 的流程:GetObject 读出现有字体 → decode → 叠加粗/斜/下划线 → 重建 → 回读
24
- t.section('gdi32: 字体读出-改写-重建往返(GetObject → decode → CreateFontIndirect → GetObject)');
25
- const base = CreateFontIndirect({ lfHeight: -18, lfWeight: 400, lfCharSet: 1, lfFaceName: 'Arial' });
26
- if (base !== 0) {
27
- const lf0 = LOGFONTW.alloc();
28
- t.checkTrue('GetObject(font) 写入 LOGFONTW.size 字节', GetObject(base, LOGFONTW.size, lf0) === LOGFONTW.size);
29
- const b0 = LOGFONTW.decode(lf0);
30
- t.check('读出 lfHeight', -18, b0.lfHeight);
31
- t.check('读出 lfWeight', 400, b0.lfWeight);
32
- t.check('读出 lfItalic(未设)', 0, b0.lfItalic);
33
- const styled = CreateFontIndirect({ ...b0, lfWeight: 700, lfItalic: 1, lfUnderline: 1 });
34
- t.checkTrue('叠加样式后重建非 0', styled !== 0);
35
- if (styled !== 0) {
36
- const lf1 = LOGFONTW.alloc();
37
- GetObject(styled, LOGFONTW.size, lf1);
38
- const b1 = LOGFONTW.decode(lf1);
39
- t.check('重建后 lfWeight', 700, b1.lfWeight);
40
- t.check('重建后 lfItalic', 1, b1.lfItalic);
41
- t.check('重建后 lfUnderline', 1, b1.lfUnderline);
42
- t.check('底字体的 lfHeight 保留', -18, b1.lfHeight);
43
- t.check('底字体的 lfCharSet 保留', 1, b1.lfCharSet);
44
- t.check('底字体的字体名保留', b0.lfFaceName, b1.lfFaceName);
45
- t.checkTrue('DeleteObject(styled) 成功', DeleteObject(styled) !== 0);
46
- }
47
- t.checkTrue('DeleteObject(base) 成功', DeleteObject(base) !== 0);
48
- }
49
- // 内存 DC 选入字体:SelectObject 返回被替换的旧对象,选回后可删除新对象(截屏与 ListView 自绘共用)
50
- t.section('gdi32: CreateCompatibleDC / SelectObject 往返');
51
- const memDc = CreateCompatibleDC(NULL);
52
- t.checkTrue('CreateCompatibleDC(NULL) 非 0', memDc !== 0);
53
- if (memDc !== 0) {
54
- const f2 = CreateFontIndirect({ lfHeight: -12, lfFaceName: 'Arial' });
55
- if (f2 !== 0) {
56
- const old = SelectObject(memDc, f2);
57
- t.checkTrue('SelectObject 返回旧对象非 0', old !== 0);
58
- t.check('再选回旧对象,返回刚选入的字体', f2, SelectObject(memDc, old));
59
- t.checkTrue('DeleteObject(f2) 成功', DeleteObject(f2) !== 0);
60
- }
61
- t.checkTrue('DeleteDC 成功', DeleteDC(memDc) !== 0);
62
- }
63
- t.section('gdi32: GetDeviceCaps(<HDC>ptr 跨 user32 品牌互认)');
64
- const hdc = GetDC(NULL);
65
- t.checkTrue('GetDC(NULL) 非 0', hdc !== 0);
66
- if (hdc !== 0)
67
- t.checkTrue('GetDeviceCaps(hdc, LOGPIXELSX) > 0', GetDeviceCaps(hdc, 88 /* DeviceCap.LOGPIXELSX */) > 0);
68
- t.checkTrue('ReleaseDC(NULL, hdc) 成功', hdc !== 0 && ReleaseDC(NULL, hdc) !== 0);
69
- t.section('user32: 弹出菜单往返');
70
- const pm = CreatePopupMenu();
71
- t.checkTrue('CreatePopupMenu 非 0', pm !== 0);
72
- if (pm !== 0)
73
- t.checkTrue('DestroyMenu(pm) 成功', DestroyMenu(pm) !== 0);
74
- t.section('comctl32: ImageList 创建-加位图-销毁往返(hbmMask 位传 NULL)');
75
- const himl = ImageListCreate(16, 16, 24 /* ImageListFlag.COLOR24 */, 0, 0); // initial/grow=0 默认
76
- t.checkTrue('ImageListCreate(16, 16, ILC_COLOR24) 非 0', himl !== 0);
77
- if (himl !== 0) {
78
- const hdcMem = CreateCompatibleDC(NULL);
79
- const hbm = hdcMem !== 0 ? CreateCompatibleBitmap(hdcMem, 16, 16) : 0;
80
- t.checkTrue('CreateCompatibleBitmap(16, 16) 非 0', hbm !== 0);
81
- if (hbm !== 0)
82
- t.check('ImageListAdd(himl, hbm, NULL) → 索引 0', 0, ImageListAdd(himl, hbm, NULL));
83
- if (hbm !== 0)
84
- DeleteObject(hbm);
85
- if (hdcMem !== 0)
86
- DeleteDC(hdcMem);
87
- t.checkTrue('ImageListDestroy(himl) 成功', ImageListDestroy(himl) !== 0);
88
- }
89
- t.section('user32: LoadImage 双变体(string 自动编码 / ordinal MAKEINTRESOURCE)');
90
- // Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 ImageType d.ts 当前值与此不一致)
91
- t.checkTrue('LoadImageOrdinal(NULL, IDI_APPLICATION, IMAGE_ICON, LR_SHARED) 非 0', LoadImageOrdinal(NULL, 32512, 2, 0, 0, 0x8000) !== 0);
92
- t.check('LoadImage(LOADFROMFILE, 不存在文件) → 0', 0, LoadImage(NULL, 'qw_no_such_icon_xyz.ico', 2, 0, 0, 0x10));
93
- t.section('user32: 滚动条(入参对象直传 / 出参 encode → call → decode)');
94
- const desk = GetDesktopWindow();
95
- t.checkTrue('GetDesktopWindow 非 0', desk !== 0);
96
- if (desk === 0)
97
- return;
98
- // 入参位(注册 encoder):DeepPartial 对象直传;cbSize 必须显式 = 结构自描述要求。
99
- // 桌面窗口无 WS_VSCROLL 样式 → Set/GetScrollInfo 返回 0 是 Win32 合法行为(.NET 的
100
- // WindowsScroll 包装同样先查样式再调用),故断言形态而非成功。
101
- t.checkTrue('SetScrollInfo 对象直传 形态 number', typeof SetScrollInfo(desk, 1, { cbSize: SCROLLINFO.size, fMask: 0x0001, nMin: 0, nMax: 100 }, 0) === 'number');
102
- // 出参位(不注册 encoder):encode() 新建 + 初值一步 → 传 .ptr → decode 字段直读。
103
- // GetScrollInfo 失败(无滚动条)不写 buffer → decode 回读 = encode 初值;成功时 Win32
104
- // 覆写 cbSize/fMask 为同值 —— 两条路径下回环断言都稳定。
105
- const si = SCROLLINFO.encode({ cbSize: SCROLLINFO.size, fMask: 0x0001 });
106
- t.checkTrue('GetScrollInfo 调用形态 number', typeof GetScrollInfo(desk, 1, si.ptr) === 'number');
107
- const info = SCROLLINFO.decode(si);
108
- t.check('decode 回读 cbSize = encode 初值', SCROLLINFO.size, info.cbSize);
109
- t.check('decode 回读 fMask = encode 初值', 0x0001, info.fMask);
110
- t.checkTrue('读回字段为 number 形态', typeof info.nMin === 'number' && typeof info.nMax === 'number');
111
- // CallWindowProc / ShowScrollBar 仅由 import 的模块加载覆盖 proc 名:
112
- // CallWindowProc 跨线程窗口过程不安全、ShowScrollBar 有副作用
113
- // NONCLIENTMETRICS 的 cbSize/uiParam 随系统变(Vista+ 504 / XP·2003 500),
114
- // 尺寸回退逻辑在 lib/dpi;此处只锁结构布局与端到端效果(见下 section)。
115
- t.check('NONCLIENTMETRICSW.size == 504(4 LOGFONTW + 12 DWORD)', 504, NONCLIENTMETRICSW.size);
116
- t.section('lib/dpi: 下沉到 JS 的 DPI / 系统字体复合逻辑');
117
- t.checkTrue('getScaleFactor() ≥ 0.5', getScaleFactor() >= 0.5);
118
- const dpiFont = createSystemDpiFont();
119
- t.checkTrue('createSystemDpiFont() 非 NULL(SPI 尺寸回退后仍取到系统消息字体)', dpiFont !== 0);
120
- if (dpiFont) {
121
- const lf = LOGFONTW.alloc();
122
- t.checkTrue('GetObject(dpiFont, LOGFONTW.size) 回读成功', GetObject(dpiFont, LOGFONTW.size, lf) === LOGFONTW.size);
123
- const fo = LOGFONTW.decode(lf);
124
- t.checkTrue('系统消息字体 lfFaceName 非空(结构确被 SPI 填过)', typeof fo.lfFaceName === 'string' && fo.lfFaceName.length > 0);
125
- t.checkTrue('DeleteObject(系统字体) 成功', DeleteObject(dpiFont) !== 0);
126
- }
127
- t.section('shell32: Shell_NotifyIconW 托盘图标增删(NOTIFYICONDATAW 对象直传)');
128
- // 尺寸基准来自 mingw 头文件实测(x86 956 / x64 976,随 hWnd/hIcon/hBalloonIcon 位宽)
129
- t.check('NOTIFYICONDATAW.size == mingw sizeof(x86 956 / x64 976)', PTR_SIZE === 4 ? 956 : 976, NOTIFYICONDATAW.size);
130
- const nid = NOTIFYICONDATAW.encode({ cbSize: NOTIFYICONDATAW.size, szTip: 'quickwin', szInfoTitle: 'qw' });
131
- const nidBack = NOTIFYICONDATAW.decode(nid);
132
- t.check('宽字符串糖 szTip 编解码往返', 'quickwin', nidBack.szTip);
133
- t.check('宽字符串糖 szInfoTitle 编解码往返', 'qw', nidBack.szInfoTitle);
134
- const trayIcon = LoadImageOrdinal(NULL, 32512 /* IDI.APPLICATION */, 1 /* ImageType.ICON */, 0, 0, 32768 /* LoadImageFlag.SHARED */);
135
- t.checkTrue('LoadImageOrdinal(IDI.APPLICATION, SHARED) 非 0', trayIcon !== 0);
136
- const trayHwnd = GetDesktopWindow();
137
- t.checkTrue('ShellNotifyIcon(ADD) 成功(Vista+ 尺寸在 XP/Win7 均被接受)', ShellNotifyIcon(0 /* NotifyIconCmd.ADD */, {
138
- cbSize: NOTIFYICONDATAW.size, hWnd: trayHwnd, uID: 77,
139
- uFlags: 1 /* NotifyIconFlag.MESSAGE */ | 2 /* NotifyIconFlag.ICON */ | 4 /* NotifyIconFlag.TIP */,
140
- uCallbackMessage: 0x8001, hIcon: trayIcon, szTip: 'quickwin test',
141
- }) !== 0);
142
- t.checkTrue('ShellNotifyIcon(DELETE) 成功(只需 hWnd+uID)', ShellNotifyIcon(2 /* NotifyIconCmd.DELETE */, { cbSize: NOTIFYICONDATAW.size, hWnd: trayHwnd, uID: 77 }) !== 0);
143
- t.section('lib/bitmap: 按像素建 32bpp DIB 段(原 gui.CreateBitmapFromPixels)');
144
- const nw = 4, nh = 2;
145
- const px = new ArrayBuffer(nw * nh * 4);
146
- const px8 = new Uint8Array(px);
147
- for (let i = 0; i < px8.length; i++)
148
- px8[i] = (i * 37 + 11) & 0xff;
149
- const hbm = createBitmapFromPixels(nw, nh, px);
150
- t.checkTrue('createBitmapFromPixels(4x2) 非 NULL', hbm !== 0);
151
- if (hbm) {
152
- const bmi = BITMAPINFOHEADER.encode({
153
- biSize: BITMAPINFOHEADER.size, biWidth: nw, biHeight: -nh,
154
- biPlanes: 1, biBitCount: 32, biSizeImage: nw * nh * 4,
155
- });
156
- const back = new PtrArrayBuffer(nw * nh * 4);
157
- const dc = GetDC(NULL);
158
- const rows = dc ? GetDIBits(dc, hbm, 0, nh, back, bmi.ptr, 0 /* ColorUse.RGB_COLORS */) : 0;
159
- if (dc)
160
- ReleaseDC(NULL, dc);
161
- t.check('GetDIBits 取回全部扫描行', nh, rows);
162
- const back8 = new Uint8Array(back);
163
- let same = rows === nh;
164
- for (let i = 0; i < back8.length; i++)
165
- if (back8[i] !== px8[i])
166
- same = false;
167
- t.checkTrue('像素逐字节一致(含 alpha,与旧 C 的 memcpy 等价)', same);
168
- t.checkTrue('DeleteObject(位图) 成功', DeleteObject(hbm) !== 0);
169
- }
170
- t.checkTrue('非法入参 → NULL(宽/高 ≤ 0、像素缓冲不足,同旧 C 语义)', createBitmapFromPixels(0, nh, px) === NULL && createBitmapFromPixels(nw, nh, new ArrayBuffer(4)) === NULL);
171
- },
172
- };
@@ -1,187 +0,0 @@
1
- import { Tester } from './test_helper.js'
2
- import { NULL, PTR_SIZE, PtrArrayBuffer } from '../lib/ffi/ctype.js'
3
- import {
4
- GetDesktopWindow, CreatePopupMenu, DestroyMenu, GetDC, ReleaseDC,
5
- LoadImage, LoadImageOrdinal, SetScrollInfo, GetScrollInfo,
6
- } from '../lib/windows/user32.js'
7
- import {
8
- GetDeviceCaps, CreateSolidBrush, DeleteObject, CreateFontIndirect, GetObject, SelectObject, CreateCompatibleDC, CreateCompatibleBitmap, DeleteDC, GetDIBits,
9
- } from '../lib/windows/gdi32.js'
10
- import { ImageListCreate, ImageListAdd, ImageListDestroy } from '../lib/windows/comctl32.js'
11
- import { ShellNotifyIcon } from '../lib/windows/shell32.js'
12
- import { BITMAPINFOHEADER, LOGFONTW, NONCLIENTMETRICSW, NOTIFYICONDATAW, SCROLLINFO } from '../lib/windows/structs.js'
13
- import { ColorUse, DeviceCap, IDI, ImageListFlag, ImageType, LoadImageFlag, NotifyIconCmd, NotifyIconFlag } from '../constants'
14
- import { createSystemDpiFont, getScaleFactor } from '../lib/dpi.js'
15
- import { createBitmapFromPixels } from '../lib/bitmap.js'
16
-
17
- export const suite = {
18
- run(t: Tester) {
19
- // 本文件 import 三个 dll 模块即在加载时执行全部 bind:proc 名错误 → GetProcAddress 失败 →
20
- // 模块加载 throw → run.ts 静默跳过本 suite → 总计数低于基线(判据链对比计数即暴露)
21
- t.section('gdi32: 画刷 / 字体 创建-删除往返(LOGFONTW 对象直传)')
22
- const brush = CreateSolidBrush(0x3366ff)
23
- t.checkTrue('CreateSolidBrush 非 0', brush !== 0)
24
- if (brush !== 0) t.checkTrue('DeleteObject(brush) 成功', DeleteObject(brush) !== 0)
25
-
26
- // DeepPartial 缺省字段跳过:lfHeight + lfFaceName 必给,其余标量保持 0(FW_DONTCARE 等合法零值)
27
- const font = CreateFontIndirect({ lfHeight: -16, lfWeight: 400, lfCharSet: 1, lfFaceName: 'Arial' })
28
- t.checkTrue('CreateFontIndirect(DeepPartial LOGFONTW) 非 0', font !== 0)
29
- if (font !== 0) t.checkTrue('DeleteObject(font) 成功', DeleteObject(font) !== 0)
30
-
31
- // react-qw ListView.getCellFont 的流程:GetObject 读出现有字体 → decode → 叠加粗/斜/下划线 → 重建 → 回读
32
- t.section('gdi32: 字体读出-改写-重建往返(GetObject → decode → CreateFontIndirect → GetObject)')
33
- const base = CreateFontIndirect({ lfHeight: -18, lfWeight: 400, lfCharSet: 1, lfFaceName: 'Arial' })
34
- if (base !== 0) {
35
- const lf0 = LOGFONTW.alloc()
36
- t.checkTrue('GetObject(font) 写入 LOGFONTW.size 字节', GetObject(base, LOGFONTW.size, lf0) === LOGFONTW.size)
37
- const b0 = LOGFONTW.decode(lf0)
38
- t.check('读出 lfHeight', -18, b0.lfHeight)
39
- t.check('读出 lfWeight', 400, b0.lfWeight)
40
- t.check('读出 lfItalic(未设)', 0, b0.lfItalic)
41
- const styled = CreateFontIndirect({ ...b0, lfWeight: 700, lfItalic: 1, lfUnderline: 1 })
42
- t.checkTrue('叠加样式后重建非 0', styled !== 0)
43
- if (styled !== 0) {
44
- const lf1 = LOGFONTW.alloc()
45
- GetObject(styled, LOGFONTW.size, lf1)
46
- const b1 = LOGFONTW.decode(lf1)
47
- t.check('重建后 lfWeight', 700, b1.lfWeight)
48
- t.check('重建后 lfItalic', 1, b1.lfItalic)
49
- t.check('重建后 lfUnderline', 1, b1.lfUnderline)
50
- t.check('底字体的 lfHeight 保留', -18, b1.lfHeight)
51
- t.check('底字体的 lfCharSet 保留', 1, b1.lfCharSet)
52
- t.check('底字体的字体名保留', b0.lfFaceName, b1.lfFaceName)
53
- t.checkTrue('DeleteObject(styled) 成功', DeleteObject(styled) !== 0)
54
- }
55
- t.checkTrue('DeleteObject(base) 成功', DeleteObject(base) !== 0)
56
- }
57
-
58
- // 内存 DC 选入字体:SelectObject 返回被替换的旧对象,选回后可删除新对象(截屏与 ListView 自绘共用)
59
- t.section('gdi32: CreateCompatibleDC / SelectObject 往返')
60
- const memDc = CreateCompatibleDC(NULL)
61
- t.checkTrue('CreateCompatibleDC(NULL) 非 0', memDc !== 0)
62
- if (memDc !== 0) {
63
- const f2 = CreateFontIndirect({ lfHeight: -12, lfFaceName: 'Arial' })
64
- if (f2 !== 0) {
65
- const old = SelectObject(memDc, f2)
66
- t.checkTrue('SelectObject 返回旧对象非 0', old !== 0)
67
- t.check('再选回旧对象,返回刚选入的字体', f2, SelectObject(memDc, old))
68
- t.checkTrue('DeleteObject(f2) 成功', DeleteObject(f2) !== 0)
69
- }
70
- t.checkTrue('DeleteDC 成功', DeleteDC(memDc) !== 0)
71
- }
72
-
73
- t.section('gdi32: GetDeviceCaps(<HDC>ptr 跨 user32 品牌互认)')
74
- const hdc = GetDC(NULL)
75
- t.checkTrue('GetDC(NULL) 非 0', hdc !== 0)
76
- if (hdc !== 0) t.checkTrue('GetDeviceCaps(hdc, LOGPIXELSX) > 0', GetDeviceCaps(hdc, DeviceCap.LOGPIXELSX) > 0)
77
- t.checkTrue('ReleaseDC(NULL, hdc) 成功', hdc !== 0 && ReleaseDC(NULL, hdc) !== 0)
78
-
79
- t.section('user32: 弹出菜单往返')
80
- const pm = CreatePopupMenu()
81
- t.checkTrue('CreatePopupMenu 非 0', pm !== 0)
82
- if (pm !== 0) t.checkTrue('DestroyMenu(pm) 成功', DestroyMenu(pm) !== 0)
83
-
84
- t.section('comctl32: ImageList 创建-加位图-销毁往返(hbmMask 位传 NULL)')
85
- const himl = ImageListCreate(16, 16, ImageListFlag.COLOR24, 0, 0) // initial/grow=0 默认
86
- t.checkTrue('ImageListCreate(16, 16, ILC_COLOR24) 非 0', himl !== 0)
87
- if (himl !== 0) {
88
- const hdcMem = CreateCompatibleDC(NULL)
89
- const hbm = hdcMem !== 0 ? CreateCompatibleBitmap(hdcMem, 16, 16) : 0
90
- t.checkTrue('CreateCompatibleBitmap(16, 16) 非 0', hbm !== 0)
91
- if (hbm !== 0) t.check('ImageListAdd(himl, hbm, NULL) → 索引 0', 0, ImageListAdd(himl, hbm, NULL))
92
- if (hbm !== 0) DeleteObject(hbm)
93
- if (hdcMem !== 0) DeleteDC(hdcMem)
94
- t.checkTrue('ImageListDestroy(himl) 成功', ImageListDestroy(himl) !== 0)
95
- }
96
-
97
- t.section('user32: LoadImage 双变体(string 自动编码 / ordinal MAKEINTRESOURCE)')
98
- // Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 ImageType d.ts 当前值与此不一致)
99
- t.checkTrue('LoadImageOrdinal(NULL, IDI_APPLICATION, IMAGE_ICON, LR_SHARED) 非 0',
100
- LoadImageOrdinal(NULL, 32512, 2, 0, 0, 0x8000) !== 0)
101
- t.check('LoadImage(LOADFROMFILE, 不存在文件) → 0', 0,
102
- LoadImage(NULL, 'qw_no_such_icon_xyz.ico', 2, 0, 0, 0x10))
103
-
104
- t.section('user32: 滚动条(入参对象直传 / 出参 encode → call → decode)')
105
- const desk = GetDesktopWindow()
106
- t.checkTrue('GetDesktopWindow 非 0', desk !== 0)
107
- if (desk === 0) return
108
- // 入参位(注册 encoder):DeepPartial 对象直传;cbSize 必须显式 = 结构自描述要求。
109
- // 桌面窗口无 WS_VSCROLL 样式 → Set/GetScrollInfo 返回 0 是 Win32 合法行为(.NET 的
110
- // WindowsScroll 包装同样先查样式再调用),故断言形态而非成功。
111
- t.checkTrue('SetScrollInfo 对象直传 形态 number',
112
- typeof SetScrollInfo(desk, 1, { cbSize: SCROLLINFO.size, fMask: 0x0001, nMin: 0, nMax: 100 }, 0) === 'number')
113
- // 出参位(不注册 encoder):encode() 新建 + 初值一步 → 传 .ptr → decode 字段直读。
114
- // GetScrollInfo 失败(无滚动条)不写 buffer → decode 回读 = encode 初值;成功时 Win32
115
- // 覆写 cbSize/fMask 为同值 —— 两条路径下回环断言都稳定。
116
- const si = SCROLLINFO.encode({ cbSize: SCROLLINFO.size, fMask: 0x0001 })
117
- t.checkTrue('GetScrollInfo 调用形态 number', typeof GetScrollInfo(desk, 1, si.ptr) === 'number')
118
- const info = SCROLLINFO.decode(si)
119
- t.check('decode 回读 cbSize = encode 初值', SCROLLINFO.size, info.cbSize)
120
- t.check('decode 回读 fMask = encode 初值', 0x0001, info.fMask)
121
- t.checkTrue('读回字段为 number 形态', typeof info.nMin === 'number' && typeof info.nMax === 'number')
122
- // CallWindowProc / ShowScrollBar 仅由 import 的模块加载覆盖 proc 名:
123
- // CallWindowProc 跨线程窗口过程不安全、ShowScrollBar 有副作用
124
-
125
- // NONCLIENTMETRICS 的 cbSize/uiParam 随系统变(Vista+ 504 / XP·2003 500),
126
- // 尺寸回退逻辑在 lib/dpi;此处只锁结构布局与端到端效果(见下 section)。
127
- t.check('NONCLIENTMETRICSW.size == 504(4 LOGFONTW + 12 DWORD)', 504, NONCLIENTMETRICSW.size)
128
-
129
- t.section('lib/dpi: 下沉到 JS 的 DPI / 系统字体复合逻辑')
130
- t.checkTrue('getScaleFactor() ≥ 0.5', getScaleFactor() >= 0.5)
131
- const dpiFont = createSystemDpiFont()
132
- t.checkTrue('createSystemDpiFont() 非 NULL(SPI 尺寸回退后仍取到系统消息字体)', dpiFont !== 0)
133
- if (dpiFont) {
134
- const lf = LOGFONTW.alloc()
135
- t.checkTrue('GetObject(dpiFont, LOGFONTW.size) 回读成功', GetObject(dpiFont, LOGFONTW.size, lf) === LOGFONTW.size)
136
- const fo = LOGFONTW.decode(lf)
137
- t.checkTrue('系统消息字体 lfFaceName 非空(结构确被 SPI 填过)', typeof fo.lfFaceName === 'string' && fo.lfFaceName.length > 0)
138
- t.checkTrue('DeleteObject(系统字体) 成功', DeleteObject(dpiFont) !== 0)
139
- }
140
-
141
- t.section('shell32: Shell_NotifyIconW 托盘图标增删(NOTIFYICONDATAW 对象直传)')
142
- // 尺寸基准来自 mingw 头文件实测(x86 956 / x64 976,随 hWnd/hIcon/hBalloonIcon 位宽)
143
- t.check('NOTIFYICONDATAW.size == mingw sizeof(x86 956 / x64 976)',
144
- PTR_SIZE === 4 ? 956 : 976, NOTIFYICONDATAW.size)
145
- const nid = NOTIFYICONDATAW.encode({ cbSize: NOTIFYICONDATAW.size, szTip: 'quickwin', szInfoTitle: 'qw' })
146
- const nidBack = NOTIFYICONDATAW.decode(nid)
147
- t.check('宽字符串糖 szTip 编解码往返', 'quickwin', nidBack.szTip)
148
- t.check('宽字符串糖 szInfoTitle 编解码往返', 'qw', nidBack.szInfoTitle)
149
-
150
- const trayIcon = LoadImageOrdinal(NULL, IDI.APPLICATION, ImageType.ICON, 0, 0, LoadImageFlag.SHARED)
151
- t.checkTrue('LoadImageOrdinal(IDI.APPLICATION, SHARED) 非 0', trayIcon !== 0)
152
- const trayHwnd = GetDesktopWindow()
153
- t.checkTrue('ShellNotifyIcon(ADD) 成功(Vista+ 尺寸在 XP/Win7 均被接受)', ShellNotifyIcon(NotifyIconCmd.ADD, {
154
- cbSize: NOTIFYICONDATAW.size, hWnd: trayHwnd, uID: 77,
155
- uFlags: NotifyIconFlag.MESSAGE | NotifyIconFlag.ICON | NotifyIconFlag.TIP,
156
- uCallbackMessage: 0x8001, hIcon: trayIcon, szTip: 'quickwin test',
157
- }) !== 0)
158
- t.checkTrue('ShellNotifyIcon(DELETE) 成功(只需 hWnd+uID)',
159
- ShellNotifyIcon(NotifyIconCmd.DELETE, { cbSize: NOTIFYICONDATAW.size, hWnd: trayHwnd, uID: 77 }) !== 0)
160
-
161
- t.section('lib/bitmap: 按像素建 32bpp DIB 段(原 gui.CreateBitmapFromPixels)')
162
- const nw = 4, nh = 2
163
- const px = new ArrayBuffer(nw * nh * 4)
164
- const px8 = new Uint8Array(px)
165
- for (let i = 0; i < px8.length; i++) px8[i] = (i * 37 + 11) & 0xff
166
- const hbm = createBitmapFromPixels(nw, nh, px)
167
- t.checkTrue('createBitmapFromPixels(4x2) 非 NULL', hbm !== 0)
168
- if (hbm) {
169
- const bmi = BITMAPINFOHEADER.encode({
170
- biSize: BITMAPINFOHEADER.size, biWidth: nw, biHeight: -nh,
171
- biPlanes: 1, biBitCount: 32, biSizeImage: nw * nh * 4,
172
- })
173
- const back = new PtrArrayBuffer<'BYTE'>(nw * nh * 4)
174
- const dc = GetDC(NULL)
175
- const rows = dc ? GetDIBits(dc, hbm, 0, nh, back, bmi.ptr, ColorUse.RGB_COLORS) : 0
176
- if (dc) ReleaseDC(NULL, dc)
177
- t.check('GetDIBits 取回全部扫描行', nh, rows)
178
- const back8 = new Uint8Array(back)
179
- let same = rows === nh
180
- for (let i = 0; i < back8.length; i++) if (back8[i] !== px8[i]) same = false
181
- t.checkTrue('像素逐字节一致(含 alpha,与旧 C 的 memcpy 等价)', same)
182
- t.checkTrue('DeleteObject(位图) 成功', DeleteObject(hbm) !== 0)
183
- }
184
- t.checkTrue('非法入参 → NULL(宽/高 ≤ 0、像素缓冲不足,同旧 C 语义)',
185
- createBitmapFromPixels(0, nh, px) === NULL && createBitmapFromPixels(nw, nh, new ArrayBuffer(4)) === NULL)
186
- },
187
- }
@@ -1,102 +0,0 @@
1
- import { WCHAR } from '../lib/ffi/bind.js';
2
- import { NULL, PtrArrayBuffer } from '../lib/ffi/ctype.js';
3
- import { CloseHandle, CopyFile, CreateDirectory, CreateFile, DeleteFile, FindClose, FindFirstFile, FindNextFile, GetFileAttributes, GetFileSize, GetTempPath, MoveFileEx, ReadFile, RemoveDirectory, SetFilePointer, WriteFile, } from '../lib/windows/kernel32.js';
4
- import { WIN32_FIND_DATAW } from '../lib/windows/structs.js';
5
- const GENERIC_READ = 0x80000000;
6
- const GENERIC_WRITE = 0x40000000;
7
- const FILE_ATTRIBUTE_DIRECTORY = 0x10;
8
- const INVALID32 = 0xffffffff;
9
- // ASCII payload ↔ UTF-16LE 缓冲互转(REG_SZ 同型,测试内联)
10
- function toWide(s) {
11
- const p = new PtrArrayBuffer(s.length * 2);
12
- const dv = new DataView(p);
13
- for (let i = 0; i < s.length; i++)
14
- dv.setUint16(i * 2, s.charCodeAt(i), true);
15
- return p;
16
- }
17
- function fromWide(p, chars) {
18
- const dv = new DataView(p);
19
- let out = '';
20
- for (let i = 0; i < chars; i++)
21
- out += String.fromCharCode(dv.getUint16(i * 2, true));
22
- return out;
23
- }
24
- export const suite = {
25
- name: 'files',
26
- run: (t) => {
27
- t.section('GetTempPath + CreateDirectory');
28
- const tmpW = WCHAR.alloc(260);
29
- const tmpLen = GetTempPath(260, tmpW);
30
- t.checkTrue(`temp path non-empty (${tmpLen})`, tmpLen > 0);
31
- const dir = `${WCHAR.decode(tmpW)}QuickWinTest`; // GetTempPath 尾带反斜杠
32
- // 目录可能残留自上次中断的 run:已存在(且是目录)则静默复用
33
- if (CreateDirectory(dir, NULL) === 0 &&
34
- (GetFileAttributes(dir) & FILE_ATTRIBUTE_DIRECTORY) === 0) {
35
- t.checkTrue('CreateDirectory', false);
36
- return;
37
- }
38
- const pathA = `${dir}\\a.txt`;
39
- const pathB = `${dir}\\b.txt`;
40
- const pathC = `${dir}\\c.txt`;
41
- const pathCn = `${dir}\\中文文件.txt`;
42
- t.section('CreateFile + WriteFile: write payload');
43
- const hW = CreateFile(pathA, GENERIC_WRITE, 0, NULL, 2 /* FileCreation.CREATE_ALWAYS */, 0x80 /* FILE_ATTRIBUTE_NORMAL */, NULL);
44
- t.checkTrue(`write handle valid (${hW})`, hW !== -1); // 失败 = INVALID_HANDLE_VALUE(-1)
45
- const payload = 'hello files';
46
- const wData = toWide(payload);
47
- const pWr = new PtrArrayBuffer(4);
48
- t.checkTrue('WriteFile ok', WriteFile(hW, wData, payload.length * 2, pWr, NULL) !== 0);
49
- t.check('bytes written', payload.length * 2, new DataView(pWr).getUint32(0, true));
50
- t.checkTrue('CloseHandle (writer)', CloseHandle(hW) !== 0);
51
- t.section('CreateFile(READ) + GetFileSize + SetFilePointer + ReadFile');
52
- const hR = CreateFile(pathA, GENERIC_READ, 0, NULL, 3 /* FileCreation.OPEN_EXISTING */, 0x80, NULL);
53
- t.checkTrue(`read handle valid (${hR})`, hR !== -1);
54
- t.check('GetFileSize', payload.length * 2, GetFileSize(hR, NULL));
55
- t.check('SetFilePointer BEGIN → 0', 0, SetFilePointer(hR, 0, NULL, 0 /* FileSeekFrom.BEGIN */));
56
- const rData = new PtrArrayBuffer(payload.length * 2);
57
- const pRd = new PtrArrayBuffer(4);
58
- t.checkTrue('ReadFile ok', ReadFile(hR, rData, payload.length * 2, pRd, NULL) !== 0);
59
- t.check('read roundtrip', payload, fromWide(rData, payload.length));
60
- // seek 到 END-4 验证负偏移 + 读尾 4 字节
61
- t.check('SetFilePointer END-4', payload.length * 2 - 4, SetFilePointer(hR, -4, NULL, 2 /* FileSeekFrom.END */));
62
- const rTail = new PtrArrayBuffer(8);
63
- ReadFile(hR, rTail, 4, pRd, NULL);
64
- t.check('seeked tail', payload.slice(-2), fromWide(rTail, 2));
65
- CloseHandle(hR);
66
- t.section('CopyFile + MoveFileEx + GetFileAttributes');
67
- const hB = CreateFile(pathB, GENERIC_WRITE, 0, NULL, 2 /* FileCreation.CREATE_ALWAYS */, 0x80, NULL);
68
- const bData = toWide('bb');
69
- WriteFile(hB, bData, 4, pWr, NULL);
70
- CloseHandle(hB);
71
- t.checkTrue('CopyFile ok', CopyFile(pathB, pathC, 0) !== 0);
72
- const hC = CreateFile(pathC, GENERIC_READ, 0, NULL, 3 /* FileCreation.OPEN_EXISTING */, 0x80, NULL);
73
- t.check('copied size', 4, GetFileSize(hC, NULL));
74
- t.checkTrue('CloseHandle (copy reader)', CloseHandle(hC) !== 0);
75
- t.checkTrue('MoveFileEx replace ok', MoveFileEx(pathB, pathC, 0x1 /* MOVEFILE_REPLACE_EXISTING */) !== 0);
76
- t.check('moved: source gone', INVALID32, GetFileAttributes(pathB));
77
- t.checkTrue('dest is file (not dir)', (GetFileAttributes(pathC) & FILE_ATTRIBUTE_DIRECTORY) === 0);
78
- t.section('FindFirstFile/FindNextFile: enumerate *.txt incl. CJK name');
79
- const hCn = CreateFile(pathCn, GENERIC_WRITE, 0, NULL, 2 /* FileCreation.CREATE_ALWAYS */, 0x80, NULL);
80
- CloseHandle(hCn); // 0 字节中文名文件
81
- const fd = WIN32_FIND_DATAW.alloc();
82
- const hFind = FindFirstFile(`${dir}\\a.txt`, fd.ptr);
83
- t.checkTrue(`FindFirstFile ok (${hFind})`, hFind !== -1);
84
- t.check('found name', 'a.txt', WIN32_FIND_DATAW.decode(fd).cFileName);
85
- t.checkTrue('FindNextFile exhausted = 0', FindNextFile(hFind, fd.ptr) === 0);
86
- t.checkTrue('FindClose', FindClose(hFind) !== 0);
87
- let count = 0;
88
- const hEnum = FindFirstFile(`${dir}\\*.txt`, fd.ptr);
89
- if (hEnum !== -1) {
90
- count = 1;
91
- while (FindNextFile(hEnum, fd.ptr) !== 0)
92
- count++;
93
- FindClose(hEnum);
94
- }
95
- t.check('enum *.txt count (a/c/CJK)', 3, count);
96
- t.section('DeleteFile + RemoveDirectory: cleanup');
97
- t.checkTrue('DeleteFile ×3', DeleteFile(pathA) !== 0 && DeleteFile(pathC) !== 0
98
- && DeleteFile(pathCn) !== 0);
99
- t.checkTrue('RemoveDirectory', RemoveDirectory(dir) !== 0);
100
- t.check('dir gone', INVALID32, GetFileAttributes(dir));
101
- },
102
- };