quickwin 1.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 (175) hide show
  1. package/README.en.md +197 -0
  2. package/README.md +196 -0
  3. package/examples/PdfCanvas.js +67 -0
  4. package/examples/PdfCanvas.tsx +83 -0
  5. package/examples/pdf_preview2.js +431 -0
  6. package/examples/pdf_preview2.ts +375 -0
  7. package/examples/pdf_viewer.js +17623 -0
  8. package/examples/pdf_viewer.tsx +279 -0
  9. package/examples/test_gallery.js +17452 -0
  10. package/examples/test_gallery.tsx +285 -0
  11. package/examples/test_react_lazy.js +17336 -0
  12. package/examples/test_react_lazy.tsx +63 -0
  13. package/examples/test_react_listbox.js +17339 -0
  14. package/examples/test_react_listbox.tsx +71 -0
  15. package/examples/test_root_window.js +17313 -0
  16. package/examples/test_root_window.tsx +41 -0
  17. package/examples/test_tab.js +17339 -0
  18. package/examples/test_tab.tsx +75 -0
  19. package/examples/tray_demo.js +79 -0
  20. package/examples/tray_demo.tsx +81 -0
  21. package/lib/assert.js +8 -0
  22. package/lib/assert.ts +7 -0
  23. package/lib/cache-utils.js +43 -0
  24. package/lib/cache-utils.ts +40 -0
  25. package/lib/dumpRects.js +25 -0
  26. package/lib/dumpRects.ts +25 -0
  27. package/lib/fetch.js +490 -0
  28. package/lib/fetch.ts +502 -0
  29. package/lib/http-common.js +342 -0
  30. package/lib/http-common.ts +399 -0
  31. package/lib/http-server.js +416 -0
  32. package/lib/http-server.ts +424 -0
  33. package/lib/polyfill.js +156 -0
  34. package/lib/polyfill.ts +171 -0
  35. package/lib/react-qw/components/Button.js +8 -0
  36. package/lib/react-qw/components/CheckBox.js +31 -0
  37. package/lib/react-qw/components/ComboBox.js +42 -0
  38. package/lib/react-qw/components/DateTimePicker.js +96 -0
  39. package/lib/react-qw/components/Input.js +50 -0
  40. package/lib/react-qw/components/Link.js +43 -0
  41. package/lib/react-qw/components/ListBox.js +48 -0
  42. package/lib/react-qw/components/ListView.js +328 -0
  43. package/lib/react-qw/components/PathPicker.js +136 -0
  44. package/lib/react-qw/components/ProgressBar.js +22 -0
  45. package/lib/react-qw/components/RadioButton.js +31 -0
  46. package/lib/react-qw/components/ScrollView.js +139 -0
  47. package/lib/react-qw/components/Slider.js +45 -0
  48. package/lib/react-qw/components/Tab.js +61 -0
  49. package/lib/react-qw/components/Tooltip.js +55 -0
  50. package/lib/react-qw/components/TreeView.js +130 -0
  51. package/lib/react-qw/components/index.js +16 -0
  52. package/lib/react-qw/components/index.ts +17 -0
  53. package/lib/react-qw/index.js +73 -0
  54. package/lib/react-qw/index.ts +90 -0
  55. package/lib/react-qw/jsx.d.ts +34 -0
  56. package/lib/react-qw/layout.js +82 -0
  57. package/lib/react-qw/layout.ts +111 -0
  58. package/lib/react-qw/props.js +27 -0
  59. package/lib/react-qw/props.ts +36 -0
  60. package/lib/react-qw/reconciler.js +333 -0
  61. package/lib/react-qw/reconciler.ts +373 -0
  62. package/lib/stream.js +2 -0
  63. package/lib/stream.ts +22 -0
  64. package/lib/text-codec.js +197 -0
  65. package/lib/text-codec.ts +171 -0
  66. package/lib/text-measure.js +52 -0
  67. package/lib/text-measure.ts +54 -0
  68. package/lib/url.js +278 -0
  69. package/lib/url.ts +201 -0
  70. package/lib/vendor/web-streams/ponyfill.d.mts +829 -0
  71. package/lib/vendor/web-streams/ponyfill.mjs +8 -0
  72. package/lib/websocket.js +541 -0
  73. package/lib/websocket.ts +555 -0
  74. package/package.json +44 -0
  75. package/quickwin.d.ts +594 -0
  76. package/quickwin_const.d.ts +900 -0
  77. package/qwin-nowasm.exe +0 -0
  78. package/qwin.exe +0 -0
  79. package/test/add.wasm +0 -0
  80. package/test/complex.wasm +0 -0
  81. package/test/complex_imports.wasm +0 -0
  82. package/test/debug/http_server_e2e.js +31 -0
  83. package/test/debug/minwasm.js +52 -0
  84. package/test/debug/mupdf_debug.js +104 -0
  85. package/test/debug/wasm.js +122 -0
  86. package/test/debug/wasm_export_debug.js +64 -0
  87. package/test/debug/wasm_names.js +46 -0
  88. package/test/global_imports.wasm +0 -0
  89. package/test/import_func.wasm +0 -0
  90. package/test/imports.wasm +0 -0
  91. package/test/run.js +91 -0
  92. package/test/run.ts +96 -0
  93. package/test/sjlj.wasm +0 -0
  94. package/test/test_basic.js +7 -0
  95. package/test/test_basic.ts +9 -0
  96. package/test/test_basic_fetch.js +34 -0
  97. package/test/test_basic_fetch.ts +33 -0
  98. package/test/test_brotli.js +48 -0
  99. package/test/test_brotli.ts +52 -0
  100. package/test/test_fetch_cache.js +169 -0
  101. package/test/test_fetch_cache.ts +187 -0
  102. package/test/test_ffi.js +157 -0
  103. package/test/test_ffi.ts +174 -0
  104. package/test/test_frame_encoding.js +128 -0
  105. package/test/test_frame_encoding.ts +132 -0
  106. package/test/test_helper.js +84 -0
  107. package/test/test_helper.ts +80 -0
  108. package/test/test_http_import.js +78 -0
  109. package/test/test_http_import.ts +74 -0
  110. package/test/test_http_server.js +142 -0
  111. package/test/test_http_server.ts +136 -0
  112. package/test/test_import_dir.js +4 -0
  113. package/test/test_import_dir.ts +5 -0
  114. package/test/test_ipv6.js +134 -0
  115. package/test/test_ipv6.ts +129 -0
  116. package/test/test_jsx.js +1314 -0
  117. package/test/test_meta_url.js +3 -0
  118. package/test/test_meta_url.ts +4 -0
  119. package/test/test_mupdf_render.js +69 -0
  120. package/test/test_mupdf_render.ts +74 -0
  121. package/test/test_mupdf_twice.js +77 -0
  122. package/test/test_mupdf_twice.ts +81 -0
  123. package/test/test_mupdf_wasm.js +33 -0
  124. package/test/test_mupdf_wasm.ts +30 -0
  125. package/test/test_net_event.js +57 -0
  126. package/test/test_net_event.ts +55 -0
  127. package/test/test_net_fetch.js +259 -0
  128. package/test/test_net_fetch.ts +238 -0
  129. package/test/test_net_websocket.js +180 -0
  130. package/test/test_net_websocket.ts +164 -0
  131. package/test/test_polyfill.js +144 -0
  132. package/test/test_polyfill.ts +159 -0
  133. package/test/test_react_complex.js +17440 -0
  134. package/test/test_react_counter.js +17331 -0
  135. package/test/test_react_flex.js +17643 -0
  136. package/test/test_react_hidden.js +17336 -0
  137. package/test/test_react_listview_link.js +17371 -0
  138. package/test/test_react_render.js +17326 -0
  139. package/test/test_url.js +173 -0
  140. package/test/test_url.ts +183 -0
  141. package/test/test_wasm_basic.js +85 -0
  142. package/test/test_wasm_basic.ts +73 -0
  143. package/test/test_wasm_import_global.js +41 -0
  144. package/test/test_wasm_import_global.ts +45 -0
  145. package/test/test_wasm_sjlj.js +145 -0
  146. package/test/test_wasm_sjlj.ts +125 -0
  147. package/test/test_wasm_types.js +97 -0
  148. package/test/test_wasm_types.ts +124 -0
  149. package/test/test_worker.js +21 -0
  150. package/test/test_worker.ts +24 -0
  151. package/test/test_worker_fetch.js +26 -0
  152. package/test/test_worker_fetch.ts +27 -0
  153. package/test/test_worker_net.js +51 -0
  154. package/test/test_worker_net.ts +54 -0
  155. package/test/test_worker_wasm_concurrent.js +44 -0
  156. package/test/test_worker_wasm_concurrent.ts +45 -0
  157. package/test/types.wasm +0 -0
  158. package/test/worker_fetch.js +13 -0
  159. package/test/worker_fetch.ts +13 -0
  160. package/test/worker_helper.js +1 -0
  161. package/test/worker_helper.ts +1 -0
  162. package/test/worker_http.js +18 -0
  163. package/test/worker_http.ts +20 -0
  164. package/test/worker_import_lib.js +12 -0
  165. package/test/worker_import_lib.ts +13 -0
  166. package/test/worker_module.js +16 -0
  167. package/test/worker_module.ts +17 -0
  168. package/test/worker_wasm_concurrent.js +22 -0
  169. package/test/worker_wasm_concurrent.ts +22 -0
  170. package/tsconfig.json +27 -0
  171. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
  172. package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
  173. package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
  174. package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
  175. package/vendor/mupdf-wasm/mupdf.js +3317 -0
