quickwin 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.en.md +2 -1
  2. package/README.md +2 -1
  3. package/examples/PdfCanvas.js +3 -3
  4. package/examples/PdfCanvas.tsx +3 -3
  5. package/examples/pdf_preview2.js +34 -19
  6. package/examples/pdf_preview2.ts +34 -20
  7. package/examples/pdf_viewer.js +956 -215
  8. package/examples/pdf_viewer.tsx +29 -19
  9. package/examples/test_gallery.js +922 -194
  10. package/examples/test_react_lazy.js +905 -177
  11. package/examples/test_react_listbox.js +905 -177
  12. package/examples/test_root_window.js +905 -177
  13. package/examples/test_tab.js +905 -177
  14. package/lib/fetch.js +35 -7
  15. package/lib/fetch.ts +24 -4
  16. package/lib/ffi-bind.js +85 -0
  17. package/lib/ffi-bind.ts +170 -0
  18. package/lib/ffi-struct.js +224 -0
  19. package/lib/ffi-struct.ts +239 -0
  20. package/lib/react-qw/components/DateTimePicker.js +21 -15
  21. package/lib/react-qw/components/Link.js +4 -6
  22. package/lib/react-qw/components/ListView.js +84 -40
  23. package/lib/react-qw/components/PathPicker.js +40 -24
  24. package/lib/react-qw/components/Tab.js +21 -10
  25. package/lib/react-qw/components/Tooltip.js +27 -15
  26. package/lib/react-qw/components/TreeView.js +23 -33
  27. package/lib/react-qw/nmhdr.js +26 -0
  28. package/lib/react-qw/nmhdr.ts +32 -0
  29. package/lib/text-measure.js +14 -24
  30. package/lib/text-measure.ts +14 -24
  31. package/lib/win-common-structs.js +39 -0
  32. package/lib/win-common-structs.ts +41 -0
  33. package/main.js +46 -0
  34. package/package.json +2 -1
  35. package/quickwin.d.ts +7 -2
  36. package/qwin-nowasm-x86.exe +0 -0
  37. package/qwin-nowasm.exe +0 -0
  38. package/qwin-x86.exe +0 -0
  39. package/qwin.exe +0 -0
  40. package/test/run.js +3 -0
  41. package/test/run.ts +3 -0
  42. package/test/test_ffi.js +12 -8
  43. package/test/test_ffi.ts +12 -12
  44. package/test/test_ffi_bind.js +42 -0
  45. package/test/test_ffi_bind.ts +46 -0
  46. package/test/test_ffi_struct.js +201 -0
  47. package/test/test_ffi_struct.ts +223 -0
  48. package/test/test_net_fetch.js +62 -0
  49. package/test/test_net_fetch.ts +72 -0
  50. package/test/test_react_complex.js +908 -180
  51. package/test/test_react_counter.js +905 -177
  52. package/test/test_react_flex.js +905 -177
  53. package/test/test_react_hidden.js +907 -179
  54. package/test/test_react_listview_link.js +905 -177
  55. package/test/test_react_render.js +905 -177
  56. package/test/test_wolfssl_read.js +201 -0
  57. package/test/test_wolfssl_read.ts +181 -0
@@ -2,6 +2,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useRef, useEffect, useState } from 'react';
3
3
  import * as gui from 'gui';
4
4
  import * as ffi from 'ffi';
