bun-app-window 0.0.0-stage → 0.1.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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +400 -2
  3. package/README.zh-CN.md +399 -0
  4. package/README.zh-TW.md +399 -0
  5. package/dist/libshell.dll +0 -0
  6. package/native/build.ps1 +67 -0
  7. package/native/fetch.ps1 +66 -0
  8. package/native/shell.cc +2134 -0
  9. package/package.json +67 -5
  10. package/src/shell/assets.d.ts +16 -0
  11. package/src/shell/debug.ts +40 -0
  12. package/src/shell/embedded.ts +42 -0
  13. package/src/shell/index.ts +291 -0
  14. package/src/shell/ipc.ts +569 -0
  15. package/src/shell/native.ts +92 -0
  16. package/src/shell/port.ts +77 -0
  17. package/src/shell/resolve.ts +211 -0
  18. package/src/shell/state.ts +211 -0
  19. package/src/shell/types.ts +144 -0
  20. package/src/ui/titlebar.ts +436 -0
  21. package/vendor/webview/compatibility/CMakeLists.txt +3 -0
  22. package/vendor/webview/compatibility/mingw/CMakeLists.txt +14 -0
  23. package/vendor/webview/compatibility/mingw/include/EventToken.h +25 -0
  24. package/vendor/webview/core/CMakeLists.txt +94 -0
  25. package/vendor/webview/core/include/webview/api.h +251 -0
  26. package/vendor/webview/core/include/webview/backends.hh +40 -0
  27. package/vendor/webview/core/include/webview/c_api_impl.hh +258 -0
  28. package/vendor/webview/core/include/webview/detail/backends/cocoa_webkit.hh +626 -0
  29. package/vendor/webview/core/include/webview/detail/backends/gtk_webkitgtk.hh +355 -0
  30. package/vendor/webview/core/include/webview/detail/backends/win32_edge.hh +909 -0
  31. package/vendor/webview/core/include/webview/detail/basic_result.hh +119 -0
  32. package/vendor/webview/core/include/webview/detail/engine_base.hh +382 -0
  33. package/vendor/webview/core/include/webview/detail/exceptions.hh +42 -0
  34. package/vendor/webview/core/include/webview/detail/json.hh +337 -0
  35. package/vendor/webview/core/include/webview/detail/native_library.hh +170 -0
  36. package/vendor/webview/core/include/webview/detail/optional.hh +118 -0
  37. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSApplication.hh +107 -0
  38. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSBundle.hh +56 -0
  39. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSEvent.hh +71 -0
  40. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSInvocation.hh +66 -0
  41. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSMethodSignature.hh +52 -0
  42. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSNotification.hh +51 -0
  43. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSNumber.hh +53 -0
  44. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSObject.hh +51 -0
  45. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSOpenPanel.hh +71 -0
  46. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSPoint.hh +53 -0
  47. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSRect.hh +53 -0
  48. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSSavePanel.hh +52 -0
  49. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSSize.hh +53 -0
  50. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSString.hh +87 -0
  51. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSURL.hh +63 -0
  52. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSURLRequest.hh +52 -0
  53. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSValue.hh +56 -0
  54. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSView.hh +91 -0
  55. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSWindow.hh +137 -0
  56. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/cocoa.hh +59 -0
  57. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/types.hh +49 -0
  58. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/Class.hh +55 -0
  59. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/autoreleasepool.hh +70 -0
  60. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/invoke.hh +75 -0
  61. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/memory.hh +57 -0
  62. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/objc.hh +47 -0
  63. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKOpenPanelParameters.hh +57 -0
  64. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKScriptMessage.hh +51 -0
  65. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKUserContentController.hh +61 -0
  66. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKUserScript.hh +71 -0
  67. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKWebView.hh +108 -0
  68. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKWebViewConfiguration.hh +60 -0
  69. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/webkit.hh +46 -0
  70. package/vendor/webview/core/include/webview/detail/platform/linux/gtk/compat.hh +137 -0
  71. package/vendor/webview/core/include/webview/detail/platform/linux/webkitgtk/compat.hh +142 -0
  72. package/vendor/webview/core/include/webview/detail/platform/linux/webkitgtk/dmabuf.hh +167 -0
  73. package/vendor/webview/core/include/webview/detail/platform/windows/com_init_wrapper.hh +126 -0
  74. package/vendor/webview/core/include/webview/detail/platform/windows/dpi.hh +161 -0
  75. package/vendor/webview/core/include/webview/detail/platform/windows/dwmapi.hh +67 -0
  76. package/vendor/webview/core/include/webview/detail/platform/windows/iid.hh +78 -0
  77. package/vendor/webview/core/include/webview/detail/platform/windows/ntdll.hh +58 -0
  78. package/vendor/webview/core/include/webview/detail/platform/windows/reg_key.hh +135 -0
  79. package/vendor/webview/core/include/webview/detail/platform/windows/shcore.hh +59 -0
  80. package/vendor/webview/core/include/webview/detail/platform/windows/theme.hh +76 -0
  81. package/vendor/webview/core/include/webview/detail/platform/windows/user32.hh +90 -0
  82. package/vendor/webview/core/include/webview/detail/platform/windows/version.hh +151 -0
  83. package/vendor/webview/core/include/webview/detail/platform/windows/webview2/loader.hh +391 -0
  84. package/vendor/webview/core/include/webview/detail/user_script.hh +76 -0
  85. package/vendor/webview/core/include/webview/detail/utility/string.hh +99 -0
  86. package/vendor/webview/core/include/webview/errors.h +67 -0
  87. package/vendor/webview/core/include/webview/errors.hh +85 -0
  88. package/vendor/webview/core/include/webview/json_deprecated.hh +61 -0
  89. package/vendor/webview/core/include/webview/macros.h +123 -0
  90. package/vendor/webview/core/include/webview/types.h +81 -0
  91. package/vendor/webview/core/include/webview/types.hh +48 -0
  92. package/vendor/webview/core/include/webview/version.h +67 -0
  93. package/vendor/webview/core/include/webview/webview.h +32 -0
  94. package/vendor/webview/core/include/webview.h +15 -0
  95. package/vendor/webview/core/src/webview.cc +1 -0
  96. package/vendor/webview/core/tests/CMakeLists.txt +18 -0
  97. package/vendor/webview/core/tests/src/functional_tests.cc +316 -0
  98. package/vendor/webview/core/tests/src/unit_tests.cc +188 -0
  99. package/vendor/webview2/build/native/include/WebView2.h +23568 -0
  100. package/vendor/webview2/build/native/include/WebView2EnvironmentOptions.h +144 -0