package/README.en.md ADDED
@@ -0,0 +1,197 @@
1
+ > [中文文档](README.md) · **English**
2
+
3
+ # QuickWin
4
+
5
+ QuickJS Win32 runtime — run JavaScript with native Windows GUI, networking, WASM, FFI, and more.
6
+
7
+ ```bash
8
+ npm i -g quickwin
9
+ quickwin script.js
10
+ ```
11
+
12
+ ## Features
13
+
14
+ - **Win32 GUI** — native windows, buttons, edit boxes, list boxes, tray icons, popup menus
15
+ - **React renderer** — declarative GUI in JSX with `useState`/`useEffect`, diff updates ([react-qw](lib/react-qw/))
16
+ - **HTTP/HTTPS** — `fetch()` API, Brotli decompression, chunked transfer, conditional caching
17
+ - **WebSocket** — full RFC 6455 implementation, ws:// + wss://
18
+ - **WebAssembly** — WAMR-based, supports `WebAssembly.*` standard API
19
+ - **FFI** — call any DLL function via libffi
20
+ - **mupdf** — embedded PDF rendering
21
+ - **Polyfills** — `TextEncoder`, `URL`, `URLSearchParams`, `btoa`/`atob`, `crypto.subtle`, `setTimeout`
22
+ - **Dynamic import** — `import('https://esm.sh/...')`, no npm install needed
23
+
24
+ ## CLI
25
+
26
+ ```bash
27
+ quickwin script.js # run a script
28
+ quickwin -o CON script.js # run with console (AllocConsole)
29
+ quickwin -o LOG script.js # run with auto-generated log file
30
+ quickwin -e "console.log('hi')" # execute expression
31
+ quickwin -- script.js --flag # -- stops option parsing
32
+ ```
33
+
34
+ ### Options
35
+
36
+ | Flag | Description |
37
+ |------|-------------|
38
+ | `-e <expr>` | Execute expression instead of a file |
39
+ | `-o CON` | Allocate console (`AllocConsole`); only needed for GUI-subsystem builds |
40
+ | `-o LOG` | Redirect stdout+stderr to `log_YYYY_MM_DD_HH_MM_SS.txt` (exe directory) |
41
+ | `-o <file>` | Redirect stdout+stderr to specified file |
42
+ | `-d` | Enable HTTP debug logging |
43
+ | `--` | Stop option parsing, remaining args passed to script |
44
+
45
+ All unknown flags are transparently forwarded to `scriptArgs` — no error,
46
+ no consumption. Script file is the first non-flag argument after option parsing.
47
+
48
+ ## Embedded Script
49
+
50
+ You can embed a JS script directly into the `qwin.exe` binary — no recompilation needed.
51
+
52
+ ### Format (appended to exe)
53
+
54
+ ```
55
+ [JS bytes (N)] [N: uint32 LE] [magic "QWJS"]
56
+ ```
57
+
58
+ ### Usage
59
+
60
+ ```bash
61
+ # Embed a script
62
+ powershell -ExecutionPolicy Bypass -File scripts/embed-js.ps1 -ExePath _build/qwin.exe -JsFile script.js
63
+
64
+ # Or via make
65
+ make embed-js JS_EMBED=script.js
66
+
67
+ # Embed a script brotli-compressed (smaller exe, decompressed at runtime)
68
+ make embed-js-br JS_EMBED=script.js
69
+
70
+ # Run it (no file argument needed)
71
+ qwin.exe
72
+ ```
73
+
74
+ When run without a script file argument, `qwin.exe` checks for embedded JS at the end of itself. If found, it executes the embedded code. If not, it falls back to `main.js`. Both raw (`QWJS`) and brotli-compressed (`QWBR`) embedded payloads are supported; the compressed form is decompressed at startup.
75
+
76
+ ## Modules
77
+
78
+ | Module | Import | Description |
79
+ |--------|--------|-------------|
80
+ | `std` | built-in | file I/O, environment, URL download |
81
+ | `os` | built-in | filesystem, process, Worker, timer |
82
+ | `gui` | built-in | Win32 window/control/message/tray API |
83
+ | `sock` | built-in | socket networking (`AddrFamily`, `FdEvent` etc.) |
84
+ | `wolfssl` | built-in | TLS/SSL (`VerifyMode`, `ReturnCode` etc.) |
85
+ | `ffi` | built-in | foreign function interface |
86
+ | `win` | built-in | DLL loading (`LoadLibrary`, `GetProcAddress`) |
87
+ | `brotli` | built-in | Brotli decompression |
88
+ | `wamr` | built-in | low-level WAMR API |
89
+ | `fetch` | `import './lib/fetch.js'` | adds `fetch()`, `Response`, `Headers` to globalThis |
90
+ | `websocket` | `import './lib/websocket.js'` | adds `WebSocket` to globalThis |
91
+ | `polyfill` | `import './lib/polyfill.js'` | adds `TextEncoder`, `URL`, `btoa`/`atob`, `setTimeout` to globalThis |
92
+ | `preact` | `lib/preact/...` | JSX → Win32 renderer (`render`, `useState`, `useEffect`) |
93
+ | `react-qw` | `lib/react-qw/` | React Custom Renderer for Win32 GUI ([docs](lib/react-qw/)) |
94
+
95
+ ## Worker
96
+
97
+ Spawn background threads for CPU-bound or I/O-bound work. Workers run in separate QuickJS runtimes with their own event loops.
98
+
99
+ ### Main Thread
100
+
101
+ ```js
102
+ import * as os from 'os'
103
+
104
+ const worker = new os.Worker('./worker.js')
105
+
106
+ worker.onmessage = (e) => {
107
+ console.log('received:', e.data)
108
+ worker.onmessage = null // clean up when done
109
+ }
110
+
111
+ worker.postMessage({ type: 'start', value: 42 })
112
+ ```
113
+
114
+ ### Worker Thread
115
+
116
+ ```js
117
+ // worker.js
118
+ import * as os from 'os'
119
+
120
+ const parent = os.Worker.parent
121
+
122
+ parent.onmessage = (e) => {
123
+ if (e.data.type === 'start') {
124
+ parent.postMessage({ type: 'result', value: e.data.value * 2 })
125
+ } else if (e.data.type === 'done') {
126
+ parent.onmessage = null // clean up to allow event loop exit
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### API
132
+
133
+ | API | Description |
134
+ |-----|-------------|
135
+ | `new os.Worker(specifier)` | Create worker. `specifier` is a file path or URL (supports `https://` for ESM imports) |
136
+ | `worker.postMessage(data)` | Send message (JSON-serializable) to worker |
137
+ | `worker.onmessage = fn` | Set callback for messages from worker. Set to `null` when done to release the message port |
138
+ | `os.Worker.parent` | (Worker side) reference to the parent thread |
139
+
140
+ ### Cleanup
141
+
142
+ Both sides must set `onmessage = null` when communication is complete. This releases the message port so the event loop can exit cleanly. Forgetting to do so will cause the process to hang.
143
+
144
+ ## Examples
145
+
146
+ ```bash
147
+ npx quickwin examples/preact_demo.js # counter GUI with JSX + hooks
148
+ npx quickwin examples/tray_demo.js # system tray app
149
+ npx quickwin examples/pdf_preview.js # PDF reader with mupdf
150
+ ```
151
+
152
+ ## Build from Source
153
+
154
+ ### Prerequisites
155
+
156
+ - MSYS2 UCRT64 or MINGW64
157
+ - Node.js (for TypeScript compilation via tsc)
158
+ - Git (for submodules)
159
+
160
+ ### Build
161
+
162
+ ```bash
163
+ git clone --recursive https://github.com/anomalyco/quickwin.git
164
+ cd quickwin
165
+
166
+ .\run.ps1 "make wamr" # build WAMR library (first time only)
167
+ .\run.ps1 "make minimal" # build qwin.exe (-Os + LTO + UPX)
168
+ .\run.ps1 "make js" # compile TypeScript
169
+ .\run.ps1 "make test" # run all tests
170
+ ```
171
+
172
+ `make nowasm` produces a smaller `_build/qwin-nowasm.exe` without WASM/WAMR
173
+ (no `WebAssembly` global); run `make test TEST=-wasm` to skip WASM tests on it.
174
+
175
+ ### Build Targets
176
+
177
+ | Target | Description |
178
+ |--------|-------------|
179
+ | `make` / `make nodebug` | fast build |
180
+ | `make minimal` | `-Os` + LTO + `-mwindows` + UPX, no console, add `-o CON` for console |
181
+ | `make nowasm` | no WASM/WAMR build → `_build/qwin-nowasm.exe` (~1.2MB, no `WebAssembly` global) |
182
+ | `make release` | `-O2` + LTO + strip, ~2.5MB |
183
+ | `make debug` | debug build with bridge logs |
184
+ | `make js` | compile TypeScript via tsc |
185
+ | `make wasm` | compile WAT → WASM fixtures |
186
+ | `make test` | run all tests |
187
+ | `make test TEST=-net` | skip network tests (fast) |
188
+ | `make test TEST=wasm` | run WASM tests only |
189
+ | `make wamr` | rebuild WAMR library |
190
+ | `make embed-js` | embed `embed.js` into exe (use `JS_EMBED=file.js`) |
191
+ | `make embed-js-br` | embed brotli-compressed JS into exe |
192
+ | `make npm-pkg` | package into `dist/quickwin/` |
193
+ | `make clean` | clean build artifacts |
194
+
195
+ ## License
196
+
197
+ MIT
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ > [English](README.en.md) · **中文**
2
+
3
+ # QuickWin
4
+
5
+ QuickJS Win32 运行时 —— 用原生 Windows GUI、网络、WASM、FFI 等运行 JavaScript。
6
+
7
+ ```bash
8
+ npm i -g quickwin
9
+ quickwin script.js
10
+ ```
11
+
12
+ ## 特性
13
+
14
+ - **Win32 原生 GUI** —— 原生窗口、按钮、编辑框、列表框、托盘图标、弹出菜单
15
+ - **React 渲染器** —— 用 JSX 声明式 GUI,支持 `useState`/`useEffect`,diff 增量更新([react-qw](lib/react-qw/))
16
+ - **HTTP/HTTPS** —— `fetch()` API、Brotli 解压、chunked 传输、条件缓存
17
+ - **WebSocket** —— 完整的 RFC 6455 实现,支持 ws:// 与 wss://
18
+ - **WebAssembly** —— 基于 WAMR,支持标准 `WebAssembly.*` API
19
+ - **FFI** —— 通过 libffi 调用任意 DLL 函数
20
+ - **mupdf** —— 内嵌 PDF 渲染
21
+ - **Polyfills** —— `TextEncoder`、`URL`、`URLSearchParams`、`btoa`/`atob`、`crypto.subtle`、`setTimeout`
22
+ - **动态导入** —— `import('https://esm.sh/...')`,无需 npm install
23
+
24
+ ## CLI
25
+
26
+ ```bash
27
+ quickwin script.js # 运行脚本
28
+ quickwin -o CON script.js # 带控制台运行(AllocConsole)
29
+ quickwin -o LOG script.js # 自动生成日志文件运行
30
+ quickwin -e "console.log('hi')" # 执行表达式
31
+ quickwin -- script.js --flag # -- 停止选项解析
32
+ ```
33
+
34
+ ### 选项
35
+
36
+ | Flag | Description |
37
+ |------|-------------|
38
+ | `-e <expr>` | 执行表达式而不是文件 |
39
+ | `-o CON` | 分配控制台(`AllocConsole`);仅 GUI 子系统构建所需 |
40
+ | `-o LOG` | 将 stdout+stderr 重定向到 `log_YYYY_MM_DD_HH_MM_SS.txt`(exe 所在目录) |
41
+ | `-o <file>` | 将 stdout+stderr 重定向到指定文件 |
42
+ | `-d` | 开启 HTTP 调试日志 |
43
+ | `--` | 停止选项解析,其余参数传给脚本 |
44
+
45
+ 所有未知参数都会透明转发给 `scriptArgs` —— 不报错、不消费。选项解析后的第一个非参数即脚本文件。
46
+
47
+ ## 内嵌脚本
48
+
49
+ 可以把一个 JS 脚本直接内嵌进 `qwin.exe` 二进制 —— 无需重新编译。
50
+
51
+ ### 格式(追加到 exe 末尾)
52
+
53
+ ```
54
+ [JS bytes (N)] [N: uint32 LE] [magic "QWJS"]
55
+ ```
56
+
57
+ ### 用法
58
+
59
+ ```bash
60
+ # 内嵌一个脚本
61
+ powershell -ExecutionPolicy Bypass -File scripts/embed-js.ps1 -ExePath _build/qwin.exe -JsFile script.js
62
+
63
+ # 或通过 make
64
+ make embed-js JS_EMBED=script.js
65
+
66
+ # 内嵌 Brotli 压缩的脚本(exe 更小,运行时解压)
67
+ make embed-js-br JS_EMBED=script.js
68
+
69
+ # 运行它(不需要文件参数)
70
+ qwin.exe
71
+ ```
72
+
73
+ 不带脚本文件参数运行时,`qwin.exe` 会检查自身末尾是否有内嵌 JS:找到则执行内嵌代码,否则回退到 `main.js`。同时支持原始(`QWJS`)与 Brotli 压缩(`QWBR`)两种内嵌格式;压缩格式在启动时解压。
74
+
75
+ ## 模块
76
+
77
+ | Module | Import | Description |
78
+ |--------|--------|-------------|
79
+ | `std` | built-in | 文件 I/O、环境变量、URL 下载 |
80
+ | `os` | built-in | 文件系统、进程、Worker、定时器 |
81
+ | `gui` | built-in | Win32 窗口/控件/消息/托盘 API |
82
+ | `sock` | built-in | Socket 网络(`AddrFamily`、`FdEvent` 等) |
83
+ | `wolfssl` | built-in | TLS/SSL(`VerifyMode`、`ReturnCode` 等) |
84
+ | `ffi` | built-in | 外部函数接口 |
85
+ | `win` | built-in | DLL 加载(`LoadLibrary`、`GetProcAddress`) |
86
+ | `brotli` | built-in | Brotli 解压 |
87
+ | `wamr` | built-in | 底层 WAMR API |
88
+ | `fetch` | `import './lib/fetch.js'` | 向 globalThis 添加 `fetch()`、`Response`、`Headers` |
89
+ | `websocket` | `import './lib/websocket.js'` | 向 globalThis 添加 `WebSocket` |
90
+ | `polyfill` | `import './lib/polyfill.js'` | 向 globalThis 添加 `TextEncoder`、`URL`、`btoa`/`atob`、`setTimeout` |
91
+ | `preact` | `lib/preact/...` | JSX → Win32 渲染器(`render`、`useState`、`useEffect`) |
92
+ | `react-qw` | `lib/react-qw/` | 面向 Win32 GUI 的 React 自定义渲染器([文档](lib/react-qw/)) |
93
+
94
+ ## Worker
95
+
96
+ 面向 CPU 密集或 I/O 密集工作创建后台线程。Worker 运行在独立的 QuickJS runtime 中,各自拥有自己的事件循环。
97
+
98
+ ### 主线程
99
+
100
+ ```js
101
+ import * as os from 'os'
102
+
103
+ const worker = new os.Worker('./worker.js')
104
+
105
+ worker.onmessage = (e) => {
106
+ console.log('received:', e.data)
107
+ worker.onmessage = null // 完成后清理
108
+ }
109
+
110
+ worker.postMessage({ type: 'start', value: 42 })
111
+ ```
112
+
113
+ ### Worker 线程
114
+
115
+ ```js
116
+ // worker.js
117
+ import * as os from 'os'
118
+
119
+ const parent = os.Worker.parent
120
+
121
+ parent.onmessage = (e) => {
122
+ if (e.data.type === 'start') {
123
+ parent.postMessage({ type: 'result', value: e.data.value * 2 })
124
+ } else if (e.data.type === 'done') {
125
+ parent.onmessage = null // 清理以允许事件循环退出
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### API
131
+
132
+ | API | Description |
133
+ |-----|-------------|
134
+ | `new os.Worker(specifier)` | 创建 worker。`specifier` 是文件路径或 URL(支持 `https://` ESM 导入) |
135
+ | `worker.postMessage(data)` | 向 worker 发送消息(JSON 可序列化) |
136
+ | `worker.onmessage = fn` | 设置来自 worker 的消息回调。完成后设为 `null` 以释放消息端口 |
137
+ | `os.Worker.parent` | (Worker 侧)父线程的引用 |
138
+
139
+ ### 清理
140
+
141
+ 通信完成后,双方都必须把 `onmessage` 设为 `null`。这会释放消息端口,使事件循环能够干净退出。忘记设置会导致进程挂起。
142
+
143
+ ## 示例
144
+
145
+ ```bash
146
+ npx quickwin examples/preact_demo.js # JSX + hooks 的计数器 GUI
147
+ npx quickwin examples/tray_demo.js # 系统托盘应用
148
+ npx quickwin examples/pdf_preview.js # 基于 mupdf 的 PDF 阅读器
149
+ ```
150
+
151
+ ## 从源码构建
152
+
153
+ ### 前置依赖
154
+
155
+ - MSYS2 UCRT64 或 MINGW64
156
+ - Node.js(用于通过 tsc 编译 TypeScript)
157
+ - Git(用于子模块)
158
+
159
+ ### 构建
160
+
161
+ ```bash
162
+ git clone --recursive https://github.com/anomalyco/quickwin.git
163
+ cd quickwin
164
+
165
+ .\run.ps1 "make wamr" # 构建 WAMR 库(仅首次)
166
+ .\run.ps1 "make minimal" # 构建 qwin.exe(-Os + LTO + UPX)
167
+ .\run.ps1 "make js" # 编译 TypeScript
168
+ .\run.ps1 "make test" # 运行全部测试
169
+ ```
170
+
171
+ `make nowasm` 会产出更小的 `_build/qwin-nowasm.exe`,不含 WASM/WAMR
172
+ (没有 `WebAssembly` 全局对象);对这种版本运行 `make test TEST=-wasm` 跳过 WASM 测试。
173
+
174
+ ### 构建目标
175
+
176
+ | Target | Description |
177
+ |--------|-------------|
178
+ | `make` / `make nodebug` | 快速构建 |
179
+ | `make minimal` | `-Os` + LTO + `-mwindows` + UPX,无控制台,需要控制台时加 `-o CON` |
180
+ | `make nowasm` | 无 WASM/WAMR 构建 → `_build/qwin-nowasm.exe`(约 1.2MB,无 `WebAssembly` 全局) |
181
+ | `make release` | `-O2` + LTO + strip,约 2.5MB |
182
+ | `make debug` | 带 bridge 日志的调试构建 |
183
+ | `make js` | 通过 tsc 编译 TypeScript |
184
+ | `make wasm` | 编译 WAT → WASM fixtures |
185
+ | `make test` | 运行全部测试 |
186
+ | `make test TEST=-net` | 跳过网络测试(快速) |
187
+ | `make test TEST=wasm` | 仅运行 WASM 测试 |
188
+ | `make wamr` | 重建 WAMR 库 |
189
+ | `make embed-js` | 把 `embed.js` 内嵌进 exe(用 `JS_EMBED=file.js`) |
190
+ | `make embed-js-br` | 把 Brotli 压缩的 JS 内嵌进 exe |
191
+ | `make npm-pkg` | 打包到 `dist/quickwin/` |
192
+ | `make clean` | 清理构建产物 |
193
+
194
+ ## 许可证
195
+
196
+ MIT
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useRef } from 'react';
3
+ import * as win from 'win';
4
+ import * as ffi from 'ffi';
5
+ const _user32 = win.LoadLibrary('user32.dll');
6
+ const _gdi32 = win.LoadLibrary('gdi32.dll');
7
+ if (!_user32 || !_gdi32)
8
+ throw new Error('Failed to load user32/gdi32');
9
+ function loadProc(lib, name) {
10
+ const ptr = win.GetProcAddress(lib, name);
11
+ if (!ptr)
12
+ throw new Error('Failed to load ' + name);
13
+ return ptr;
14
+ }
15
+ const GetDC_ = loadProc(_user32, 'GetDC');
16
+ const ReleaseDC_ = loadProc(_user32, 'ReleaseDC');
17
+ const SetDIBitsToDevice_ = loadProc(_gdi32, 'SetDIBitsToDevice');
18
+ const FFI_PTR = ffi.FFI_TYPE_POINTER;
19
+ const FFI_U32 = ffi.FFI_TYPE_UINT32;
20
+ const FFI_S32 = ffi.FFI_TYPE_SINT32;
21
+ function makeBitmapInfo(w, h) {
22
+ const bmi = new ArrayBuffer(40);
23
+ const bv = new DataView(bmi);
24
+ bv.setUint32(0, 40, true);
25
+ bv.setInt32(4, w, true);
26
+ bv.setInt32(8, -h, true);
27
+ bv.setUint16(12, 1, true);
28
+ bv.setUint16(14, 24, true);
29
+ return bmi;
30
+ }
31
+ export const PdfCanvas = forwardRef(({ pixmap, style }, ref) => {
32
+ const canvasRef = useRef(null);
33
+ const pixmapRef = useRef(pixmap);
34
+ pixmapRef.current = pixmap;
35
+ return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: style, ref: (h) => {
36
+ canvasRef.current = h;
37
+ if (typeof ref === 'function')
38
+ ref(h);
39
+ else if (ref)
40
+ ref.current = h;
41
+ }, onEvent: (e) => {
42
+ const hwnd = e.hwnd;
43
+ if (e.msg === 0x14)
44
+ return 1;
45
+ if (e.msg === 15 /* gui.WmMsg.PAINT */) {
46
+ const pm = pixmapRef.current;
47
+ if (!pm)
48
+ return 0;
49
+ const hdc = ffi.ffiCall(GetDC_, [ffi.FFI_TYPE_UINT64], [hwnd], ffi.FFI_TYPE_UINT64);
50
+ if (hdc) {
51
+ const bmi = makeBitmapInfo(pm.w, pm.h);
52
+ ffi.ffiCall(SetDIBitsToDevice_, [
53
+ ffi.FFI_TYPE_UINT64, FFI_S32, FFI_S32, FFI_U32, FFI_U32,
54
+ FFI_S32, FFI_S32, FFI_U32, FFI_U32,
55
+ FFI_PTR, FFI_PTR, FFI_U32
56
+ ], [
57
+ hdc, 0, 0, pm.w, pm.h,
58
+ 0, 0, 0, pm.h,
59
+ pm.data, bmi, 0
60
+ ], FFI_S32);
61
+ ffi.ffiCall(ReleaseDC_, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32);
62
+ }
63
+ return 0;
64
+ }
65
+ return;
66
+ } }));
67
+ });
@@ -0,0 +1,83 @@
1
+ import { forwardRef, useRef } from 'react'
2
+ import * as gui from 'gui'
3
+ import * as win from 'win'
4
+ import * as ffi from 'ffi'
5
+ import type { WStyle } from '../lib/react-qw/jsx.d.ts'
6
+
7
+ const _user32 = win.LoadLibrary('user32.dll')
8
+ const _gdi32 = win.LoadLibrary('gdi32.dll')
9
+ if (!_user32 || !_gdi32) throw new Error('Failed to load user32/gdi32')
10
+
11
+ function loadProc(lib: win.HMODULE, name: string): number {
12
+ const ptr = win.GetProcAddress(lib, name)
13
+ if (!ptr) throw new Error('Failed to load ' + name)
14
+ return ptr
15
+ }
16
+
17
+ const GetDC_ = loadProc(_user32, 'GetDC')
18
+ const ReleaseDC_ = loadProc(_user32, 'ReleaseDC')
19
+ const SetDIBitsToDevice_ = loadProc(_gdi32, 'SetDIBitsToDevice')
20
+
21
+ const FFI_PTR = ffi.FFI_TYPE_POINTER
22
+ const FFI_U32 = ffi.FFI_TYPE_UINT32
23
+ const FFI_S32 = ffi.FFI_TYPE_SINT32
24
+
25
+ function makeBitmapInfo(w: number, h: number): ArrayBuffer {
26
+ const bmi = new ArrayBuffer(40)
27
+ const bv = new DataView(bmi)
28
+ bv.setUint32(0, 40, true)
29
+ bv.setInt32(4, w, true)
30
+ bv.setInt32(8, -h, true)
31
+ bv.setUint16(12, 1, true)
32
+ bv.setUint16(14, 24, true)
33
+ return bmi
34
+ }
35
+
36
+ export interface PdfCanvasProps {
37
+ pixmap?: { data: ArrayBuffer; w: number; h: number }
38
+ style?: WStyle
39
+ }
40
+
41
+ export const PdfCanvas = forwardRef<gui.HWND, PdfCanvasProps>(
42
+ ({ pixmap, style }, ref) => {
43
+ const canvasRef = useRef<gui.HWND>(null)
44
+ const pixmapRef = useRef(pixmap)
45
+ pixmapRef.current = pixmap
46
+
47
+ return (
48
+ <w type="STATIC"
49
+ ws={gui.WindowStyle.VISIBLE | gui.WindowStyle.CLIPCHILDREN}
50
+ style={style}
51
+ ref={(h: gui.HWND) => {
52
+ canvasRef.current = h
53
+ if (typeof ref === 'function') ref(h)
54
+ else if (ref) (ref as React.RefObject<gui.HWND | null>).current = h
55
+ }}
56
+ onEvent={(e) => {
57
+ const hwnd = e.hwnd
58
+ if (e.msg === 0x14) return 1
59
+ if (e.msg === gui.WmMsg.PAINT) {
60
+ const pm = pixmapRef.current
61
+ if (!pm) return 0
62
+ const hdc = ffi.ffiCall(GetDC_, [ffi.FFI_TYPE_UINT64], [hwnd], ffi.FFI_TYPE_UINT64)
63
+ if (hdc) {
64
+ const bmi = makeBitmapInfo(pm.w, pm.h)
65
+ ffi.ffiCall(SetDIBitsToDevice_, [
66
+ ffi.FFI_TYPE_UINT64, FFI_S32, FFI_S32, FFI_U32, FFI_U32,
67
+ FFI_S32, FFI_S32, FFI_U32, FFI_U32,
68
+ FFI_PTR, FFI_PTR, FFI_U32
69
+ ], [
70
+ hdc, 0, 0, pm.w, pm.h,
71
+ 0, 0, 0, pm.h,
72
+ pm.data, bmi, 0
73
+ ], FFI_S32)
74
+ ffi.ffiCall(ReleaseDC_, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32)
75
+ }
76
+ return 0
77
+ }
78
+ return
79
+ }}
80
+ />
81
+ )
82
+ }
83
+ )