5
+ import { struct } from '../../ffi-struct.js';
6
+ import { nmCode } from '../nmhdr.js';
7
+ const TCITEMW = struct({
8
+ mask: 'u32',
9
+ dwState: 'u32',
10
+ dwStateMask: 'u32',
11
+ pszText: 'ptr',
12
+ cchTextMax: 'i32',
13
+ iImage: 'i32',
14
+ lParam: 'ptr',
15
+ });
5
16
  function textToUtf16(s) {
6
17
  const buf = new ArrayBuffer((s.length + 1) * 2);
7
18
  const dv = new DataView(buf);
@@ -9,10 +20,6 @@ function textToUtf16(s) {
9
20
  dv.setUint16(i * 2, s.charCodeAt(i), true);
10
21
  return buf;
11
22
  }
12
- function readI32(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
23
  export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSelectedIndex = 0, onChange, style }, ref) => {
17
24
  const [internalIndex, setInternalIndex] = useState(defaultSelectedIndex);
18
25
  const isControlled = controlledIndex !== undefined;
@@ -25,11 +32,15 @@ export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSe
25
32
  gui.SendMessage(h, 4873 /* gui.TcMsg.DELETEALLITEMS */, 0, 0);
26
33
  for (let i = 0; i < tabs.length; i++) {
27
34
  const titleBuf = textToUtf16(tabs[i].title);
28
- const tci = new ArrayBuffer(40);
29
- const dv = new DataView(tci);
30
- dv.setUint32(0, 1 /* gui.TcItemFlag.TEXT */, true);
31
- dv.setBigUint64(16, BigInt(ffi.bufferPtr(titleBuf)), true);
32
- dv.setInt32(24, tabs[i].title.length + 1, true);
35
+ const tci = TCITEMW.write({
36
+ mask: 1 /* gui.TcItemFlag.TEXT */,
37
+ dwState: 0,
38
+ dwStateMask: 0,
39
+ pszText: ffi.bufferPtr(titleBuf),
40
+ cchTextMax: tabs[i].title.length + 1,
41
+ iImage: 0,
42
+ lParam: 0,
43
+ });
33
44
  const tciPtr = ffi.bufferPtr(tci);
34
45
  gui.SendMessage(h, 4926 /* gui.TcMsg.INSERTITEMW */, i, tciPtr);
35
46
  }
@@ -45,7 +56,7 @@ export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSe
45
56
  const h = tabHwnd.current;
46
57
  if (!h)
47
58
  return;
48
- const code = readI32(e.lParam, 16);
59
+ const code = nmCode(e.lParam);
49
60
  // SysTabControl32 在 comctl32 v6 下不发标准的 TCN_SELCHANGE (-550),
50
61
  // 收到 NM_CLICK (-2) 或 TCN_SELCHANGING (-551) 时读实际选中项
51
62
  if (code === -552 /* gui.TcNotifyCode.SELCHANGING */ || code === -2 /* gui.SysLinkNotifyCode.CLICK */) {
@@ -1,25 +1,37 @@
1
1
  import { useRef, useEffect, Children, cloneElement } from 'react';
2
2
  import * as gui from 'gui';
3
3
  import * as ffi from 'ffi';
4
- const TTTOOLINFO_CBSIZE = 64;
5
- function setQword(dv, offset, val) {
6
- dv.setUint32(offset, val & 0xFFFFFFFF, true);
7
- dv.setUint32(offset + 4, Math.floor(val / 0x100000000), true);
8
- }
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
+ });
9
16
  function buildToolInfo(hTarget, text) {
10
- const textOff = TTTOOLINFO_CBSIZE;
17
+ const size = TTTOOLINFOW.size;
11
18
  const textLen = (text.length + 1) * 2;
12
- const buf = new ArrayBuffer(TTTOOLINFO_CBSIZE + textLen);
19
+ const buf = new ArrayBuffer(size + textLen);
20
+ TTTOOLINFOW.write({
21
+ cbSize: size,
22
+ uFlags: 16 /* gui.TtToolFlag.SUBCLASS */ | 1 /* gui.TtToolFlag.IDISHWND */,
23
+ hwnd: hTarget,
24
+ uId: hTarget,
25
+ rect: [0, 0, 0, 0],
26
+ hinst: 0,
27
+ lpszText: ffi.bufferPtr(buf) + size,
28
+ lParam: 0,
29
+ lpReserved: 0,
30
+ }, buf);
13
31
  const dv = new DataView(buf);
14
- dv.setUint32(0, TTTOOLINFO_CBSIZE, true);
15
- dv.setUint32(4, 16 /* gui.TtToolFlag.SUBCLASS */ | 1 /* gui.TtToolFlag.IDISHWND */, true);
16
- setQword(dv, 8, hTarget);
17
- setQword(dv, 16, hTarget);
18
32
  for (let i = 0; i < text.length; i++)
19
- dv.setUint16(textOff + i * 2, text.charCodeAt(i), true);
20
- dv.setUint16(textOff + text.length * 2, 0, true);
21
- const bufPtr = ffi.bufferPtr(buf);
22
- setQword(dv, 48, bufPtr + textOff);
33
+ dv.setUint16(size + i * 2, text.charCodeAt(i), true);
34
+ dv.setUint16(size + text.length * 2, 0, true);
23
35
  return buf;
24
36
  }
25
37
  function Tooltip({ text, children, balloon }) {
@@ -2,6 +2,21 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef, useRef, useEffect, useState } from 'react';
3
3
  import * as gui from 'gui';
4
4
  import * as ffi from 'ffi';
