quickwin 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.en.md +1 -2
  2. package/README.md +19 -23
  3. package/examples/PdfCanvas.js +8 -37
  4. package/examples/PdfCanvas.tsx +11 -41
  5. package/examples/exec_server.js +20054 -1423
  6. package/examples/exec_server.tsx +489 -0
  7. package/examples/exec_server_worker.js +383 -33
  8. package/examples/exec_server_worker.ts +408 -30
  9. package/examples/pdf_preview2.js +25 -82
  10. package/examples/pdf_preview2.ts +27 -84
  11. package/examples/pdf_viewer.js +1318 -1178
  12. package/examples/pdf_viewer.tsx +16 -67
  13. package/examples/setres.js +22 -47
  14. package/examples/setres.ts +23 -57
  15. package/examples/test_gallery.js +1313 -1074
  16. package/examples/test_react_lazy.js +1284 -1056
  17. package/examples/test_react_listbox.js +1283 -1055
  18. package/examples/test_root_window.js +1279 -1051
  19. package/examples/test_tab.js +1284 -1056
  20. package/lib/abort.js +70 -0
  21. package/lib/abort.ts +69 -0
  22. package/lib/certs.js +214 -0
  23. package/lib/certs.ts +224 -0
  24. package/lib/exec-log.js +15 -0
  25. package/lib/exec-log.ts +30 -0
  26. package/lib/fetch.js +37 -4
  27. package/lib/fetch.ts +35 -4
  28. package/lib/ffi/bind.js +352 -0
  29. package/lib/ffi/bind.ts +442 -0
  30. package/lib/ffi/ctype.js +168 -0
  31. package/lib/ffi/ctype.ts +331 -0
  32. package/lib/ffi/index.js +6 -0
  33. package/lib/ffi/index.ts +6 -0
  34. package/lib/ffi/struct.js +412 -0
  35. package/lib/ffi/struct.ts +664 -0
  36. package/lib/http-common.js +43 -5
  37. package/lib/http-common.ts +48 -6
  38. package/lib/http-server.js +187 -29
  39. package/lib/http-server.ts +177 -26
  40. package/lib/react-qw/components/DateTimePicker.js +18 -37
  41. package/lib/react-qw/components/Input.js +3 -10
  42. package/lib/react-qw/components/Link.js +6 -19
  43. package/lib/react-qw/components/ListView.js +49 -154
  44. package/lib/react-qw/components/PathPicker.js +28 -88
  45. package/lib/react-qw/components/ScrollView.js +15 -8
  46. package/lib/react-qw/components/Slider.js +2 -1
  47. package/lib/react-qw/components/Tab.js +6 -22
  48. package/lib/react-qw/components/Tooltip.js +10 -21
  49. package/lib/react-qw/components/TreeView.js +8 -31
  50. package/lib/react-qw/index.js +4 -2
  51. package/lib/react-qw/index.ts +4 -2
  52. package/lib/react-qw/nmhdr.js +3 -24
  53. package/lib/react-qw/nmhdr.ts +5 -30
  54. package/lib/react-qw/props.js +7 -2
  55. package/lib/react-qw/props.ts +7 -3
  56. package/lib/react-qw/reconciler.js +23 -13
  57. package/lib/react-qw/reconciler.ts +22 -13
  58. package/lib/text-measure.js +18 -23
  59. package/lib/text-measure.ts +20 -25
  60. package/lib/websocket.js +12 -2
  61. package/lib/websocket.ts +15 -2
  62. package/lib/windows/comctl32.js +18 -0
  63. package/lib/windows/comctl32.ts +20 -0
  64. package/lib/windows/comdlg32.js +7 -0
  65. package/lib/windows/comdlg32.ts +9 -0
  66. package/lib/windows/gdi32.js +90 -0
  67. package/lib/windows/gdi32.ts +97 -0
  68. package/lib/windows/kernel32.js +66 -0
  69. package/lib/windows/kernel32.ts +76 -0
  70. package/lib/windows/ntdll.js +8 -0
  71. package/lib/windows/ntdll.ts +10 -0
  72. package/lib/windows/ole32.js +7 -0
  73. package/lib/windows/ole32.ts +9 -0
  74. package/lib/windows/shell32.js +11 -0
  75. package/lib/windows/shell32.ts +14 -0
  76. package/lib/windows/structs.js +384 -0
  77. package/lib/windows/structs.ts +422 -0
  78. package/lib/windows/user32.js +469 -0
  79. package/lib/windows/user32.ts +502 -0
  80. package/package.json +1 -1
  81. package/quickwin.d.ts +22 -37
  82. package/quickwin_const.d.ts +1 -0
  83. package/qwin-nowasm-x86.exe +0 -0
  84. package/qwin-nowasm.exe +0 -0
  85. package/qwin-x86.exe +0 -0
  86. package/qwin.exe +0 -0
  87. package/test/bench_ffi_callraw.js +236 -0
  88. package/test/bench_ffi_callraw.ts +236 -0
  89. package/test/bench_ffi_overhead.js +139 -0
  90. package/test/bench_ffi_overhead.ts +141 -0
  91. package/test/bench_sendmessage.js +104 -0
  92. package/test/bench_sendmessage.ts +100 -0
  93. package/test/bench_sendmessage_nonnull.js +187 -0
  94. package/test/bench_sendmessage_nonnull.ts +188 -0
  95. package/test/run.js +12 -4
  96. package/test/run.ts +13 -5
  97. package/test/test_basic_fetch.js +8 -0
  98. package/test/test_basic_fetch.ts +9 -0
  99. package/test/test_ffi.js +25 -43
  100. package/test/test_ffi.ts +33 -52
  101. package/test/test_ffi_abi.js +235 -0
  102. package/test/test_ffi_abi.ts +218 -0
  103. package/test/test_ffi_bind.js +538 -8
  104. package/test/test_ffi_bind.ts +559 -8
  105. package/test/test_ffi_struct.js +632 -88
  106. package/test/test_ffi_struct.ts +666 -91
  107. package/test/test_ffi_struct_layout.js +495 -0
  108. package/test/test_ffi_struct_layout.ts +543 -0
  109. package/test/test_ffi_user32.js +103 -0
  110. package/test/test_ffi_user32.ts +113 -0
  111. package/test/test_ffi_windows.js +101 -0
  112. package/test/test_ffi_windows.ts +113 -0
  113. package/test/test_helper.js +8 -1
  114. package/test/test_helper.ts +9 -1
  115. package/test/test_http_import.js +18 -0
  116. package/test/test_http_import.ts +19 -0
  117. package/test/test_http_server.js +73 -1
  118. package/test/test_http_server.ts +75 -1
  119. package/test/test_ipv6.js +2 -2
  120. package/test/test_ipv6.ts +2 -2
  121. package/test/test_net_fetch.js +70 -8
  122. package/test/test_net_fetch.ts +69 -7
  123. package/test/test_net_websocket.js +1 -1
  124. package/test/test_net_websocket.ts +1 -1
  125. package/test/test_react_complex.js +1294 -1066
  126. package/test/test_react_counter.js +1284 -1056
  127. package/test/test_react_flex.js +1304 -1069
  128. package/test/test_react_hidden.js +1287 -1059
  129. package/test/test_react_listview_link.js +1284 -1056
  130. package/test/test_react_notify.js +18534 -0
  131. package/test/test_react_render.js +1284 -1056
  132. package/test/test_wolfssl_read.js +3 -2
  133. package/test/test_wolfssl_read.ts +3 -1
  134. package/test/test_worker_net.js +35 -17
  135. package/test/test_worker_net.ts +27 -15
  136. package/tsconfig.json +1 -1
  137. package/examples/exec_server.ts +0 -80
  138. package/lib/ffi-bind.js +0 -85
  139. package/lib/ffi-bind.ts +0 -170
  140. package/lib/ffi-struct.js +0 -224
  141. package/lib/ffi-struct.ts +0 -239
  142. package/lib/win-common-structs.js +0 -39
  143. package/lib/win-common-structs.ts +0 -41