@@ -0,0 +1,399 @@
1
+ # bun-app-window
2
+
3
+ [![npm version](https://img.shields.io/npm/v/bun-app-window?color=cb3837&logo=npm)](https://www.npmjs.com/package/bun-app-window)
4
+ [![Bun](https://img.shields.io/badge/Bun-%3E%3D1.4.3-f472b6?logo=bun&logoColor=white)](https://bun.sh)
5
+ [![Platform](https://img.shields.io/badge/Platform-Windows%20x64-0078D6?logo=windows&logoColor=white)](#系統需求與設計邊界)
6
+ [![WebView2](https://img.shields.io/badge/WebView2-System%20Runtime-008080)](#系統需求與設計邊界)
7
+ [![License](https://img.shields.io/badge/License-MIT-2ea44f)](./LICENSE)
8
+
9
+ 正體中文 | [English](./README.md) | [简体中文](./README.zh-CN.md)
10
+
11
+ 專為 Bun 應用程式設計的 Windows 原生視窗外殼,基於系統內建的 WebView2 執行環境(Runtime)承載前端介面。
12
+
13
+ 本專案遵循極簡職責邊界:僅負責視窗生命週期、幾何尺寸、DPI 縮放適配、無邊框視窗自訂及底層訊息通道;商業邏輯與 UI 渲染完全解耦於外殼層之外。
14
+
15
+ ```ts
16
+ import { openShell, serveOnSafePort, FLAG_FRAMELESS } from "bun-app-window";
17
+
18
+ const server = serveOnSafePort({ fetch: () => new Response("<h1>Hello</h1>") });
19
+ await openShell({ url: `http://127.0.0.1:${server.port}`, flags: FLAG_FRAMELESS });
20
+ ```
21
+
22
+ ---
23
+
24
+ ## 系統需求與設計邊界
25
+
26
+ 在導入本專案前,請確認以下技術前提與架構取捨是否符合你的應用場景:
27
+
28
+ | 維度 | 限制與說明 |
29
+ | --- | --- |
30
+ | 僅支援 Windows x64 | `package.json` 中的 `os` 與 `cpu` 欄位已做強制限制,拒絕在其他平台安裝(套件名稱保持平台中立僅為預留命名空間)。預編譯動態連結程式庫 `libshell.dll` 隨套件發佈,無需在安裝期編譯 |
31
+ | 僅支援 Bun 執行環境 | 套件內直接發佈 TypeScript 原始碼,要求 Bun ≥ 1.4.3。Node.js 環境雖可完成套件安裝,但無法直接執行 |
32
+ | 基於 `bun:ffi` 建構 | `bun:ffi` 在 Bun 中仍標記為實驗性功能(Experimental)。官方建議正式環境優先採用 Node-API,本專案選擇 `bun:ffi` 是為了換取零第三方相依性的輕量架構 |
33
+
34
+ 執行環境與能力邊界:
35
+
36
+ - 依賴系統 WebView2 Runtime:目標機器需已安裝 WebView2 執行環境(Windows 11 預設內建)。
37
+ - 要求互動式桌面工作階段:受 Windows 工作階段隔離(Session 0 Isolation)機制限制,WebView2 無法在 Session 0(如系統背景服務)中啟動;但實測在 High 完整性層級的管理員提權行程(Process)中可正常執行。
38
+ - 明確不包含的功能:系統匣常駐圖示(System Tray)、原生頂部功能表列、自動更新、多視窗管理、跨平台支援。
39
+
40
+ ## 安裝
41
+
42
+ ```bash
43
+ bun add bun-app-window
44
+ ```
45
+
46
+ ## 快速開始
47
+
48
+ ```ts
49
+ import { openShell, serveOnSafePort, FLAG_FRAMELESS, FLAG_CENTER } from "bun-app-window";
50
+
51
+ const server = serveOnSafePort({
52
+ fetch: () => new Response("<h1>Hello</h1>", {
53
+ headers: { "content-type": "text/html; charset=utf-8" },
54
+ }),
55
+ });
56
+
57
+ const shell = await openShell({
58
+ url: `http://127.0.0.1:${server.port}`,
59
+ title: "My App",
60
+ width: 1000,
61
+ height: 680,
62
+ flags: FLAG_FRAMELESS | FLAG_CENTER,
63
+ minWidth: 480,
64
+ minHeight: 320,
65
+ });
66
+
67
+ shell.post(JSON.stringify({ t: "ready" })); // 宿主 → 頁面單向訊息
68
+ shell.setTitle("Doing work…");
69
+ ```
70
+
71
+ 說明:`createShell(opts)` 僅解析並載入原生動態連結程式庫,不建立視窗;`openShell(opts)` 則一步完成實例建立與視窗啟動,若啟動失敗將直接拋出例外錯誤。
72
+
73
+ ## 核心 API
74
+
75
+ ### 實例方法與屬性
76
+
77
+ | 成員 | 說明 |
78
+ | --- | --- |
79
+ | `start()` | 建立並啟動視窗,回傳 `{ rc, error }`。呼叫後立即回傳,UI 訊息迴圈執行於獨立原生執行緒 |
80
+ | `stop()` | 銷毀視窗並釋放底層資源,支援冪等重複呼叫 |
81
+ | `show()` | 顯示視窗。常搭配 `FLAG_DEFER_SHOW` 使用,實現在頁面渲染就緒後再展現視窗 |
82
+ | `isAlive()` | 檢測視窗是否存活(註:`state()` 無法區分「尚未啟動」與「已銷毀」狀態) |
83
+ | `state()` | 取得目前視窗狀態:`{ maximized, minimized, focused, dark, raw }` |
84
+ | `lastError()` | 取得最近一次操作失敗的錯誤訊息,無錯誤時回傳空字串 |
85
+ | `setTitle` / `minimize` / `toggleMaximize` / `close` | 基礎視窗控制操作,回傳狀態碼 `rc` |
86
+ | `setMinSize(w, h)` | 設定視窗最小尺寸(單位:邏輯像素) |
87
+ | `drag()` | 觸發原生視窗拖曳(於頁面 `pointerdown` 事件中呼叫,完整保留 Windows 貼齊版面配置特性) |
88
+ | `beginResize(edge)` | 觸發原生邊框縮放(於頁面 `pointermove` 事件中呼叫,`edge` 取值見 `RESIZE_EDGE.*`) |
89
+ | `post(json)` | 宿主向頁面單向投遞 JSON 訊息,非同步發送且不等待回傳結果 |
90
+ | `setZoom(f)` / `getZoom()` | 設定或取得頁面縮放比例(有效範圍 `0.25`–`3.0`,越界值自動鉗制) |
91
+ | `reload()` / `openDevTools()` / `toggleFullscreen()` | 頁面重新載入、開啟開發人員工具、切換全螢幕(分別對應 `Ctrl+R` / `F12` / `F11`) |
92
+ | `setBrowserUi(mask)` | 停用指定的瀏覽器預設行為,傳入 `BROWSER_UI_*` 旗標的按位元 OR 組合 |
93
+ | `showContextMenu(x, y, items)` | 於指定螢幕座標彈出 Win32 原生右鍵快顯功能表(詳見下文用法) |
94
+ | `pickFolder(title?)` | 呼叫原生資料夾選擇對話方塊(同步阻塞目前呼叫執行緒) |
95
+ | `libInfo` | 原生動態連結程式庫解析中繼資料(包含實際路徑、載入來源、SHA-256 雜湊值) |
96
+ | `symbols` | 底層直通介面:直接存取透過 `dlopen` 匯出的底層 C 符號 |
97
+
98
+ ### 視窗旗標(Flags)
99
+
100
+ 透過按位元 OR(`|`)組合傳入 `opts.flags`:
101
+
102
+ | 旗標 | 作用說明 |
103
+ | --- | --- |
104
+ | `FLAG_FRAMELESS` | 無邊框視窗模式,移除系統預設標題列,交由前端頁面自行繪製 |
105
+ | `FLAG_RESIZABLE` | 允許使用者調整視窗大小 |
106
+ | `FLAG_CENTER` | 啟動時於目前顯示器工作區置中顯示(有邊框與無邊框模式均生效) |
107
+ | `FLAG_TOPMOST` | 視窗保持最上層顯示 |
108
+ | `FLAG_DEFER_SHOW` | 建立視窗後保持隱藏狀態,等待明確呼叫 `show()` |
109
+ | `FLAG_ACCEPT_DROP` | 允許視窗接收外部檔案或資料夾拖放事件 |
110
+ | `FLAG_START_MAXIMIZED` | 視窗首次顯示即處於最大化狀態,避免從一般尺寸跳變至最大化的中間閃爍畫格 |
111
+
112
+ ## 子路徑模組
113
+
114
+ 核心視窗控制之外的擴充能力均拆分於獨立子路徑下,可依需求匯入。
115
+
116
+ ### `bun-app-window/ipc` — 型別化雙向通訊與事件匯流排
117
+
118
+ 宿主與前端頁面共用同一份 TypeScript 型別宣告,用戶端透過泛型在編譯期完成方法簽章與事件負載驗證:
119
+
120
+ ```ts
121
+ // api.ts —— 宿主與頁面共同匯入的型別契約
122
+ export type Api = { greet(name: string): string };
123
+ export type Events = { windowState: { maximized: boolean } };
124
+ ```
125
+
126
+ ```ts
127
+ // 宿主端
128
+ import { createIpcServer } from "bun-app-window/ipc";
129
+
130
+ const ipc = createIpcServer({
131
+ service: { greet: (name: string) => `Hello ${name}` },
132
+ });
133
+
134
+ serveOnSafePort({
135
+ fetch(req) { /* 處理靜態頁面與驗證 */ },
136
+ websocket: {
137
+ open: (ws) => ipc.attach(ws),
138
+ message: (ws, raw) => ipc.handle(ws, raw),
139
+ close: (ws) => ipc.detach(ws),
140
+ },
141
+ });
142
+
143
+ ipc.emit("windowState", { maximized: false }); // 向頁面主動推播事件
144
+ ```
145
+
146
+ ```ts
147
+ // 頁面端
148
+ import { connect } from "bun-app-window/ipc";
149
+ import type { Api, Events } from "./api";
150
+
151
+ const api = await connect<Api, Events>();
152
+ await api.greet("Ada"); // 回傳型別自動推導為 string
153
+ api.on("windowState", (s) => bar.setMaximized(s.maximized));
154
+ ```
155
+
156
+ 預設情況下,`connect` 會從目前頁面 URL 的查詢參數 `?t=` 中讀取驗證 Token 並連線至 `/ws`。若需自訂連線方式,可明確傳入 `url` 或自訂 `channel` 實例。
157
+
158
+ ### `bun-app-window/titlebar` — Windows 11 風格標題列
159
+
160
+ 用於在無邊框模式下快速掛載符合 Windows 11 視覺規範的自訂標題列:
161
+
162
+ ```ts
163
+ // 頁面端
164
+ import { mountTitleBar, bridgeFromWindowService } from "bun-app-window/titlebar";
165
+
166
+ const bar = mountTitleBar({
167
+ title: "我的應用程式",
168
+ icon: "/icon.svg",
169
+ bridge: bridgeFromWindowService(api.window),
170
+ });
171
+
172
+ api.on("windowState", (s) => bar.setMaximized(s.maximized));
173
+ ```
174
+
175
+ 宿主端需在 IPC 服務中公開對應的四個視窗控制方法:
176
+
177
+ ```ts
178
+ // 宿主端
179
+ createIpcServer({
180
+ service: {
181
+ window: {
182
+ minimize: () => shell.minimize(),
183
+ toggleMaximize: () => shell.toggleMaximize(),
184
+ close: () => shell.close(),
185
+ drag: () => shell.drag(),
186
+ },
187
+ },
188
+ });
189
+ ```
190
+
191
+ 實作細節:
192
+
193
+ - 原生拖曳體驗:拖曳操作透過呼叫 `shell.drag()` 交由底層發送 `WM_NCLBUTTONDOWN`(`HTCAPTION`)訊息處理,完整保留 Windows 11 貼齊版面配置(Snap Layouts)與連按兩下最大化等系統級行為。
194
+ - 樣式自訂與圖示方案:色彩與尺寸均透過 CSS 變數(`--ws-tb-*`)控制,覆寫變數即可實作佈景主題切換。按鈕圖示預設採用相容性較佳的幾何向量符號;若設定 `icons: "fluent"` 則切換為 `Segoe Fluent Icons` 字型渲染(注意:若目標系統缺少該字型,按鈕將退回顯示為缺字方框)。
195
+
196
+ ### `bun-app-window/state` — 視窗位置與尺寸持久化
197
+
198
+ 用於自動記錄並在下次啟動時還原視窗的座標、尺寸及最大化狀態:
199
+
200
+ ```ts
201
+ import { attachPlacementStore, loadPlacement, restorePlacement } from "bun-app-window/state";
202
+
203
+ const FILE = "C:\\Users\\me\\AppData\\Roaming\\MyApp\\window.json";
204
+ const shell = await openShell({ url, flags: FLAG_DEFER_SHOW });
205
+
206
+ const saved = await loadPlacement(FILE);
207
+ if (saved) restorePlacement(shell, saved);
208
+
209
+ shell.show();
210
+
211
+ const store = attachPlacementStore(shell, FILE); // 定時輪詢狀態,視窗靜止時不觸發寫入磁碟
212
+ process.on("beforeExit", () => store.flush());
213
+ ```
214
+
215
+ 底層原生實作已妥善處理以下邊界場景:
216
+
217
+ - 最大化狀態下儲存的是視窗「還原後的正常矩形(Restore Rect)」而非全螢幕尺寸。
218
+ - 跨顯示器不同縮放比例下的 DPI 座標換算。
219
+ - 螢幕拓撲變化(如拔除外接螢幕)後的螢幕外座標越界驗證與修正。
220
+
221
+ 若無需背景輪詢,亦可直接呼叫 `capturePlacement` 與 `savePlacement` 執行單次手動讀寫。
222
+
223
+ ### `bun-app-window/debug` — 診斷與除錯介面
224
+
225
+ 提供底層視窗度量與命中測試(Hit-Test)資訊。注意:診斷介面不屬於穩定 API 契約,後續版本可能隨時調整。
226
+
227
+ ```ts
228
+ import { debugOf } from "bun-app-window/debug";
229
+
230
+ const d = debugOf(shell);
231
+ d.metrics(); // 取得 pos / win / client / work / border / round 等幾何度量
232
+ d.dpi(); // 取得目前 DPI 感知層級與縮放係數
233
+ d.hittest(x, y);
234
+ ```
235
+
236
+ ## 常見場景與最佳實踐
237
+
238
+ ### 1. 停用瀏覽器預設行為
239
+
240
+ WebView2 預設保留了完整的瀏覽器互動語意。建構桌面應用程式時,通常建議將其全部停用:
241
+
242
+ ```ts
243
+ import { BROWSER_UI_ALL } from "bun-app-window";
244
+
245
+ const shell = await openShell({ url, browserUi: BROWSER_UI_ALL });
246
+ ```
247
+
248
+ 亦可依需求組合以下旗標:
249
+
250
+ | 旗標 | 停用的瀏覽器行為 |
251
+ | --- | --- |
252
+ | `BROWSER_UI_MENU` | 預設網頁右鍵選單(Edge 快顯功能表) |
253
+ | `BROWSER_UI_KEYS` | 瀏覽器內建快速鍵(如 `Ctrl+P` / `Ctrl+F` / `Ctrl+S`) |
254
+ | `BROWSER_UI_STATUSBAR` | 左下角連結懸停狀態列 |
255
+ | `BROWSER_UI_SWIPE` | 觸控板雙指橫滑上一頁 / 下一頁手勢 |
256
+ | `BROWSER_UI_AUTOFILL` | 表單自動填入提示 |
257
+
258
+ 註:`F11`、`F12` 及 `Ctrl±` 縮放由視窗外殼層直接接管,不受上述旗標影響。
259
+
260
+ ### 2. 原生右鍵快顯功能表
261
+
262
+ 停用預設瀏覽器選單後,可透過 `showContextMenu` 喚起基於 Win32 `TrackPopupMenu` 繪製的原生選單。頁面無法直接呼叫它——需先在 IPC 服務上暴露:
263
+
264
+ ```ts
265
+ // 宿主端:將選單能力掛載到 window 服務
266
+ createIpcServer({
267
+ service: {
268
+ window: {
269
+ showContextMenu: (x: number, y: number, items: string) =>
270
+ shell.showContextMenu(x, y, items),
271
+ },
272
+ },
273
+ });
274
+ ```
275
+
276
+ ```ts
277
+ // 頁面端觸發選單
278
+ document.addEventListener("contextmenu", async (e) => {
279
+ e.preventDefault();
280
+ await api.window.showContextMenu(
281
+ Math.round(e.screenX),
282
+ Math.round(e.screenY),
283
+ "open|開啟\nrefresh|重新整理\n-\nquit|結束"
284
+ );
285
+ });
286
+
287
+ // 頁面端監聽選單點擊結果
288
+ window.addEventListener("shell-msg", (e) => {
289
+ if (e.detail.t === "menu") console.log("選取選單項目 ID:", e.detail.id);
290
+ });
291
+ ```
292
+
293
+ 選單項目定義採用換行符號分隔的字串格式:每行格式為 `id|顯示文字`,單獨一行 `-` 表示分隔線。
294
+
295
+ ### 3. 接收外部檔案拖放
296
+
297
+ 啟用 `FLAG_ACCEPT_DROP` 後,視窗即可接收來自檔案總管(Windows Explorer)的檔案或資料夾拖放:
298
+
299
+ ```ts
300
+ // 宿主端
301
+ const shell = await openShell({ url, flags: FLAG_ACCEPT_DROP });
302
+ ```
303
+
304
+ ```js
305
+ // 頁面端接收路徑清單
306
+ window.addEventListener("shell-msg", (e) => {
307
+ if (e.detail.t === "drop") console.log("拖入檔案路徑:", e.detail.paths);
308
+ });
309
+ ```
310
+
311
+ 針對管理員提權場景(High 完整性層級行程接收來自 Medium 層級檔案總管的訊息),底層已透過 `ChangeWindowMessageFilterEx` 自動放行 UIPI 訊息過濾。
312
+
313
+ ### 4. 前端頁面桌面化樣式建議
314
+
315
+ 視窗外殼層不會向頁面隱式注入任何 DOM 或全域樣式。為消除「網頁感」,建議應用程式於前端自行補充以下基礎規則:
316
+
317
+ ```css
318
+ html, body { cursor: default; }
319
+ body { user-select: none; }
320
+ input, textarea, .log { user-select: text; cursor: auto; }
321
+ button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
322
+ ```
323
+
324
+ ```html
325
+ <input type="text" spellcheck="false" autocomplete="off" autocapitalize="off">
326
+ ```
327
+
328
+ 尤其建議為輸入框設定 `spellcheck="false"`,否則瀏覽器拼字檢查極易在檔案路徑或專業數值下方繪製紅色波浪底線,破壞桌面應用程式的原生觀感。
329
+
330
+ ### 5. 編譯為單一可執行檔(.exe)
331
+
332
+ 使用 `bun build --compile` 封裝時,可透過匯入 `bun-app-window/embedded` 將原生 DLL 一併內嵌至產物中:
333
+
334
+ ```ts
335
+ import { createShell } from "bun-app-window";
336
+ import { embeddedDll } from "bun-app-window/embedded"; // 將 DLL 位元組內嵌至封裝產物
337
+
338
+ const shell = await createShell({ url, embedded: embeddedDll });
339
+ ```
340
+
341
+ ```bash
342
+ bun build --compile --windows-hide-console src/index.ts --outfile dist/app.exe
343
+ ```
344
+
345
+ 提示:
346
+
347
+ - 僅透過副作用匯入 `import "bun-app-window/embedded";` 同樣生效。
348
+ - 封裝桌面端應用程式時請務必加上 `--windows-hide-console` 參數,否則啟動時將伴隨彈出黑色主控台(Console)視窗。
349
+
350
+ ## 底層機制與互動參考
351
+
352
+ ### 原生動態連結程式庫載入順序
353
+
354
+ 執行階段依以下優先順序尋找並載入 `libshell.dll`:
355
+
356
+ 1. 明確路徑:若傳入 `opts.nativeLibPath`,則直接載入該路徑並略過後續推斷。
357
+ 2. 內嵌位元組釋放:若包含編譯期內嵌位元組,將其釋放至 `%LOCALAPPDATA%\<appName>\runtime` 目錄下(採用內容雜湊值命名,自動清理歷史舊版本)。
358
+ 3. 可執行檔同級目錄:尋找目前 `.exe` 所在目錄下的 `libshell.dll`。
359
+ 4. 開發態路徑:依序尋找套件根目錄下的 `.build/libshell.dll` 與 `dist/libshell.dll`。
360
+
361
+ ### 內建快速鍵與滑鼠行為
362
+
363
+ | 觸發方式 | 預設行為 |
364
+ | --- | --- |
365
+ | `Ctrl +` / `Ctrl -` / `Ctrl 0` | 頁面縮放:放大一階 / 縮小一階 / 重設為 100% |
366
+ | `Ctrl + 滑鼠滾輪` | 頁面縮放:放大 / 縮小 |
367
+ | `F11` | 切換全螢幕模式(隱藏視窗邊框並覆蓋系統工作列) |
368
+ | `F5` / `Ctrl + R` | 重新載入目前頁面 |
369
+ | `F12` | 開啟 WebView2 開發人員工具 |
370
+ | `Alt + Space` / 標題列右鍵 | 彈出 Windows 系統視窗選單 |
371
+
372
+ ## 開發與驗證
373
+
374
+ 本專案工具鏈基於 Bun 1.4.3 與 TypeScript 7.0.2 建構,`tsconfig.json` 開啟全量嚴格模式檢查。品質把關直接採用原生 CLI 指令:
375
+
376
+ ```bash
377
+ bun check # 執行 TypeScript 型別檢查
378
+ bun test # 執行自動化測試套件(位於 tests/,共 103 個測試案例)
379
+ bun run demo # 啟動互動式自我檢測範例程式(需於桌面工作階段下執行)
380
+ ```
381
+
382
+ 注意:每當修改 `src/shell/` 或 `package.json` 後,除執行上述測試外,還需執行一次「作為外部專案相依套件安裝」的整合冒煙測試(Smoke Test),具體流程詳見 `AGENTS.md` 中的「依賴套件驗收」章節。
383
+
384
+ 自動化測試或截圖腳本若需透過 Win32 `EnumWindows` 過濾視窗控制代碼(HWND),請依據視窗模式比對對應的視窗類別名稱(Window Class):
385
+
386
+ | 視窗模式 | 對應視窗類別名稱 |
387
+ | --- | --- |
388
+ | 標準有邊框模式(未傳 `FLAG_FRAMELESS`) | `webview` |
389
+ | 無邊框模式(傳入 `FLAG_FRAMELESS`) | `webview_shell` |
390
+
391
+ ## 致謝
392
+
393
+ - 底層跨平台視窗抽象源自 [webview/webview](https://github.com/webview/webview) `0.12.0`(MIT 授權,作者 Serge Zaitsev · Steffen André Langnes)。儲存庫內 `vendor/webview/` 目錄為其原始碼原樣拷貝,未作任何侵入式修改。
394
+ - WebView2 C/C++ SDK 源自 [Microsoft.Web.WebView2](https://www.nuget.org/packages/Microsoft.Web.WebView2) `1.0.1150.38`(NuGet 套件隨儲存庫發佈以支援離線建構),執行環境由作業系統提供。
395
+ - Co-authored-by DeepSeek
396
+
397
+ ## 授權條款
398
+
399
+ 本專案基於 [MIT License](./LICENSE) 開源發佈。`vendor/webview/` 目錄同樣遵循 MIT 授權條款,其原始著作權聲明完整保留於對應原始碼檔案中。
Binary file
@@ -0,0 +1,67 @@
1
+ #Requires -Version 7.0
2
+ param(
3
+ [string]$Root = (Join-Path $PSScriptRoot '..'),
4
+ [switch]$Clean
5
+ )
6
+
7
+ Set-StrictMode -Version Latest
8
+ $ErrorActionPreference = 'Stop'
9
+
10
+ $wv = Join-Path $Root 'vendor\webview'
11
+ $sdk = Join-Path $Root 'vendor\webview2'
12
+ $out = Join-Path $Root '.build'
13
+ New-Item -ItemType Directory -Force -Path $out | Out-Null
14
+
15
+ if ($Clean -and (Test-Path $out)) {
16
+ Get-ChildItem -File $out | Remove-Item -Force
17
+ }
18
+
19
+ foreach ($p in @(
20
+ (Join-Path $wv 'core\include\webview\webview.h'),
21
+ (Join-Path $wv 'compatibility\mingw\include\EventToken.h'),
22
+ (Join-Path $sdk 'build\native\include\WebView2.h')
23
+ )) {
24
+ if (-not (Test-Path $p)) { throw "missing prerequisite: $p" }
25
+ }
26
+
27
+ $includes = @(
28
+ (Join-Path $wv 'core\include'),
29
+ (Join-Path $wv 'compatibility\mingw\include'),
30
+ (Join-Path $sdk 'build\native\include')
31
+ ) | ForEach-Object { "-I$_" }
32
+
33
+ $libs = @('advapi32', 'ole32', 'oleaut32', 'uuid', 'shell32', 'shlwapi', 'user32', 'version', 'dwmapi') |
34
+ ForEach-Object { "-l$_" }
35
+
36
+ $args = @(
37
+ '-std=c++17', '-O2', '-shared',
38
+ '-static-libgcc', '-static-libstdc++',
39
+ '-DWEBVIEW_STATIC', '-DUNICODE', '-D_UNICODE',
40
+ '-Wall', '-Wextra',
41
+ '-Wno-unknown-pragmas', '-Wno-unused-parameter'
42
+ ) + $includes + @(
43
+ (Join-Path $PSScriptRoot 'shell.cc'),
44
+ '-o', (Join-Path $out 'libshell.dll')
45
+ ) + $libs
46
+
47
+ Write-Host ('clang++ ' + ($args -join ' '))
48
+ Write-Host ''
49
+ & clang++.exe @args
50
+ if ($LASTEXITCODE -ne 0) { throw "clang++ failed: $LASTEXITCODE" }
51
+
52
+ $dll = Join-Path $out 'libshell.dll'
53
+
54
+ # 同时产出一份到 dist/:包分发从这里取(package.json 的 files 与 embedded 入口都指向 dist)
55
+ $distDir = Join-Path $Root 'dist'
56
+ New-Item -ItemType Directory -Force -Path $distDir | Out-Null
57
+ Copy-Item $dll (Join-Path $distDir 'libshell.dll') -Force
58
+
59
+ Write-Host ''
60
+ Write-Host ('built: {0} ({1:N0} bytes)' -f $dll, (Get-Item $dll).Length)
61
+ Write-Host ('staged: {0}' -f (Join-Path $distDir 'libshell.dll'))
62
+ Write-Host ''
63
+ Write-Host '=== exported symbols ==='
64
+ & llvm-nm.exe --defined-only --extern-only --demangle $dll 2>&1 | Select-String 'shell_'
65
+ Write-Host ''
66
+ Write-Host '=== runtime deps ==='
67
+ & llvm-objdump.exe -p $dll 2>&1 | Select-String 'DLL Name'
@@ -0,0 +1,66 @@
1
+ #Requires -Version 7.0
2
+ param(
3
+ [string]$Root = (Join-Path $PSScriptRoot '..'),
4
+ [switch]$Force
5
+ )
6
+
7
+ Set-StrictMode -Version Latest
8
+ $ErrorActionPreference = 'Stop'
9
+
10
+ $vendor = Join-Path $Root 'vendor'
11
+ $wvDir = Join-Path $vendor 'webview'
12
+ $nugetDir = Join-Path $vendor 'webview2'
13
+ New-Item -ItemType Directory -Force -Path $vendor | Out-Null
14
+
15
+ # 1) webview/webview 源码
16
+ if (-not (Test-Path (Join-Path $wvDir '.git'))) {
17
+ Write-Host '[1/3] clone webview/webview ...'
18
+ & git.exe clone --depth 1 https://github.com/webview/webview.git $wvDir 2>&1 | Out-Host
19
+ if ($LASTEXITCODE -ne 0) { throw "git clone failed: $LASTEXITCODE" }
20
+ } else {
21
+ Write-Host '[1/3] webview 已存在,跳过'
22
+ }
23
+
24
+ # 2) WebView2 SDK (NuGet 包就是 zip)
25
+ $pkg = Join-Path $vendor 'Microsoft.Web.WebView2.1.0.1150.38.nupkg'
26
+ if (-not (Test-Path $pkg) -or $Force) {
27
+ Write-Host '[2/3] download WebView2 SDK ...'
28
+ & aria2c.exe --console-log-level=warn --summary-interval=0 --max-tries=3 `
29
+ -d $vendor -o (Split-Path $pkg -Leaf) `
30
+ 'https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/1.0.1150.38'
31
+ if ($LASTEXITCODE -ne 0) { throw "aria2c failed: $LASTEXITCODE" }
32
+ } else {
33
+ Write-Host '[2/3] WebView2 包已存在,跳过'
34
+ }
35
+
36
+ # 3) 解压
37
+ $sdkProbe = Join-Path $nugetDir 'build\native\include\WebView2.h'
38
+ if (-not (Test-Path $sdkProbe) -or $Force) {
39
+ Write-Host '[3/3] extract ...'
40
+ if (Test-Path $nugetDir) { Remove-Item -Recurse -Force $nugetDir }
41
+ & 7z.exe x $pkg "-o$nugetDir" -y | Out-Null
42
+ if ($LASTEXITCODE -ne 0) { throw "7z failed: $LASTEXITCODE" }
43
+ } else {
44
+ Write-Host '[3/3] 已解压,跳过'
45
+ }
46
+
47
+ # 报告
48
+ Write-Host ''
49
+ Write-Host '=== webview HEAD ==='
50
+ & git.exe -C $wvDir log -1 --format='%H %ad %s' --date=short
51
+ Write-Host ''
52
+ Write-Host '=== compat mingw ==='
53
+ Get-ChildItem -Recurse -File (Join-Path $wvDir 'compatibility\mingw\include') |
54
+ ForEach-Object { $_.FullName.Substring($wvDir.Length + 1) }
55
+ Write-Host ''
56
+ Write-Host '=== WebView2 headers ==='
57
+ Get-ChildItem -File (Join-Path $nugetDir 'build\native\include') |
58
+ ForEach-Object { '{0,-34} {1,10:N0}' -f $_.Name, $_.Length }
59
+ Write-Host ''
60
+ Write-Host '=== WebView2 loader (x64) ==='
61
+ Get-ChildItem -File (Join-Path $nugetDir 'build\native\x64') -ErrorAction SilentlyContinue |
62
+ ForEach-Object { '{0,-34} {1,12:N0}' -f $_.Name, $_.Length }
63
+ Write-Host ''
64
+ Write-Host '=== webview 版本 ==='
65
+ Select-String -Path (Join-Path $wvDir 'core\include\webview\version.h') -Pattern 'WEBVIEW_VERSION_(MAJOR|MINOR|PATCH)' |
66
+ ForEach-Object { $_.Line.Trim() }