5
+ import { struct } from '../../ffi-struct.js';
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 });
5
20
  function textToUtf16(s) {
6
21
  const buf = new ArrayBuffer((s.length + 1) * 2);
7
22
  const dv = new DataView(buf);
@@ -9,51 +24,26 @@ function textToUtf16(s) {
9
24
  dv.setUint16(i * 2, s.charCodeAt(i), true);
10
25
  return buf;
11
26
  }
12
- function readI32(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
27
  function bufPtr(buf) {
17
28
  return ffi.bufferPtr(buf);
18
29
  }
19
- function setPtr(dv, offset, ptr) {
20
- dv.setUint32(offset, ptr & 0xFFFFFFFF, true);
21
- dv.setUint32(offset + 4, Math.floor(ptr / 0x100000000), true);
22
- }
23
30
  function buildTvItem(textPtr, cChildren) {
24
31
  let mask = 1 /* gui.TvIfFlag.TEXT */;
25
32
  if (cChildren > 0)
26
33
  mask |= 64 /* gui.TvIfFlag.CHILDREN */;
27
- const buf = new ArrayBuffer(56);
28
- const dv = new DataView(buf);
29
- dv.setUint32(0, mask, true); // mask (offset 0)
30
- setPtr(dv, 8, 0); // hItem
31
- dv.setUint32(16, 0, true); // state
32
- dv.setUint32(20, 0, true); // stateMask
33
- setPtr(dv, 24, textPtr); // pszText
34
- dv.setInt32(32, 260, true); // cchTextMax
35
- dv.setInt32(36, 0, true); // iImage
36
- dv.setInt32(40, 0, true); // iSelectedImage
37
- dv.setInt32(44, cChildren, true); // cChildren
38
- setPtr(dv, 48, 0); // lParam
39
- return buf;
40
- }
41
- function buildTvInsertStruct(hParent, hInsertAfter, itemBuf) {
42
- const buf = new ArrayBuffer(72);
43
- const dv = new DataView(buf);
44
- setPtr(dv, 0, hParent); // hParent (offset 0)
45
- setPtr(dv, 8, hInsertAfter); // hInsertAfter (offset 8)
46
- const src = new DataView(itemBuf);
47
- for (let i = 0; i < 56; i += 4)
48
- dv.setUint32(16 + i, src.getUint32(i, true), true);
49
- return buf;
34
+ return TVITEM.write({
35
+ mask, hItem: 0, state: 0, stateMask: 0,
36
+ pszText: textPtr, cchTextMax: 260,
37
+ iImage: 0, iSelectedImage: 0, cChildren, lParam: 0,
38
+ });
50
39
  }
51
40
  function insertItems(hTree, nodes, parentHandle, hItemMap, keyMap) {
52
41
  for (const node of nodes) {
53
42
  const cChildren = node.children && node.children.length > 0 ? 1 : 0;
54
43
  const textBuf = textToUtf16(node.label);
55
44
  const itemBuf = buildTvItem(bufPtr(textBuf), cChildren);
56
- const tvins = buildTvInsertStruct(parentHandle, -65536 /* gui.TvInsertAfter.ROOT */, itemBuf);
45
+ const item = TVITEM.read(itemBuf);
46
+ const tvins = TVINSERTSTRUCT.write({ hParent: parentHandle, hInsertAfter: -65536 /* gui.TvInsertAfter.ROOT */, item });
57
47
  const hItem = gui.SendMessage(hTree, 4402 /* gui.TvMsg.INSERTITEMW */, 0, bufPtr(tvins));
58
48
  hItemMap.set(hItem, node);
59
49
  if (node.key)
@@ -112,7 +102,7 @@ const TreeView = forwardRef(function TreeViewInner({ data, onSelect, defaultSele
112
102
  }, [selKey]);
113
103
  return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
114
104
  if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
115
- const code = readI32(e.lParam, 16);
105
+ const code = nmCode(e.lParam);
116
106
  if (code === -451 /* gui.TvNotifyCode.SELCHANGEDW */) {
117
107
  const h = tvRef.current;
118
108
  if (!h)
@@ -0,0 +1,26 @@
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 通知码)
24
+ export function nmCode(lParam) {
25
+ return readI32At(lParam, NMHDR_CODE);
26
+ }
@@ -0,0 +1,32 @@
1
+ import * as os from 'os'
2
+ import * as ffi from 'ffi'
3
+ import { struct } from '../ffi-struct.js'
4
+
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 通知码)
30
+ export function nmCode(lParam: number): number {
31
+ return readI32At(lParam, NMHDR_CODE)
32
+ }
@@ -1,29 +1,19 @@
1
1
  import * as ffi from 'ffi';
2
- import * as win from 'win';
3
2
  import * as gui from 'gui';
4
- function loadProc(lib, name) {
5
- const ptr = win.GetProcAddress(lib, name);
6
- if (!ptr)
7
- throw new Error(`Cannot load ${name}`);
8
- return ptr;
9
- }
10
- const user32 = win.LoadLibrary('user32.dll');
11
- const gdi32 = win.LoadLibrary('gdi32.dll');
12
- if (!user32 || !gdi32)
13
- throw new Error('Failed to load system DLLs');
14
- const DrawTextW = loadProc(user32, 'DrawTextW');
15
- const GetDC = loadProc(user32, 'GetDC');
16
- const ReleaseDC = loadProc(user32, 'ReleaseDC');
17
- const SelectObject = loadProc(gdi32, 'SelectObject');
18
- const FFI_TYPE_UINT64 = ffi.FFI_TYPE_UINT64;
19
- const FFI_S32 = ffi.FFI_TYPE_SINT32;
20
- const FFI_PTR = ffi.FFI_TYPE_POINTER;
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
+ });
21
12
  export function measureText(hdc, text, maxWidth) {
22
- const textBuf = new TextEncoder('utf-16le').encode(text + '\0').buffer;
23
13
  const rect = new ArrayBuffer(16);
24
14
  const dv = new DataView(rect);
25
15
  dv.setInt32(8, maxWidth, true);
26
- ffi.ffiCall(DrawTextW, [FFI_TYPE_UINT64, FFI_PTR, FFI_S32, FFI_PTR, FFI_S32], [hdc, textBuf, -1, rect, 1024 /* gui.DrawTextFlag.CALCRECT */], FFI_TYPE_UINT64);
16
+ user32.DrawTextW(hdc, text, -1, rect, 1024 /* gui.DrawTextFlag.CALCRECT */);
27
17
  return { width: dv.getInt32(8, true), height: dv.getInt32(12, true) };
28
18
  }
29
19
  export function getButtonIdealSize(hwnd) {
@@ -33,15 +23,15 @@ export function getButtonIdealSize(hwnd) {
33
23
  return { width: dv.getInt32(0, true), height: dv.getInt32(4, true) };
34
24
  }
35
25
  export function measureTextForHwnd(hwnd, text) {
36
- const hdc = ffi.ffiCall(GetDC, [FFI_TYPE_UINT64], [hwnd], FFI_TYPE_UINT64);
26
+ const hdc = user32.GetDC(hwnd);
37
27
  if (!hdc)
38
28
  return { width: 0, height: 0 };
39
29
  const hFont = gui.SendMessage(hwnd, 49 /* gui.WmMsg.GETFONT */, 0, 0);
40
- const oldFont = hFont ? ffi.ffiCall(SelectObject, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hdc, hFont], FFI_TYPE_UINT64) : 0;
30
+ const oldFont = hFont ? gdi32.SelectObject(hdc, hFont) : 0;
41
31
  const result = measureText(hdc, text, 0);
42
32
  if (hFont)
43
- ffi.ffiCall(SelectObject, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hdc, oldFont], FFI_TYPE_UINT64);
44
- ffi.ffiCall(ReleaseDC, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32);
33
+ gdi32.SelectObject(hdc, oldFont);
34
+ user32.ReleaseDC(hwnd, hdc);
45
35
  const wr = gui.GetWindowRect(hwnd);
46
36
  const cr = gui.GetClientRect(hwnd);
47
37
  if (wr && cr) {
@@ -1,31 +1,21 @@
1
1
  import * as ffi from 'ffi'
2
- import * as win from 'win'
3
2
  import * as gui from 'gui'
3
+ import { bindLib } from './ffi-bind.js'
4
4
 
5
- function loadProc(lib: win.HMODULE, name: string): number {
6
- const ptr = win.GetProcAddress(lib, name)
7
- if (!ptr) throw new Error(`Cannot load ${name}`)
8
- return ptr
9
- }
10
-
11
- const user32 = win.LoadLibrary('user32.dll')
12
- const gdi32 = win.LoadLibrary('gdi32.dll')
13
- if (!user32 || !gdi32) throw new Error('Failed to load system DLLs')
14
- const DrawTextW = loadProc(user32, 'DrawTextW')
15
- const GetDC = loadProc(user32, 'GetDC')
16
- const ReleaseDC = loadProc(user32, 'ReleaseDC')
17
- const SelectObject = loadProc(gdi32, 'SelectObject')
18
-
19
- const FFI_TYPE_UINT64 = ffi.FFI_TYPE_UINT64
20
- const FFI_S32 = ffi.FFI_TYPE_SINT32
21
- const FFI_PTR = ffi.FFI_TYPE_POINTER
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
+ })
22
13
 
23
14
  export function measureText(hdc: number, text: string, maxWidth: number): { width: number; height: number } {
24
- const textBuf = new TextEncoder('utf-16le').encode(text + '\0').buffer
25
15
  const rect = new ArrayBuffer(16)
26
16
  const dv = new DataView(rect)
27
17
  dv.setInt32(8, maxWidth, true)
28
- ffi.ffiCall(DrawTextW, [FFI_TYPE_UINT64, FFI_PTR, FFI_S32, FFI_PTR, FFI_S32], [hdc, textBuf, -1, rect, gui.DrawTextFlag.CALCRECT], FFI_TYPE_UINT64)
18
+ user32.DrawTextW(hdc, text, -1, rect, gui.DrawTextFlag.CALCRECT)
29
19
  return { width: dv.getInt32(8, true), height: dv.getInt32(12, true) }
30
20
  }
31
21
 
@@ -37,13 +27,13 @@ export function getButtonIdealSize(hwnd: gui.HWND): { width: number; height: num
37
27
  }
38
28
 
39
29
  export function measureTextForHwnd(hwnd: gui.HWND, text: string): { width: number; height: number } {
40
- const hdc = ffi.ffiCall(GetDC, [FFI_TYPE_UINT64], [hwnd], FFI_TYPE_UINT64)
30
+ const hdc = user32.GetDC(hwnd)
41
31
  if (!hdc) return { width: 0, height: 0 }
42
32
  const hFont = gui.SendMessage(hwnd, gui.WmMsg.GETFONT, 0, 0)
43
- const oldFont = hFont ? ffi.ffiCall(SelectObject, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hdc, hFont], FFI_TYPE_UINT64) : 0
33
+ const oldFont = hFont ? gdi32.SelectObject(hdc, hFont) : 0
44
34
  const result = measureText(hdc, text, 0)
45
- if (hFont) ffi.ffiCall(SelectObject, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hdc, oldFont], FFI_TYPE_UINT64)
46
- ffi.ffiCall(ReleaseDC, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32)
35
+ if (hFont) gdi32.SelectObject(hdc, oldFont)
36
+ user32.ReleaseDC(hwnd, hdc)
47
37
  const wr = gui.GetWindowRect(hwnd)