@@ -1,30 +1,20 @@
1
1
  import { useRef, useEffect, Children, cloneElement } from 'react';
2
2
  import * as gui from 'gui';
3
- import * as ffi from 'ffi';
4
- import { struct } from '../../ffi-struct.js';
5
- const TTTOOLINFOW = struct({
6
- cbSize: 'u32',
7
- uFlags: 'u32',
8
- hwnd: 'ptr',
9
- uId: 'ptr',
10
- rect: 'i32[4]',
11
- hinst: 'ptr',
12
- lpszText: 'ptr',
13
- lParam: 'ptr',
14
- lpReserved: 'ptr', // WinXP+ 追加字段(系统 sizeof 含之)
15
- });
3
+ import { NULL, PtrArrayBuffer } from '../../ffi/ctype.js';
4
+ import { TTTOOLINFOW } from '../../windows/structs.js';
5
+ import { SetWindowPos } from '../../windows/user32.js';
16
6
  function buildToolInfo(hTarget, text) {
17
7
  const size = TTTOOLINFOW.size;
18
8
  const textLen = (text.length + 1) * 2;
19
- const buf = new ArrayBuffer(size + textLen);
20
- TTTOOLINFOW.write({
9
+ const buf = new PtrArrayBuffer(size + textLen);
10
+ TTTOOLINFOW.encode({
21
11
  cbSize: size,
22
12
  uFlags: 16 /* gui.TtToolFlag.SUBCLASS */ | 1 /* gui.TtToolFlag.IDISHWND */,
23
13
  hwnd: hTarget,
24
14
  uId: hTarget,
25
15
  rect: [0, 0, 0, 0],
26
- hinst: 0,
27
- lpszText: ffi.bufferPtr(buf) + size,
16
+ hinst: NULL,
17
+ lpszText: buf.ptr + size,
28
18
  lParam: 0,
29
19
  lpReserved: 0,
30
20
  }, buf);
@@ -46,16 +36,15 @@ function Tooltip({ text, children, balloon }) {
46
36
  if (!hTT)
47
37
  return;
48
38
  hTTRef.current = hTT;
49
- gui.SetWindowPos(hTT, -1 /* gui.SetWindowPosHwnd.TOPMOST */, 0, 0, 0, 0, 2 /* gui.SetWindowPosFlag.SWP_NOMOVE */ | 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 16 /* gui.SetWindowPosFlag.SWP_NOACTIVATE */);
39
+ SetWindowPos(hTT, -1 /* gui.SetWindowPosHwnd.TOPMOST */, 0, 0, 0, 0, 2 /* gui.SetWindowPosFlag.SWP_NOMOVE */ | 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 16 /* gui.SetWindowPosFlag.SWP_NOACTIVATE */);
50
40
  const ti = buildToolInfo(hTarget, text);
51
- const tiPtr = ffi.bufferPtr(ti);
52
- gui.SendMessage(hTT, 1074 /* gui.TtMsg.ADDTOOLW */, 0, tiPtr);
41
+ gui.SendMessage(hTT, 1074 /* gui.TtMsg.ADDTOOLW */, 0, ti.ptr);
53
42
  gui.SendMessage(hTT, 1048 /* gui.TtMsg.SETMAXTIPWIDTH */, 0, 400);
54
43
  gui.SendMessage(hTT, 1025 /* gui.TtMsg.ACTIVATE */, 1, 0);
55
44
  return () => {
56
45
  if (hTT) {
57
46
  const ti2 = buildToolInfo(hTarget, text);
58
- gui.SendMessage(hTT, 1075 /* gui.TtMsg.DELTOOLW */, 0, ffi.bufferPtr(ti2));
47
+ gui.SendMessage(hTT, 1075 /* gui.TtMsg.DELTOOLW */, 0, ti2.ptr);
59
48
  gui.DestroyWindow(hTT);
60
49
  }
61
50
  hTTRef.current = null;
@@ -1,37 +1,14 @@
1
1
  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
- import * as ffi from 'ffi';
5
- import { struct } from '../../ffi-struct.js';
4
+ import { WCHAR } from '../../ffi/bind.js';
5
+ import { TVITEM, TVINSERTSTRUCT } from '../../windows/structs.js';
6
6
  import { nmCode } from '../nmhdr.js';
7
- const TVITEM = struct({
8
- mask: 'u32',
9
- hItem: 'ptr',
10
- state: 'u32',
11
- stateMask: 'u32',
12
- pszText: 'ptr',
13
- cchTextMax: 'i32',
14
- iImage: 'i32',
15
- iSelectedImage: 'i32',
16
- cChildren: 'i32',
17
- lParam: 'ptr',
18
- });
19
- const TVINSERTSTRUCT = struct({ hParent: 'ptr', hInsertAfter: 'ptr', item: TVITEM });
20
- function textToUtf16(s) {
21
- const buf = new ArrayBuffer((s.length + 1) * 2);
22
- const dv = new DataView(buf);
23
- for (let i = 0; i < s.length; i++)
24
- dv.setUint16(i * 2, s.charCodeAt(i), true);
25
- return buf;
26
- }
27
- function bufPtr(buf) {
28
- return ffi.bufferPtr(buf);
29
- }
30
7
  function buildTvItem(textPtr, cChildren) {
31
8
  let mask = 1 /* gui.TvIfFlag.TEXT */;
32
9
  if (cChildren > 0)
33
10
  mask |= 64 /* gui.TvIfFlag.CHILDREN */;
34
- return TVITEM.write({
11
+ return TVITEM.encode({
35
12
  mask, hItem: 0, state: 0, stateMask: 0,
36
13
  pszText: textPtr, cchTextMax: 260,
37
14
  iImage: 0, iSelectedImage: 0, cChildren, lParam: 0,
@@ -40,11 +17,11 @@ function buildTvItem(textPtr, cChildren) {
40
17
  function insertItems(hTree, nodes, parentHandle, hItemMap, keyMap) {
41
18
  for (const node of nodes) {
42
19
  const cChildren = node.children && node.children.length > 0 ? 1 : 0;
43
- const textBuf = textToUtf16(node.label);
44
- const itemBuf = buildTvItem(bufPtr(textBuf), cChildren);
45
- const item = TVITEM.read(itemBuf);
46
- const tvins = TVINSERTSTRUCT.write({ hParent: parentHandle, hInsertAfter: -65536 /* gui.TvInsertAfter.ROOT */, item });
47
- const hItem = gui.SendMessage(hTree, 4402 /* gui.TvMsg.INSERTITEMW */, 0, bufPtr(tvins));
20
+ const textBuf = WCHAR.encode(node.label);
21
+ const itemBuf = buildTvItem(textBuf.ptr, cChildren);
22
+ const item = TVITEM.decode(itemBuf);
23
+ const tvins = TVINSERTSTRUCT.encode({ hParent: parentHandle, hInsertAfter: -65536 /* gui.TvInsertAfter.ROOT */, item });
24
+ const hItem = gui.SendMessage(hTree, 4402 /* gui.TvMsg.INSERTITEMW */, 0, tvins.ptr);
48
25
  hItemMap.set(hItem, node);
49
26
  if (node.key)
50
27
  keyMap.set(node.key, hItem);
@@ -1,4 +1,5 @@
1
1
  import * as gui from 'gui';
2
+ import { DefWindowProc, GetSystemMetrics } from '../windows/user32.js';
2
3
  import reconciler, { scaleFactor, instancesByHwnd, forceFlexLayout } from './reconciler.js';
3
4
  const noop = () => { };
4
5
  let defaultClassRegistered = false;
@@ -20,7 +21,7 @@ function ensureDefaultClass() {
20
21
  return result;
21
22
  if (msg === 5 /* gui.WmMsg.SIZE */)
22
23
  forceFlexLayout(hwnd);
23
- return gui.DefWindowProc(hwnd, msg, wParam, lParam);
24
+ return DefWindowProc(hwnd, msg, wParam, lParam);
24
25
  });
25
26
  defaultClassRegistered = true;
26
27
  return DEFAULT_CLASS;
@@ -33,7 +34,8 @@ export function createRoot(container) {
33
34
  const ws = (cfg.ws ?? 0) | 13565952 /* gui.WindowStyle.OVERLAPPEDWINDOW */;
34
35
  const w = (cfg.width ?? 800) * scaleFactor;
35
36
  const h = (cfg.height ?? 600) * scaleFactor;
36
- const [sw, sh] = gui.GetScreenSize();
37
+ // GetScreenSize 等价内联:SM_CXSCREEN=0 / SM_CYSCREEN=1
38
+ const [sw, sh] = [GetSystemMetrics(0), GetSystemMetrics(1)];
37
39
  const x = cfg.x === undefined ? ((sw - w) / 2) | 0
38
40
  : cfg.x === -2147483648 /* gui.CreatePos.USEDEFAULT */ ? -2147483648 /* gui.CreatePos.USEDEFAULT */ : cfg.x * scaleFactor;
39
41
  const y = cfg.y === undefined ? ((sh - h) / 2) | 0
@@ -1,5 +1,6 @@
1
1
  import * as gui from 'gui'
2
2
  import React from 'react'
3
+ import { DefWindowProc, GetSystemMetrics } from '../windows/user32.js'
3
4
  import reconciler, { scaleFactor, instancesByHwnd, forceFlexLayout, type Instance } from './reconciler.js'
4
5
 
5
6
  const noop = () => { }
@@ -32,7 +33,7 @@ function ensureDefaultClass() {
32
33
  const result = entry?.onEvent?.({ hwnd, msg, wParam, lParam })
33
34
  if (typeof result === 'number') return result
34
35
  if (msg === gui.WmMsg.SIZE) forceFlexLayout(hwnd)
35
- return gui.DefWindowProc(hwnd, msg, wParam, lParam)
36
+ return DefWindowProc(hwnd, msg, wParam, lParam)
36
37
  })
37
38
  defaultClassRegistered = true
38
39
  return DEFAULT_CLASS
@@ -46,7 +47,8 @@ export function createRoot(container: gui.HWND | RootWindowConfig) {
46
47
  const ws = (cfg.ws ?? 0) | gui.WindowStyle.OVERLAPPEDWINDOW
47
48
  const w = (cfg.width ?? 800) * scaleFactor
48
49
  const h = (cfg.height ?? 600) * scaleFactor
49
- const [sw, sh] = gui.GetScreenSize()
50
+ // GetScreenSize 等价内联:SM_CXSCREEN=0 / SM_CYSCREEN=1
51
+ const [sw, sh] = [GetSystemMetrics(0), GetSystemMetrics(1)]
50
52
  const x = cfg.x === undefined ? ((sw - w) / 2) | 0
51
53
  : cfg.x === gui.CreatePos.USEDEFAULT ? gui.CreatePos.USEDEFAULT
52
54
  : cfg.x * scaleFactor
@@ -1,26 +1,5 @@
1
- import * as os from 'os';
2
- import * as ffi from 'ffi';
3
- import { struct } from '../ffi-struct.js';
4
- // 架构相关常量:32 位(ia32)/64 位(x64) Windows ABI
5
- export const IS64 = os.arch === 'x64';
6
- export const PTR_SIZE = IS64 ? 8 : 4;
7
- // NMHDR = hwndFrom(ptr) + idFrom(ptr) + code(i32)。
8
- // 用 ffi-struct 定义以复现 MSVC/嵌套结构的“尾 padding 传染”(内嵌 NMHDR 占满 24/12 字节)。
9
- export const NMHDR = struct({ hwndFrom: 'ptr', idFrom: 'ptr', code: 'i32' });
10
- export const NMHDR_SIZE = NMHDR.size; // 24 (x64) / 12 (ia32)
11
- export const NMHDR_CODE = NMHDR.offsetOf('code'); // 16 / 8
12
- export function readI32At(ptr, offset) {
13
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
14
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
15
- }
16
- export function readU32At(ptr, offset) {
17
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
18
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24) >>> 0;
19
- }
20
- export function readU16At(ptr, offset) {
21
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8);
22
- }
23
- // 读 NMHDR.code(WM_NOTIFY 通知码)
1
+ import { NMHDR } from '../windows/structs.js';
2
+ // 读 NMHDR.code(WM_NOTIFY 通知码):lParam 是通知结构的原生指针,decode 直接从该地址读
24
3
  export function nmCode(lParam) {
25
- return readI32At(lParam, NMHDR_CODE);
4
+ return NMHDR.decode(lParam).code;
26
5
  }
@@ -1,32 +1,7 @@
1
- import * as os from 'os'
2
- import * as ffi from 'ffi'
3
- import { struct } from '../ffi-struct.js'
1
+ import type { Ptr } from '../ffi/ctype.js'
2
+ import { NMHDR } from '../windows/structs.js'
4
3
 
5
- // 架构相关常量:32 位(ia32)/64 位(x64) Windows ABI
6
- export const IS64 = os.arch === 'x64'
7
- export const PTR_SIZE = IS64 ? 8 : 4
8
-
9
- // NMHDR = hwndFrom(ptr) + idFrom(ptr) + code(i32)。
10
- // 用 ffi-struct 定义以复现 MSVC/嵌套结构的“尾 padding 传染”(内嵌 NMHDR 占满 24/12 字节)。
11
- export const NMHDR = struct({ hwndFrom: 'ptr', idFrom: 'ptr', code: 'i32' })
12
- export const NMHDR_SIZE = NMHDR.size // 24 (x64) / 12 (ia32)
13
- export const NMHDR_CODE = NMHDR.offsetOf('code') // 16 / 8
14
-
15
- export function readI32At(ptr: number, offset: number): number {
16
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
17
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24)
18
- }
19
-
20
- export function readU32At(ptr: number, offset: number): number {
21
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
22
- (ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24) >>> 0
23
- }
24
-
25
- export function readU16At(ptr: number, offset: number): number {
26
- return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8)
27
- }
28
-
29
- // 读 NMHDR.code(WM_NOTIFY 通知码)
4
+ // 读 NMHDR.code(WM_NOTIFY 通知码):lParam 是通知结构的原生指针,decode 直接从该地址读
30
5
  export function nmCode(lParam: number): number {
31
- return readI32At(lParam, NMHDR_CODE)
32
- }
6
+ return NMHDR.decode(lParam as Ptr<'NMHDR'>).code
7
+ }
@@ -1,7 +1,12 @@
1
1
  import * as gui from 'gui';
2
+ import { SetWindowText, EnableWindow } from '../windows/user32.js';
2
3
  export function applyProps(instance, newProps, _oldProps) {
3
4
  const hwnd = instance.hwnd;
4
5
  instance.props = newProps;
6
+ // 延迟控件(DELAYED_CONTROLS)建窗前 hwnd 为 null:窗口操作此刻全是 no-op
7
+ // (text/ws/style 在 ensureChildWindow 建窗时应用),FFI 位不收 JS null——早退。
8
+ if (hwnd === null)
9
+ return;
5
10
  const textVal = 'text' in newProps ? newProps.text : (typeof newProps.children === 'string' ? newProps.children :
6
11
  typeof newProps.children === 'number' ? String(newProps.children) : undefined);
7
12
  if (textVal !== undefined) {
@@ -10,13 +15,13 @@ export function applyProps(instance, newProps, _oldProps) {
10
15
  const sel = gui.SendMessage(hwnd, 176 /* gui.EditMsg.GETSEL */, 0, 0);
11
16
  cursor = sel >>> 16;
12
17
  }
13
- gui.SetWindowText(hwnd, textVal);
18
+ SetWindowText(hwnd, textVal);
14
19
  if (instance.type === 'EDIT' && cursor >= 0) {
15
20
  gui.SendMessage(hwnd, 177 /* gui.EditMsg.SETSEL */, cursor, cursor);
16
21
  }
17
22
  }
18
23
  if ('disabled' in newProps) {
19
- gui.EnableWindow(hwnd, !newProps.disabled);
24
+ EnableWindow(hwnd, !newProps.disabled ? 1 : 0);
20
25
  }
21
26
  if ('hidden' in newProps) {
22
27
  gui.ShowWindow(hwnd, newProps.hidden ? 0 /* gui.ShowWindowCmd.HIDE */ : 5 /* gui.ShowWindowCmd.SHOW */);
@@ -1,4 +1,5 @@
1
1
  import * as gui from 'gui'
2
+ import { SetWindowText, EnableWindow } from '../windows/user32.js'
2
3
  import type { Instance, Props } from './reconciler.js'
3
4
 
4
5
  export function applyProps(
@@ -6,8 +7,11 @@ export function applyProps(
6
7
  newProps: Props,
7
8
  _oldProps: Props,
8
9
  ) {
9
- const hwnd = instance.hwnd!
10
+ const hwnd = instance.hwnd
10
11
  instance.props = newProps
12
+ // 延迟控件(DELAYED_CONTROLS)建窗前 hwnd 为 null:窗口操作此刻全是 no-op
13
+ // (text/ws/style 在 ensureChildWindow 建窗时应用),FFI 位不收 JS null——早退。
14
+ if (hwnd === null) return
11
15
 
12
16
  const textVal = 'text' in newProps ? newProps.text : (
13
17
  typeof newProps.children === 'string' ? newProps.children :
@@ -19,13 +23,13 @@ export function applyProps(
19
23
  const sel = gui.SendMessage(hwnd, gui.EditMsg.GETSEL, 0, 0)
20
24
  cursor = sel >>> 16
21
25
  }
22
- gui.SetWindowText(hwnd, textVal)
26
+ SetWindowText(hwnd, textVal)
23
27
  if (instance.type === 'EDIT' && cursor >= 0) {
24
28
  gui.SendMessage(hwnd, gui.EditMsg.SETSEL, cursor, cursor)
25
29
  }
26
30
  }
27
31
  if ('disabled' in newProps) {
28
- gui.EnableWindow(hwnd, !newProps.disabled)
32
+ EnableWindow(hwnd, !newProps.disabled ? 1 : 0)
29
33
  }
30
34
  if ('hidden' in newProps) {
31
35
  gui.ShowWindow(hwnd, newProps.hidden ? gui.ShowWindowCmd.HIDE : gui.ShowWindowCmd.SHOW)
@@ -3,27 +3,37 @@ import { createContext } from 'react';
3
3
  import { DefaultEventPriority, NoEventPriority } from 'react-reconciler/constants';
4
4
  import * as gui from 'gui';
5
5
  import * as os from 'os';
6
+ import { GetDC, ReleaseDC, GetWindowLongPtr, CallWindowProc, SetWindowPos, InvalidateRect, SetParent, SetWindowText } from '../windows/user32.js';
7
+ import { GetDeviceCaps } from '../windows/gdi32.js';
8
+ import { NULL } from '../ffi/ctype.js';
6
9
  import { applyProps } from './props.js';
7
10
  import { calculateFlexLayout } from './layout.js';
8
11
  import { getButtonIdealSize, measureTextForHwnd } from '../text-measure.js';
9
12
  const dpiFont = gui.CreateSystemDpiFont();
10
- export const scaleFactor = gui.GetScaleFactor();
13
+ // GetScaleFactor 等价内联:GetDC → GetDeviceCaps(LOGPIXELSX=88) → ReleaseDC 配对
14
+ export const scaleFactor = (() => {
15
+ const hdc = GetDC(NULL);
16
+ const dpi = hdc ? GetDeviceCaps(hdc, 88) : 96;
17
+ if (hdc)
18
+ ReleaseDC(NULL, hdc);
19
+ return dpi / 96;
20
+ })();
11
21
  const DELAYED_CONTROLS = new Set(['SysListView32', 'SysTreeView32', 'LISTBOX', 'COMBOBOX', 'msctls_trackbar32', 'SysDateTimePick32', 'SysLink']);
12
22
  function isDelayedControl(winClass) {
13
23
  return DELAYED_CONTROLS.has(winClass);
14
24
  }
15
25
  function setupWindowProc(instance, hwnd) {
16
26
  instance.hwnd = hwnd;
17
- const oldProc = gui.GetWindowLongPtr(hwnd, -4 /* gui.Gwlp.WNDPROC */);
27
+ const oldProc = GetWindowLongPtr(hwnd, -4 /* gui.Gwlp.WNDPROC */);
18
28
  instancesByHwnd.set(hwnd, instance);
19
29
  gui.SetWindowProc(hwnd, (hwnd, msg, wParam, lParam) => {
20
30
  const ev = instance.props.onEvent;
21
31
  if (!ev)
22
- return gui.CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
32
+ return CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
23
33
  if (typeof ev === 'object') {
24
- return ev.fn({ hwnd, msg, wParam, lParam, callOldWndProc: () => gui.CallWindowProc(oldProc, hwnd, msg, wParam, lParam) });
34
+ return ev.fn({ hwnd, msg, wParam, lParam, callOldWndProc: () => CallWindowProc(oldProc, hwnd, msg, wParam, lParam) });
25
35
  }
26
- const result = gui.CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
36
+ const result = CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
27
37
  const override = ev({ hwnd, msg, wParam, lParam });
28
38
  return typeof override === 'number' && Number.isInteger(override) ? override : result;
29
39
  });
@@ -94,8 +104,8 @@ function runFlexLayout(inst) {
94
104
  if (lr && lr.x === r.x && lr.y === r.y && lr.w === r.width && lr.h === r.height)
95
105
  continue;
96
106
  console.log('flex: set', child.type, child.hwnd, 'to', r.x, r.y, r.width, r.height);
97
- gui.SetWindowPos(child.hwnd, 0 /* gui.SetWindowPosHwnd.TOP */, (r.x + pl) * scaleFactor, (r.y + pt) * scaleFactor, r.width * scaleFactor, r.height * scaleFactor, 64 /* gui.SetWindowPosFlag.SWP_SHOWWINDOW */);
98
- gui.InvalidateRect(child.hwnd, null, true);
107
+ SetWindowPos(child.hwnd, 0 /* gui.SetWindowPosHwnd.TOP */, (r.x + pl) * scaleFactor, (r.y + pt) * scaleFactor, r.width * scaleFactor, r.height * scaleFactor, 64 /* gui.SetWindowPosFlag.SWP_SHOWWINDOW */);
108
+ InvalidateRect(child.hwnd, NULL, 1);
99
109
  child.lastRect = { x: r.x, y: r.y, w: r.width, h: r.height };
100
110
  }
101
111
  for (const c of children)
@@ -143,7 +153,7 @@ const hostConfig = {
143
153
  ensureChildWindow(child, parent.hwnd);
144
154
  }
145
155
  else {
146
- gui.SetParent(child.hwnd, parent.hwnd);
156
+ SetParent(child.hwnd, parent.hwnd ?? NULL);
147
157
  }
148
158
  if (DEBUG)
149
159
  console.log('[reconciler] appendInitialChild parent:', parent.hwnd, 'child:', child.hwnd);
@@ -154,7 +164,7 @@ const hostConfig = {
154
164
  ensureChildWindow(child, parent.hwnd);
155
165
  }
156
166
  else {
157
- gui.SetParent(child.hwnd, parent.hwnd);
167
+ SetParent(child.hwnd, parent.hwnd ?? NULL);
158
168
  }
159
169
  if (DEBUG)
160
170
  console.log('[reconciler] appendChild parent:', parent.hwnd, 'child:', child.hwnd);
@@ -165,7 +175,7 @@ const hostConfig = {
165
175
  ensureChildWindow(child, container);
166
176
  }
167
177
  else {
168
- gui.SetParent(child.hwnd, container);
178
+ SetParent(child.hwnd, container);
169
179
  }
170
180
  const rootInst = instancesByHwnd.get(container);
171
181
  if (rootInst)
@@ -178,7 +188,7 @@ const hostConfig = {
178
188
  ensureChildWindow(child, parent.hwnd);
179
189
  }
180
190
  else {
181
- gui.SetParent(child.hwnd, parent.hwnd);
191
+ SetParent(child.hwnd, parent.hwnd ?? NULL);
182
192
  }
183
193
  if (DEBUG)
184
194
  console.log('[reconciler] insertBefore parent:', parent.hwnd, 'child:', child.hwnd, 'before:', beforeChild.hwnd);
@@ -191,7 +201,7 @@ const hostConfig = {
191
201
  ensureChildWindow(child, container);
192
202
  }
193
203
  else {
194
- gui.SetParent(child.hwnd, container);
204
+ SetParent(child.hwnd, container);
195
205
  }
196
206
  const rootInst = instancesByHwnd.get(container);
197
207
  if (!rootInst)
@@ -246,7 +256,7 @@ const hostConfig = {
246
256
  },
247
257
  resetTextContent(_instance) { },
248
258
  commitTextUpdate(instance, _oldText, newText) {
249
- gui.SetWindowText(instance.hwnd, newText);
259
+ SetWindowText(instance.hwnd, newText);
250
260
  },
251
261
  hideInstance(instance) {
252
262
  if (instance.hwnd !== null)
@@ -3,6 +3,9 @@ import { createContext } from 'react'
3
3
  import { DefaultEventPriority, NoEventPriority } from 'react-reconciler/constants'
4
4
  import * as gui from 'gui'
5
5
  import * as os from 'os'
6
+ import { GetDC, ReleaseDC, GetWindowLongPtr, CallWindowProc, SetWindowPos, InvalidateRect, SetParent, SetWindowText } from '../windows/user32.js'
7
+ import { GetDeviceCaps } from '../windows/gdi32.js'
8
+ import { NULL } from '../ffi/ctype.js'
6
9
  import { applyProps } from './props.js'
7
10
  import { calculateFlexLayout, type FlexStyle } from './layout.js'
8
11
  import { getButtonIdealSize, measureTextForHwnd } from '../text-measure.js'
@@ -12,7 +15,13 @@ import type { WIntrinsicProps } from './jsx.js'
12
15
  declare const DEBUG: boolean
13
16
 
14
17
  const dpiFont = gui.CreateSystemDpiFont()
15
- export const scaleFactor = gui.GetScaleFactor()
18
+ // GetScaleFactor 等价内联:GetDC → GetDeviceCaps(LOGPIXELSX=88) → ReleaseDC 配对
19
+ export const scaleFactor = (() => {
20
+ const hdc = GetDC(NULL)
21
+ const dpi = hdc ? GetDeviceCaps(hdc, 88) : 96
22
+ if (hdc) ReleaseDC(NULL, hdc)
23
+ return dpi / 96
24
+ })()
16
25
 
17
26
  type Container = gui.HWND
18
27
  export type Props = WIntrinsicProps
@@ -33,15 +42,15 @@ function isDelayedControl(winClass: string): boolean {
33
42
 
34
43
  function setupWindowProc(instance: Instance, hwnd: gui.HWND) {
35
44
  instance.hwnd = hwnd
36
- const oldProc = gui.GetWindowLongPtr(hwnd, gui.Gwlp.WNDPROC)
45
+ const oldProc = GetWindowLongPtr(hwnd, gui.Gwlp.WNDPROC)
37
46
  instancesByHwnd.set(hwnd, instance)
38
47
  gui.SetWindowProc(hwnd, (hwnd: gui.HWND, msg: number, wParam: number, lParam: number) => {
39
48
  const ev = instance.props.onEvent
40
- if (!ev) return gui.CallWindowProc(oldProc, hwnd, msg, wParam, lParam)
49
+ if (!ev) return CallWindowProc(oldProc, hwnd, msg, wParam, lParam)
41
50
  if (typeof ev === 'object') {
42
- return ev.fn({ hwnd, msg, wParam, lParam, callOldWndProc: () => gui.CallWindowProc(oldProc, hwnd, msg, wParam, lParam) })
51
+ return ev.fn({ hwnd, msg, wParam, lParam, callOldWndProc: () => CallWindowProc(oldProc, hwnd, msg, wParam, lParam) })
43
52
  }
44
- const result = gui.CallWindowProc(oldProc, hwnd, msg, wParam, lParam)
53
+ const result = CallWindowProc(oldProc, hwnd, msg, wParam, lParam)
45
54
  const override = ev({ hwnd, msg, wParam, lParam })
46
55
  return typeof override === 'number' && Number.isInteger(override) ? override : result
47
56
  })
@@ -109,8 +118,8 @@ function runFlexLayout(inst: Instance) {
109
118
  const lr = child.lastRect
110
119
  if (lr && lr.x === r.x && lr.y === r.y && lr.w === r.width && lr.h === r.height) continue
111
120
  console.log('flex: set', child.type, child.hwnd, 'to', r.x, r.y, r.width, r.height)
112
- gui.SetWindowPos(child.hwnd!, gui.SetWindowPosHwnd.TOP, (r.x + pl) * scaleFactor, (r.y + pt) * scaleFactor, r.width * scaleFactor, r.height * scaleFactor, gui.SetWindowPosFlag.SWP_SHOWWINDOW)
113
- gui.InvalidateRect(child.hwnd!, null, true)
121
+ SetWindowPos(child.hwnd!, gui.SetWindowPosHwnd.TOP, (r.x + pl) * scaleFactor, (r.y + pt) * scaleFactor, r.width * scaleFactor, r.height * scaleFactor, gui.SetWindowPosFlag.SWP_SHOWWINDOW)
122
+ InvalidateRect(child.hwnd!, NULL, 1)
114
123
  child.lastRect = { x: r.x, y: r.y, w: r.width, h: r.height }
115
124
  }
116
125
  for (const c of children) runFlexLayout(c)
@@ -178,7 +187,7 @@ const hostConfig: QuickWinHostConfig = {
178
187
  if (child.hwnd === null) {
179
188
  ensureChildWindow(child, parent.hwnd!)
180
189
  } else {
181
- gui.SetParent(child.hwnd, parent.hwnd)
190
+ SetParent(child.hwnd, parent.hwnd ?? NULL)
182
191
  }
183
192
  if (DEBUG) console.log('[reconciler] appendInitialChild parent:', parent.hwnd, 'child:', child.hwnd)
184
193
  parent.children.push(child)
@@ -188,7 +197,7 @@ const hostConfig: QuickWinHostConfig = {
188
197
  if (child.hwnd === null) {
189
198
  ensureChildWindow(child, parent.hwnd!)
190
199
  } else {
191
- gui.SetParent(child.hwnd, parent.hwnd)
200
+ SetParent(child.hwnd, parent.hwnd ?? NULL)
192
201
  }
193
202
  if (DEBUG) console.log('[reconciler] appendChild parent:', parent.hwnd, 'child:', child.hwnd)
194
203
  parent.children.push(child)
@@ -198,7 +207,7 @@ const hostConfig: QuickWinHostConfig = {
198
207
  if (child.hwnd === null) {
199
208
  ensureChildWindow(child, container)
200
209
  } else {
201
- gui.SetParent(child.hwnd, container)
210
+ SetParent(child.hwnd, container)
202
211
  }
203
212
  const rootInst = instancesByHwnd.get(container)
204
213
  if (rootInst) rootInst.children.push(child)
@@ -209,7 +218,7 @@ const hostConfig: QuickWinHostConfig = {
209
218
  if (child.hwnd === null) {
210
219
  ensureChildWindow(child, parent.hwnd!)
211
220
  } else {
212
- gui.SetParent(child.hwnd, parent.hwnd)
221
+ SetParent(child.hwnd, parent.hwnd ?? NULL)
213
222
  }
214
223
  if (DEBUG) console.log('[reconciler] insertBefore parent:', parent.hwnd, 'child:', child.hwnd, 'before:', beforeChild.hwnd)
215
224
  const idx = parent.children.indexOf(beforeChild)
@@ -220,7 +229,7 @@ const hostConfig: QuickWinHostConfig = {
220
229
  if (child.hwnd === null) {
221
230
  ensureChildWindow(child, container)
222
231
  } else {
223
- gui.SetParent(child.hwnd, container)
232
+ SetParent(child.hwnd, container)
224
233
  }
225
234
  const rootInst = instancesByHwnd.get(container)
226
235
  if (!rootInst) throw new Error('insertInContainerBefore: no root instance for container')
@@ -270,7 +279,7 @@ const hostConfig: QuickWinHostConfig = {
270
279
  resetTextContent(_instance: Instance) { },
271
280
 
272
281
  commitTextUpdate(instance: Instance, _oldText: string, newText: string) {
273
- gui.SetWindowText(instance.hwnd!, newText)
282
+ SetWindowText(instance.hwnd!, newText)
274
283
  },
275
284
 
276
285
  hideInstance(instance: Instance) {
@@ -1,37 +1,32 @@
1
- import * as ffi from 'ffi';
2
1
  import * as gui from 'gui';
3
- import { bindLib } from './ffi-bind.js';
4
- const user32 = bindLib('user32.dll', {
5
- DrawTextW: 'ptr wstr i32 ptr i32 -> i32',
6
- GetDC: 'ptr -> ptr',
7
- ReleaseDC: 'ptr ptr -> i32',
8
- });
9
- const gdi32 = bindLib('gdi32.dll', {
10
- SelectObject: 'ptr ptr -> ptr',
11
- });
2
+ import { DrawText, GetDC, ReleaseDC } from './windows/user32.js';
3
+ import { SelectObject } from './windows/gdi32.js';
4
+ import { RECT, SIZE } from './windows/structs.js';
12
5
  export function measureText(hdc, text, maxWidth) {
13
- const rect = new ArrayBuffer(16);
14
- const dv = new DataView(rect);
15
- dv.setInt32(8, maxWidth, true);
16
- user32.DrawTextW(hdc, text, -1, rect, 1024 /* gui.DrawTextFlag.CALCRECT */);
17
- return { width: dv.getInt32(8, true), height: dv.getInt32(12, true) };
6
+ // rect 双向位不注册 encoder(CALCRECT 就地写回):encode(初值) 新建 + DeepPartial 初值一步到位
7
+ //(left/top/bottom 缺省跳过保持 0)→ 传 .ptr → decode 读回
8
+ const r = RECT.encode({ right: maxWidth });
9
+ DrawText(hdc, text, -1, r.ptr, 1024 /* gui.DrawTextFlag.CALCRECT */);
10
+ const { right, bottom } = RECT.decode(r);
11
+ return { width: right, height: bottom };
18
12
  }
19
13
  export function getButtonIdealSize(hwnd) {
20
- const size = new ArrayBuffer(8);
21
- gui.SendMessage(hwnd, 5633 /* gui.ButtonExtMsg.GETIDEALSIZE */, 0, ffi.bufferPtr(size));
22
- const dv = new DataView(size);
23
- return { width: dv.getInt32(0, true), height: dv.getInt32(4, true) };
14
+ // SIZE 纯出参位(GETIDEALSIZE 就地写回):alloc → 传 .ptr → decode 读回
15
+ const size = SIZE.alloc();
16
+ gui.SendMessage(hwnd, 5633 /* gui.ButtonExtMsg.GETIDEALSIZE */, 0, size.ptr);
17
+ const { cx, cy } = SIZE.decode(size);
18
+ return { width: cx, height: cy };
24
19
  }
25
20
  export function measureTextForHwnd(hwnd, text) {
26
- const hdc = user32.GetDC(hwnd);
21
+ const hdc = GetDC(hwnd);
27
22
  if (!hdc)
28
23
  return { width: 0, height: 0 };
29
24
  const hFont = gui.SendMessage(hwnd, 49 /* gui.WmMsg.GETFONT */, 0, 0);
30
- const oldFont = hFont ? gdi32.SelectObject(hdc, hFont) : 0;
25
+ const oldFont = hFont ? SelectObject(hdc, hFont) : 0;
31
26
  const result = measureText(hdc, text, 0);
32
27
  if (hFont)
33
- gdi32.SelectObject(hdc, oldFont);
34
- user32.ReleaseDC(hwnd, hdc);
28
+ SelectObject(hdc, oldFont);
29
+ ReleaseDC(hwnd, hdc);
35
30
  const wr = gui.GetWindowRect(hwnd);
36
31
  const cr = gui.GetClientRect(hwnd);
37
32
  if (wr && cr) {
@@ -1,39 +1,34 @@
1
- import * as ffi from 'ffi'
1
+ import type { Ptr } from './ffi/ctype.js'
2
2
  import * as gui from 'gui'
3
- import { bindLib } from './ffi-bind.js'
3
+ import { DrawText, GetDC, ReleaseDC } from './windows/user32.js'
4
+ import { SelectObject } from './windows/gdi32.js'
5
+ import { RECT, SIZE } from './windows/structs.js'
4
6
 
5
- const user32 = bindLib('user32.dll', {
6
- DrawTextW: 'ptr wstr i32 ptr i32 -> i32',
7
- GetDC: 'ptr -> ptr',
8
- ReleaseDC: 'ptr ptr -> i32',
9
- })
10
- const gdi32 = bindLib('gdi32.dll', {
11
- SelectObject: 'ptr ptr -> ptr',
12
- })
13
-
14
- export function measureText(hdc: number, text: string, maxWidth: number): { width: number; height: number } {
15
- const rect = new ArrayBuffer(16)
16
- const dv = new DataView(rect)
17
- dv.setInt32(8, maxWidth, true)
18
- user32.DrawTextW(hdc, text, -1, rect, gui.DrawTextFlag.CALCRECT)
19
- return { width: dv.getInt32(8, true), height: dv.getInt32(12, true) }
7
+ export function measureText(hdc: Ptr<'HDC'>, text: string, maxWidth: number): { width: number; height: number } {
8
+ // rect 双向位不注册 encoder(CALCRECT 就地写回):encode(初值) 新建 + DeepPartial 初值一步到位
9
+ //(left/top/bottom 缺省跳过保持 0)→ 传 .ptr → decode 读回
10
+ const r = RECT.encode({ right: maxWidth })
11
+ DrawText(hdc, text, -1, r.ptr, gui.DrawTextFlag.CALCRECT)
12
+ const { right, bottom } = RECT.decode(r)
13
+ return { width: right, height: bottom }
20
14
  }
21
15
 
22
16
  export function getButtonIdealSize(hwnd: gui.HWND): { width: number; height: number } {
23
- const size = new ArrayBuffer(8)
24
- gui.SendMessage(hwnd, gui.ButtonExtMsg.GETIDEALSIZE, 0, ffi.bufferPtr(size))
25
- const dv = new DataView(size)
26
- return { width: dv.getInt32(0, true), height: dv.getInt32(4, true) }
17
+ // SIZE 纯出参位(GETIDEALSIZE 就地写回):alloc → 传 .ptr → decode 读回
18
+ const size = SIZE.alloc()
19
+ gui.SendMessage(hwnd, gui.ButtonExtMsg.GETIDEALSIZE, 0, size.ptr)
20
+ const { cx, cy } = SIZE.decode(size)
21
+ return { width: cx, height: cy }
27
22
  }
28
23
 
29
24
  export function measureTextForHwnd(hwnd: gui.HWND, text: string): { width: number; height: number } {
30
- const hdc = user32.GetDC(hwnd)
25
+ const hdc = GetDC(hwnd)
31
26
  if (!hdc) return { width: 0, height: 0 }
32
27
  const hFont = gui.SendMessage(hwnd, gui.WmMsg.GETFONT, 0, 0)
33
- const oldFont = hFont ? gdi32.SelectObject(hdc, hFont) : 0
28
+ const oldFont = hFont ? SelectObject(hdc, hFont) : 0
34
29
  const result = measureText(hdc, text, 0)
35
- if (hFont) gdi32.SelectObject(hdc, oldFont)
36
- user32.ReleaseDC(hwnd, hdc)
30
+ if (hFont) SelectObject(hdc, oldFont)
31
+ ReleaseDC(hwnd, hdc)
37
32
  const wr = gui.GetWindowRect(hwnd)
38
33
  const cr = gui.GetClientRect(hwnd)
39
34
  if (wr && cr) {