48
38
  const cr = gui.GetClientRect(hwnd)
49
39
  if (wr && cr) {
@@ -0,0 +1,39 @@
1
+ import { struct } from './ffi-struct.js';
2
+ // 跨组件/示例共享的 Windows 结构体(ffi-struct 按本机架构 MSVC 对齐布局)。
3
+ // OPENFILENAMEW(GetOpenFileNameW)。Win2000+ 定义含尾部 pvReserved/dwReserved/FlagsEx(即 XP 系统上的真实 sizeof=152/88):
4
+ export const OPENFILENAMEW = struct({
5
+ lStructSize: 'u32',
6
+ hwndOwner: 'ptr',
7
+ hInstance: 'ptr',
8
+ lpstrFilter: 'ptr',
9
+ lpstrCustomFilter: 'ptr',
10
+ nMaxCustFilter: 'u32',
11
+ nFilterIndex: 'u32',
12
+ lpstrFile: 'ptr',
13
+ nMaxFile: 'u32',
14
+ lpstrFileTitle: 'ptr',
15
+ nMaxFileTitle: 'u32',
16
+ lpstrInitialDir: 'ptr',
17
+ lpstrTitle: 'ptr',
18
+ Flags: 'u32',
19
+ nFileOffset: 'u16',
20
+ nFileExtension: 'u16',
21
+ lpstrDefExt: 'ptr',
22
+ lCustData: 'ptr',
23
+ lpfnHook: 'ptr',
24
+ lpTemplateName: 'ptr',
25
+ pvReserved: 'ptr',
26
+ dwReserved: 'u32',
27
+ FlagsEx: 'u32',
28
+ });
29
+ // BROWSEINFOW(SHBrowseForFolderW):
30
+ export const BROWSEINFOW = struct({
31
+ hwndOwner: 'ptr',
32
+ pidlRoot: 'ptr',
33
+ pszDisplayName: 'ptr',
34
+ lpszTitle: 'ptr',
35
+ ulFlags: 'u32',
36
+ lpfn: 'ptr',
37
+ lParam: 'ptr',
38
+ iImage: 'i32',
39
+ });
@@ -0,0 +1,41 @@
1
+ import { struct } from './ffi-struct.js'
2
+
3
+ // 跨组件/示例共享的 Windows 结构体(ffi-struct 按本机架构 MSVC 对齐布局)。
4
+ // OPENFILENAMEW(GetOpenFileNameW)。Win2000+ 定义含尾部 pvReserved/dwReserved/FlagsEx(即 XP 系统上的真实 sizeof=152/88):
5
+ export const OPENFILENAMEW = struct({
6
+ lStructSize: 'u32',
7
+ hwndOwner: 'ptr',
8
+ hInstance: 'ptr',
9
+ lpstrFilter: 'ptr',
10
+ lpstrCustomFilter: 'ptr',
11
+ nMaxCustFilter: 'u32',
12
+ nFilterIndex: 'u32',
13
+ lpstrFile: 'ptr',
14
+ nMaxFile: 'u32',
15
+ lpstrFileTitle: 'ptr',
16
+ nMaxFileTitle: 'u32',
17
+ lpstrInitialDir: 'ptr',
18
+ lpstrTitle: 'ptr',
19
+ Flags: 'u32',
20
+ nFileOffset: 'u16',
21
+ nFileExtension: 'u16',
22
+ lpstrDefExt: 'ptr',
23
+ lCustData: 'ptr',
24
+ lpfnHook: 'ptr',
25
+ lpTemplateName: 'ptr',
26
+ pvReserved: 'ptr',
27
+ dwReserved: 'u32',
28
+ FlagsEx: 'u32',
29
+ })
30
+
31
+ // BROWSEINFOW(SHBrowseForFolderW):
32
+ export const BROWSEINFOW = struct({
33
+ hwndOwner: 'ptr',
34
+ pidlRoot: 'ptr',
35
+ pszDisplayName: 'ptr',
36
+ lpszTitle: 'ptr',
37
+ ulFlags: 'u32',
38
+ lpfn: 'ptr',
39
+ lParam: 'ptr',
40
+ iImage: 'i32',
41
+ })
package/main.js ADDED
@@ -0,0 +1,46 @@
1
+ import { CallWindowProc, CreateSystemDpiFont, CreateWindow as CreateWindowWithoutScale, DefWindowProc, GetScaleFactor, GetWindowLongPtr, GetWindowText, RegisterClass, SendMessage, SetWindowProc, ShowWindow, } from "gui";
2
+ const scaleFactor = GetScaleFactor();
3
+ const dpiFont = CreateSystemDpiFont();
4
+ if (!dpiFont)
5
+ throw new Error('Failed to create DPI font');
6
+ const CreateWindow = (className, title, style, x, y, cx, cy, parent, id) => {
7
+ let hwnd = CreateWindowWithoutScale(className, title, style, x * scaleFactor, y * scaleFactor, cx * scaleFactor, cy * scaleFactor, parent, id);
8
+ if (hwnd)
9
+ SendMessage(hwnd, 48 /* WmMsg.SETFONT */, dpiFont, 1);
10
+ return hwnd;
11
+ };
12
+ let btn = null;
13
+ let edit = null;
14
+ let list = null;
15
+ function wndProc(hwnd, msg, wParam, lParam) {
16
+ if (msg === 1 /* WmMsg.CREATE */) {
17
+ btn = CreateWindow("BUTTON", "添加", 1073741824 /* WindowStyle.CHILD */ | 268435456 /* WindowStyle.VISIBLE */ | 0 /* ButtonStyle.PUSHBUTTON */, 120, 10, 80, 30, hwnd, null);
18
+ edit = CreateWindow("EDIT", "", 1073741824 /* WindowStyle.CHILD */ | 268435456 /* WindowStyle.VISIBLE */ | 8388608 /* WindowStyle.BORDER */, 10, 10, 100, 20, hwnd, null);
19
+ list = CreateWindow("LISTBOX", "", 1073741824 /* WindowStyle.CHILD */ | 268435456 /* WindowStyle.VISIBLE */ | 8388608 /* WindowStyle.BORDER */, 10, 40, 200, 100, hwnd, null);
20
+ if (!btn)
21
+ return 0;
22
+ const oldProc = GetWindowLongPtr(btn, -4 /* Gwlp.WNDPROC */);
23
+ SetWindowProc(btn, (hwnd, msg, wParam, lParam) => {
24
+ if (msg == 513 /* WmMsg.LBUTTONDOWN */) {
25
+ if (!edit)
26
+ return 0;
27
+ if (!list)
28
+ return 0;
29
+ const text = GetWindowText(edit);
30
+ if (text.trim())
31
+ SendMessage(list, 384 /* LbMsg.ADDSTRING */, 0, text);
32
+ return 0;
33
+ }
34
+ return CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
35
+ });
36
+ return 0;
37
+ }
38
+ else if (msg === 273 /* WmMsg.COMMAND */) {
39
+ return 0;
40
+ }
41
+ return DefWindowProc(hwnd, msg, wParam, lParam);
42
+ }
43
+ RegisterClass("MainWindow", wndProc);
44
+ var hwnd = CreateWindow("MainWindow", "Todo List", 13565952 /* WindowStyle.OVERLAPPEDWINDOW */, 0, 0, 600, 400, null, null);
45
+ if (hwnd)
46
+ ShowWindow(hwnd);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "quickwin",
4
- "version": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "description": "QuickJS Win32 runtime — GUI, networking, WASM, FFI, and more",
6
6
  "bin": {
7
7
  "quickwin": "qwin.exe",
@@ -18,6 +18,7 @@
18
18
  "qwin-nowasm.exe",
19
19
  "qwin-x86.exe",
20
20
  "qwin-nowasm-x86.exe",
21
+ "main.js",
21
22
  "lib/",
22
23
  "test/",
23
24
  "examples/",
package/quickwin.d.ts CHANGED
@@ -386,6 +386,7 @@ declare module "wolfssl" {
386
386
  function wolfSSL_connect(ssl: WOLFSSL): number;
387
387
  function wolfSSL_shutdown(ssl: WOLFSSL): number;
388
388
  function wolfSSL_write(ssl: WOLFSSL, buf: ArrayBuffer): number;
389
+ /** 成功返回 ArrayBuffer;EOF/暂无数据/错误返回 null(区分用 wolfSSL_get_error:6=ZERO_RETURN 关闭,2=WANT_READ) */
389
390
  function wolfSSL_read(ssl: WOLFSSL, sz: number): ArrayBuffer | null;
390
391
  function wolfSSL_get_error(ssl: WOLFSSL, ret: number): number;
391
392
  function wolfSSL_UseSNI(ssl: WOLFSSL, type: SniType, name: string, len?: number): number;
@@ -553,6 +554,8 @@ declare module "ffi" {
553
554
  type TYPE_OF_FFI_TYPE_SINT32 = number & { readonly __label: unique symbol }
554
555
  type TYPE_OF_FFI_TYPE_UINT64 = number & { readonly __label: unique symbol }
555
556
  type TYPE_OF_FFI_TYPE_SINT64 = number & { readonly __label: unique symbol }
557
+ type TYPE_OF_FFI_TYPE_FLOAT = number & { readonly __label: unique symbol }
558
+ type TYPE_OF_FFI_TYPE_DOUBLE = number & { readonly __label: unique symbol }
556
559
  type TYPE_OF_FFI_TYPE_POINTER = number & { readonly __label: unique symbol }
557
560
 
558
561
  const FFI_TYPE_VOID: TYPE_OF_FFI_TYPE_VOID;
@@ -564,10 +567,12 @@ declare module "ffi" {
564
567
  const FFI_TYPE_SINT32: TYPE_OF_FFI_TYPE_SINT32;
565
568
  const FFI_TYPE_UINT64: TYPE_OF_FFI_TYPE_UINT64;
566
569
  const FFI_TYPE_SINT64: TYPE_OF_FFI_TYPE_SINT64;
570
+ const FFI_TYPE_FLOAT: TYPE_OF_FFI_TYPE_FLOAT;
571
+ const FFI_TYPE_DOUBLE: TYPE_OF_FFI_TYPE_DOUBLE;
567
572
  const FFI_TYPE_POINTER: TYPE_OF_FFI_TYPE_POINTER;
568
573
 
569
- type FfiType = TYPE_OF_FFI_TYPE_VOID | TYPE_OF_FFI_TYPE_UINT8 | TYPE_OF_FFI_TYPE_SINT8 | TYPE_OF_FFI_TYPE_UINT16 | TYPE_OF_FFI_TYPE_SINT16 | TYPE_OF_FFI_TYPE_UINT32 | TYPE_OF_FFI_TYPE_SINT32 | TYPE_OF_FFI_TYPE_UINT64 | TYPE_OF_FFI_TYPE_SINT64 | TYPE_OF_FFI_TYPE_POINTER;
570
- type TypeArg<T extends FfiType> = T extends Exclude<FfiType, TYPE_OF_FFI_TYPE_VOID | TYPE_OF_FFI_TYPE_POINTER> ? number : T extends TYPE_OF_FFI_TYPE_POINTER ? (ArrayBuffer | null) : never;
574
+ type FfiType = TYPE_OF_FFI_TYPE_VOID | TYPE_OF_FFI_TYPE_UINT8 | TYPE_OF_FFI_TYPE_SINT8 | TYPE_OF_FFI_TYPE_UINT16 | TYPE_OF_FFI_TYPE_SINT16 | TYPE_OF_FFI_TYPE_UINT32 | TYPE_OF_FFI_TYPE_SINT32 | TYPE_OF_FFI_TYPE_UINT64 | TYPE_OF_FFI_TYPE_SINT64 | TYPE_OF_FFI_TYPE_FLOAT | TYPE_OF_FFI_TYPE_DOUBLE | TYPE_OF_FFI_TYPE_POINTER;
575
+ type TypeArg<T extends FfiType> = T extends Exclude<FfiType, TYPE_OF_FFI_TYPE_VOID | TYPE_OF_FFI_TYPE_POINTER> ? number : T extends TYPE_OF_FFI_TYPE_POINTER ? (ArrayBuffer | number | null) : never;
571
576
  type TypeArgs<T extends FfiType[], Args extends(number | null | ArrayBuffer)[] = []> = T extends [infer T1 extends FfiType, ...infer RES extends FfiType[]] ? TypeArgs<RES, [...Args, TypeArg<T1>]> : Args;
572
577
 
573
578
  function ffiCall<const T extends Exclude<FfiType, TYPE_OF_FFI_TYPE_VOID>[], const R extends FfiType>(func: number, argTypes: T, args: TypeArgs<T>, retType: R): R extends TYPE_OF_FFI_TYPE_VOID ? undefined : R extends TYPE_OF_FFI_TYPE_POINTER ? number | null : TypeArg<R>;
Binary file
package/qwin-nowasm.exe CHANGED
Binary file
package/qwin-x86.exe CHANGED
Binary file
package/qwin.exe CHANGED
Binary file
package/test/run.js CHANGED
@@ -23,7 +23,10 @@ const suiteDefs = [
23
23
  { name: 'mupdf-twice', file: './test_mupdf_twice.js', tags: ['wasm', 'mupdf'] },
24
24
  { name: 'mupdf-render', file: './test_mupdf_render.js', tags: ['wasm', 'mupdf'] },
25
25
  { name: 'ffi', file: './test_ffi.js', tags: [] },
26
+ { name: 'ffi-bind', file: './test_ffi_bind.js', tags: [] },
27
+ { name: 'ffi-struct', file: './test_ffi_struct.js', tags: [] },
26
28
  { name: 'net-fetch', file: './test_net_fetch.js', tags: ['net'] },
29
+ { name: 'net-wolfssl-read', file: './test_wolfssl_read.js', tags: ['net'] },
27
30
  { name: 'net-websocket', file: './test_net_websocket.js', tags: ['net'] },
28
31
  { name: 'net-event', file: './test_net_event.js', tags: ['net'] },
29
32
  { name: 'http-server', file: './test_http_server.js', tags: ['net'] },
package/test/run.ts CHANGED
@@ -27,7 +27,10 @@ const suiteDefs = [
27
27
  { name: 'mupdf-twice', file: './test_mupdf_twice.js', tags: ['wasm', 'mupdf'] },
28
28
  { name: 'mupdf-render', file: './test_mupdf_render.js', tags: ['wasm', 'mupdf'] },
29
29
  { name: 'ffi', file: './test_ffi.js', tags: [] },
30
+ { name: 'ffi-bind', file: './test_ffi_bind.js', tags: [] },
31
+ { name: 'ffi-struct', file: './test_ffi_struct.js', tags: [] },
30
32
  { name: 'net-fetch', file: './test_net_fetch.js', tags: ['net'] },
33
+ { name: 'net-wolfssl-read', file: './test_wolfssl_read.js', tags: ['net'] },
31
34
  { name: 'net-websocket', file: './test_net_websocket.js', tags: ['net'] },
32
35
  { name: 'net-event', file: './test_net_event.js', tags: ['net'] },
33
36
  { name: 'http-server', file: './test_http_server.js', tags: ['net'] },
package/test/test_ffi.js CHANGED
@@ -159,14 +159,18 @@ export const suite = {
159
159
  else {
160
160
  std.printf(' no printers installed (skip)\n');
161
161
  }
162
- t.section('pointer arg type check');
163
- let threw = false;
164
- try {
165
- ffi.ffiCall(enumPrinters, [ffi.FFI_TYPE_POINTER], [123], ffi.FFI_TYPE_VOID);
166
- }
167
- catch (e) {
168
- threw = true;
162
+ t.section('PTR accepts raw number handles (XP 32-bit fix)');
163
+ const hUser32 = win.LoadLibrary('user32.dll');
164
+ t.checkTrue('LoadLibrary("user32.dll") succeeds', hUser32 !== null);
165
+ if (hUser32) {
166
+ const getDC = win.GetProcAddress(hUser32, 'GetDC');
167
+ t.checkTrue('GetProcAddress("GetDC") succeeds', getDC !== null);
168
+ if (getDC) {
169
+ const screenDc = ffi.ffiCall(getDC, [ffi.FFI_TYPE_POINTER], [0], ffi.FFI_TYPE_POINTER);
170
+ t.checkTrue('PTR slot takes number (NULL hwnd) and returns a DC handle', screenDc !== 0 && screenDc !== null);
171
+ const bogus = ffi.ffiCall(getDC, [ffi.FFI_TYPE_POINTER], [0x12345678], ffi.FFI_TYPE_POINTER);
172
+ t.checkTrue('PTR slot takes non-zero number without throwing', bogus === 0 || bogus === null);
173
+ }
169
174
  }
170
- t.checkTrue('non-ArrayBuffer pointer arg throws TypeError', threw);
171
175
  }
172
176
  };