bun-app-window 0.7.0 → 0.8.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.
@@ -14,15 +14,7 @@
14
14
  */
15
15
 
16
16
  import { createHash } from "node:crypto";
17
- import {
18
- existsSync,
19
- mkdirSync,
20
- readdirSync,
21
- renameSync,
22
- rmSync,
23
- statSync,
24
- writeFileSync,
25
- } from "node:fs";
17
+ import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
26
18
  import { homedir } from "node:os";
27
19
  import { basename, dirname, join, resolve } from "node:path";
28
20
 
@@ -48,176 +40,165 @@ const INSTALLED = /[\\/]node_modules[\\/]/i.test(PKG_ROOT);
48
40
 
49
41
  /** 是否运行在 `bun build --compile` 产物内 */
50
42
  export function isPackedRuntime(): boolean {
51
- return (
52
- /^[A-Za-z]:[\\/]~BUN[\\/]/i.test(import.meta.dir) ||
53
- import.meta.dir.startsWith("/$bunfs/")
54
- );
43
+ return /^[A-Za-z]:[\\/]~BUN[\\/]/i.test(import.meta.dir) || import.meta.dir.startsWith("/$bunfs/");
55
44
  }
56
45
 
57
46
  function sha256Hex(buf: Uint8Array): string {
58
- return createHash("sha256").update(buf).digest("hex");
47
+ return createHash("sha256").update(buf).digest("hex");
59
48
  }
60
49
 
61
50
  function runtimeDir(appName: string): string {
62
- const base =
63
- process.env.LOCALAPPDATA && process.env.LOCALAPPDATA.length > 0
64
- ? process.env.LOCALAPPDATA
65
- : join(homedir(), "AppData", "Local");
66
- return join(base, appName, "runtime");
51
+ const base =
52
+ process.env.LOCALAPPDATA && process.env.LOCALAPPDATA.length > 0
53
+ ? process.env.LOCALAPPDATA
54
+ : join(homedir(), "AppData", "Local");
55
+ return join(base, appName, "runtime");
67
56
  }
68
57
 
69
58
  function fromPath(path: string, source: NativeLibInfo["source"]): NativeLibInfo {
70
- return {
71
- path,
72
- source,
73
- version: PKG_VERSION,
74
- sha256: "",
75
- bytes: statSync(path).size,
76
- reused: false,
77
- pruned: [],
78
- };
59
+ return {
60
+ path,
61
+ source,
62
+ version: PKG_VERSION,
63
+ sha256: "",
64
+ bytes: statSync(path).size,
65
+ reused: false,
66
+ pruned: [],
67
+ };
79
68
  }
80
69
 
81
70
  /** 写入目标目录;同名同大小视为可复用,写入走临时文件 + rename 保证原子性 */
82
- function materialize(
83
- dir: string,
84
- bytes: Uint8Array,
85
- sha256: string,
86
- ): { path: string; reused: boolean } {
87
- mkdirSync(dir, { recursive: true });
71
+ function materialize(dir: string, bytes: Uint8Array, sha256: string): { path: string; reused: boolean } {
72
+ mkdirSync(dir, { recursive: true });
73
+
74
+ const finalName = `libshell-${sha256.slice(0, 16)}.dll`;
75
+ const finalPath = join(dir, finalName);
76
+
77
+ if (existsSync(finalPath)) {
78
+ try {
79
+ if (statSync(finalPath).size === bytes.length) {
80
+ return { path: finalPath, reused: true };
81
+ }
82
+ } catch {
83
+ // 读不到就重写
84
+ }
85
+ }
88
86
 
89
- const finalName = `libshell-${sha256.slice(0, 16)}.dll`;
90
- const finalPath = join(dir, finalName);
87
+ const tmpPath = join(dir, `.tmp-${process.pid}-${Date.now().toString(36)}.dll`);
88
+ writeFileSync(tmpPath, bytes);
91
89
 
92
- if (existsSync(finalPath)) {
93
90
  try {
94
- if (statSync(finalPath).size === bytes.length) {
95
- return { path: finalPath, reused: true };
96
- }
97
- } catch {
98
- // 读不到就重写
91
+ renameSync(tmpPath, finalPath);
92
+ } catch (err) {
93
+ // Windows 上目标已存在时 rename 会失败:另一个实例可能刚好抢先写入
94
+ rmSync(tmpPath, { force: true });
95
+ if (!existsSync(finalPath)) throw err;
99
96
  }
100
- }
101
-
102
- const tmpPath = join(dir, `.tmp-${process.pid}-${Date.now().toString(36)}.dll`);
103
- writeFileSync(tmpPath, bytes);
104
-
105
- try {
106
- renameSync(tmpPath, finalPath);
107
- } catch (err) {
108
- // Windows 上目标已存在时 rename 会失败:另一个实例可能刚好抢先写入
109
- rmSync(tmpPath, { force: true });
110
- if (!existsSync(finalPath)) throw err;
111
- }
112
97
 
113
- return { path: finalPath, reused: false };
98
+ return { path: finalPath, reused: false };
114
99
  }
115
100
 
116
101
  /** 删掉与当前哈希不符的旧副本;正被其它实例占用的删除失败则忽略 */
117
102
  function pruneOldVersions(dir: string, keepName: string): string[] {
118
- const pruned: string[] = [];
119
- let entries: string[];
120
- try {
121
- entries = readdirSync(dir);
122
- } catch {
123
- return pruned;
124
- }
125
-
126
- for (const name of entries) {
127
- if (!DLL_PATTERN.test(name)) continue;
128
- if (name === keepName) continue;
103
+ const pruned: string[] = [];
104
+ let entries: string[];
129
105
  try {
130
- rmSync(join(dir, name), { force: true });
131
- pruned.push(name);
106
+ entries = readdirSync(dir);
132
107
  } catch {
133
- // 正被其它实例加载,跳过
108
+ return pruned;
109
+ }
110
+
111
+ for (const name of entries) {
112
+ if (!DLL_PATTERN.test(name)) continue;
113
+ if (name === keepName) continue;
114
+ try {
115
+ rmSync(join(dir, name), { force: true });
116
+ pruned.push(name);
117
+ } catch {
118
+ // 正被其它实例加载,跳过
119
+ }
134
120
  }
135
- }
136
- return pruned;
121
+ return pruned;
137
122
  }
138
123
 
139
124
  /** 从 Bun.embeddedFiles 找内嵌 DLL;没有则返回 null */
140
125
  async function readBunEmbedded(): Promise<Uint8Array | null> {
141
- const files = (Bun as unknown as { embeddedFiles?: ReadonlyArray<Blob & { name: string }> })
142
- .embeddedFiles;
143
- if (!files || files.length === 0) return null;
144
-
145
- const hit = files.find((f) => DLL_PATTERN.test(f.name));
146
- if (!hit) return null;
147
-
148
- const buf = new Uint8Array(await hit.arrayBuffer());
149
- if (buf.length < 2 || buf[0] !== 0x4d || buf[1] !== 0x5a) {
150
- throw new Error(`embedded native library is not a PE file (${hit.name})`);
151
- }
152
- return buf;
126
+ const files = (Bun as unknown as { embeddedFiles?: ReadonlyArray<Blob & { name: string }> }).embeddedFiles;
127
+ if (!files || files.length === 0) return null;
128
+
129
+ const hit = files.find((f) => DLL_PATTERN.test(f.name));
130
+ if (!hit) return null;
131
+
132
+ const buf = new Uint8Array(await hit.arrayBuffer());
133
+ if (buf.length < 2 || buf[0] !== 0x4d || buf[1] !== 0x5a) {
134
+ throw new Error(`embedded native library is not a PE file (${hit.name})`);
135
+ }
136
+ return buf;
153
137
  }
154
138
 
155
139
  export type ResolveOptions = {
156
- nativeLibPath?: string;
157
- appName?: string;
158
- embedded?: () => Promise<Uint8Array | null>;
140
+ nativeLibPath?: string;
141
+ appName?: string;
142
+ embedded?: () => Promise<Uint8Array | null>;
159
143
  };
160
144
 
161
145
  export async function resolveNativeLib(opts: ResolveOptions = {}): Promise<NativeLibInfo> {
162
- const appName = opts.appName ?? "BunAppWindow";
163
-
164
- // 1. 显式指定
165
- if (opts.nativeLibPath) {
166
- if (!existsSync(opts.nativeLibPath)) {
167
- throw new Error(`nativeLibPath does not exist: ${opts.nativeLibPath}`);
146
+ const appName = opts.appName ?? "BunAppWindow";
147
+
148
+ // 1. 显式指定
149
+ if (opts.nativeLibPath) {
150
+ if (!existsSync(opts.nativeLibPath)) {
151
+ throw new Error(`nativeLibPath does not exist: ${opts.nativeLibPath}`);
152
+ }
153
+ return fromPath(opts.nativeLibPath, "explicit");
168
154
  }
169
- return fromPath(opts.nativeLibPath, "explicit");
170
- }
171
-
172
- if (isPackedRuntime()) {
173
- // 2. 内嵌字节 → 释放到用户目录
174
- const bytes = opts.embedded ? await opts.embedded() : await readBunEmbedded();
175
- if (bytes) {
176
- const sha256 = sha256Hex(bytes);
177
- try {
178
- const { path, reused } = materialize(runtimeDir(appName), bytes, sha256);
179
- return {
180
- path,
181
- source: "embedded",
182
- version: PKG_VERSION,
183
- sha256,
184
- bytes: bytes.length,
185
- reused,
186
- pruned: pruneOldVersions(dirname(path), basename(path)),
187
- };
188
- } catch {
189
- // %LOCALAPPDATA% 不可写(受限环境):继续尝试同级文件
190
- }
155
+
156
+ if (isPackedRuntime()) {
157
+ // 2. 内嵌字节 → 释放到用户目录
158
+ const bytes = opts.embedded ? await opts.embedded() : await readBunEmbedded();
159
+ if (bytes) {
160
+ const sha256 = sha256Hex(bytes);
161
+ try {
162
+ const { path, reused } = materialize(runtimeDir(appName), bytes, sha256);
163
+ return {
164
+ path,
165
+ source: "embedded",
166
+ version: PKG_VERSION,
167
+ sha256,
168
+ bytes: bytes.length,
169
+ reused,
170
+ pruned: pruneOldVersions(dirname(path), basename(path)),
171
+ };
172
+ } catch {
173
+ // %LOCALAPPDATA% 不可写(受限环境):继续尝试同级文件
174
+ }
175
+ }
176
+
177
+ // 3. 可执行文件同级
178
+ for (const candidate of [
179
+ join(dirname(process.execPath), "libshell.dll"),
180
+ join(dirname(process.execPath), "dist", "libshell.dll"),
181
+ ]) {
182
+ if (existsSync(candidate)) return fromPath(candidate, "package");
183
+ }
184
+
185
+ throw new Error(
186
+ "cannot locate libshell.dll in a compiled executable. " +
187
+ "Either place libshell.dll next to the executable, or import " +
188
+ "`bun-app-window/embedded` so it gets bundled. " +
189
+ `Searched: ${dirname(process.execPath)}`,
190
+ );
191
191
  }
192
192
 
193
- // 3. 可执行文件同级
194
- for (const candidate of [
195
- join(dirname(process.execPath), "libshell.dll"),
196
- join(dirname(process.execPath), "dist", "libshell.dll"),
197
- ]) {
198
- if (existsSync(candidate)) return fromPath(candidate, "package");
193
+ // 4. 开发态 / 包安装态:包根下的 .build/ 再 dist/
194
+ const source: NativeLibInfo["source"] = INSTALLED ? "package" : "dev";
195
+ for (const candidate of [join(PKG_ROOT, ".build", "libshell.dll"), join(PKG_ROOT, "dist", "libshell.dll")]) {
196
+ if (existsSync(candidate)) return fromPath(candidate, source);
199
197
  }
200
198
 
201
199
  throw new Error(
202
- "cannot locate libshell.dll in a compiled executable. " +
203
- "Either place libshell.dll next to the executable, or import " +
204
- "`bun-app-window/embedded` so it gets bundled. " +
205
- `Searched: ${dirname(process.execPath)}`,
200
+ "libshell.dll not found. Build the native layer first: " +
201
+ "`bun run native/build.ts`. " +
202
+ `Searched under ${PKG_ROOT}`,
206
203
  );
207
- }
208
-
209
- // 4. 开发态 / 包安装态:包根下的 .build/ 再 dist/
210
- const source: NativeLibInfo["source"] = INSTALLED ? "package" : "dev";
211
- for (const candidate of [
212
- join(PKG_ROOT, ".build", "libshell.dll"),
213
- join(PKG_ROOT, "dist", "libshell.dll"),
214
- ]) {
215
- if (existsSync(candidate)) return fromPath(candidate, source);
216
- }
217
-
218
- throw new Error(
219
- "libshell.dll not found. Build the native layer first: " +
220
- "`bun run native/build.ts`. " +
221
- `Searched under ${PKG_ROOT}`,
222
- );
223
204
  }
@@ -84,14 +84,7 @@ export function parseBounds(text: string): WindowPlacement | null {
84
84
  if (nums.some((n) => !Number.isFinite(n))) {
85
85
  return null;
86
86
  }
87
- const [x, y, width, height, maximized, dpi] = nums as [
88
- number,
89
- number,
90
- number,
91
- number,
92
- number,
93
- number,
94
- ];
87
+ const [x, y, width, height, maximized, dpi] = nums as [number, number, number, number, number, number];
95
88
  // 尺寸为 0 或负数说明窗口还没建好(原生在无窗口时返回 0 长度文本,这里再兜一层)
96
89
  if (width <= 0 || height <= 0 || dpi <= 0) {
97
90
  return null;
@@ -217,15 +210,9 @@ export type PlacementStoreOptions = {
217
210
  * 首次 `attach` 会立刻抓一次快照并写盘——程序若在用户操作前崩溃,也还有
218
211
  * 初始位置可恢复。
219
212
  */
220
- export function attachPlacementStore(
221
- shell: Shell,
222
- file: string,
223
- opts: PlacementStoreOptions = {},
224
- ): PlacementStore {
213
+ export function attachPlacementStore(shell: Shell, file: string, opts: PlacementStoreOptions = {}): PlacementStore {
225
214
  const debounceMs = Math.max(0, opts.debounceMs ?? DEFAULTS.debounceMs);
226
- const pump =
227
- opts.events ??
228
- attachEventPump(shell, { intervalMs: Math.max(1, opts.pumpMs ?? DEFAULTS.pumpMs) });
215
+ const pump = opts.events ?? attachEventPump(shell, { intervalMs: Math.max(1, opts.pumpMs ?? DEFAULTS.pumpMs) });
229
216
  const ownsPump = opts.events === undefined;
230
217
 
231
218
  let last: WindowPlacement | null = null;
@@ -277,9 +264,11 @@ export function attachPlacementStore(
277
264
  return;
278
265
  }
279
266
  // 串行化写入:并发 rename 同一目标会互相踩
280
- pending = pending.then(() => savePlacement(file, now)).catch(() => {
281
- // 写盘失败不该打断应用;下一次变化会重试
282
- });
267
+ pending = pending
268
+ .then(() => savePlacement(file, now))
269
+ .catch(() => {
270
+ // 写盘失败不该打断应用;下一次变化会重试
271
+ });
283
272
  };
284
273
 
285
274
  const scheduleWrite = (): void => {
@@ -171,14 +171,14 @@ export const BROWSER_UI_ALL =
171
171
  * `Shell.beginResize()` 在 TS 层查表转换,本常量成为真正的入参契约。
172
172
  */
173
173
  export const RESIZE_EDGE = {
174
- LEFT: 10,
175
- RIGHT: 11,
176
- TOP: 12,
177
- TOPLEFT: 13,
178
- TOPRIGHT: 14,
179
- BOTTOM: 15,
180
- BOTTOMLEFT: 16,
181
- BOTTOMRIGHT: 17,
174
+ LEFT: 10,
175
+ RIGHT: 11,
176
+ TOP: 12,
177
+ TOPLEFT: 13,
178
+ TOPRIGHT: 14,
179
+ BOTTOM: 15,
180
+ BOTTOMLEFT: 16,
181
+ BOTTOMRIGHT: 17,
182
182
  } as const;
183
183
 
184
184
  export type ResizeEdge = (typeof RESIZE_EDGE)[keyof typeof RESIZE_EDGE];
@@ -201,14 +201,14 @@ export type ResizeEdge = (typeof RESIZE_EDGE)[keyof typeof RESIZE_EDGE];
201
201
  * 时需要它,而那种时候手写映射必然抄错。
202
202
  */
203
203
  export const RESIZE_INDEX: Readonly<Record<number, number>> = {
204
- [RESIZE_EDGE.LEFT]: 0,
205
- [RESIZE_EDGE.RIGHT]: 1,
206
- [RESIZE_EDGE.TOP]: 2,
207
- [RESIZE_EDGE.BOTTOM]: 3,
208
- [RESIZE_EDGE.TOPLEFT]: 4,
209
- [RESIZE_EDGE.TOPRIGHT]: 5,
210
- [RESIZE_EDGE.BOTTOMLEFT]: 6,
211
- [RESIZE_EDGE.BOTTOMRIGHT]: 7,
204
+ [RESIZE_EDGE.LEFT]: 0,
205
+ [RESIZE_EDGE.RIGHT]: 1,
206
+ [RESIZE_EDGE.TOP]: 2,
207
+ [RESIZE_EDGE.BOTTOM]: 3,
208
+ [RESIZE_EDGE.TOPLEFT]: 4,
209
+ [RESIZE_EDGE.TOPRIGHT]: 5,
210
+ [RESIZE_EDGE.BOTTOMLEFT]: 6,
211
+ [RESIZE_EDGE.BOTTOMRIGHT]: 7,
212
212
  };
213
213
 
214
214
  /**
@@ -218,7 +218,7 @@ export const RESIZE_INDEX: Readonly<Record<number, number>> = {
218
218
  * `rc=-1`,与「窗口已销毁」是同一个外部表现,调用方按 `rc` 处理即可。
219
219
  */
220
220
  export function resizeIndex(edge: number): number {
221
- return RESIZE_INDEX[edge] ?? -1;
221
+ return RESIZE_INDEX[edge] ?? -1;
222
222
  }
223
223
 
224
224
  /**
@@ -230,9 +230,7 @@ export function resizeIndex(edge: number): number {
230
230
  *
231
231
  * 过去消费者只能自己抄一份(抄了 14 个数,没有任何门禁能发现它与 C++ 漂移)。
232
232
  */
233
- export const ZOOM_LADDER = [
234
- 0.25, 0.33, 0.5, 0.67, 0.8, 0.9, 1.0, 1.1, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0,
235
- ] as const;
233
+ export const ZOOM_LADDER = [0.25, 0.33, 0.5, 0.67, 0.8, 0.9, 1.0, 1.1, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0] as const;
236
234
 
237
235
  /** 缩放倍率的上下限(`ZOOM_LADDER` 的首尾两项) */
238
236
  export const ZOOM_MIN = ZOOM_LADDER[0];
@@ -275,74 +273,74 @@ export const STATE_MINIMIZED = 2;
275
273
  export const STATE_FOCUSED = 4;
276
274
 
277
275
  export type WindowState = {
278
- raw: number;
279
- maximized: boolean;
280
- minimized: boolean;
281
- focused: boolean;
282
- /** 系统当前是否深色(原生侧读 AppsUseLightTheme,并随 WM_SETTINGCHANGE 刷新) */
283
- dark: boolean;
276
+ raw: number;
277
+ maximized: boolean;
278
+ minimized: boolean;
279
+ focused: boolean;
280
+ /** 系统当前是否深色(原生侧读 AppsUseLightTheme,并随 WM_SETTINGCHANGE 刷新) */
281
+ dark: boolean;
284
282
  };
285
283
 
286
284
  /** 原生库的解析结果 */
287
285
  export type NativeLibInfo = {
288
- path: string;
289
- /**
290
- * explicit — 调用方显式指定
291
- * dev — 本仓开发态(build/ 或 dist/)
292
- * package — 包内 dist/ 或可执行文件同级
293
- * embedded — 从编译产物内嵌字节释放
294
- * fallback-tmp — 受限环境下退回 Bun 默认释放
295
- */
296
- source: "explicit" | "dev" | "package" | "embedded" | "fallback-tmp";
297
- /**
298
- * `bun-app-window` 自身的版本号(取自包内 `package.json`,不是 DLL 的 PE 版本)。
299
- *
300
- * 加它的理由:消费者要在界面上显示「连上的确实是这个版本」时,过去只能
301
- * `import libPkg from "bun-app-window/package.json"` 再拼字符串——那是一条
302
- * 额外的子路径依赖,且 `package.json` 在 `files` 白名单里所以要跟着发布面走。
303
- * 现在顺手带出来,页面侧直接读 `shell.libInfo.version`。
304
- */
305
- version: string;
306
- sha256: string;
307
- bytes: number;
308
- reused: boolean;
309
- pruned: string[];
286
+ path: string;
287
+ /**
288
+ * explicit — 调用方显式指定
289
+ * dev — 本仓开发态(build/ 或 dist/)
290
+ * package — 包内 dist/ 或可执行文件同级
291
+ * embedded — 从编译产物内嵌字节释放
292
+ * fallback-tmp — 受限环境下退回 Bun 默认释放
293
+ */
294
+ source: "explicit" | "dev" | "package" | "embedded" | "fallback-tmp";
295
+ /**
296
+ * `bun-app-window` 自身的版本号(取自包内 `package.json`,不是 DLL 的 PE 版本)。
297
+ *
298
+ * 加它的理由:消费者要在界面上显示「连上的确实是这个版本」时,过去只能
299
+ * `import libPkg from "bun-app-window/package.json"` 再拼字符串——那是一条
300
+ * 额外的子路径依赖,且 `package.json` 在 `files` 白名单里所以要跟着发布面走。
301
+ * 现在顺手带出来,页面侧直接读 `shell.libInfo.version`。
302
+ */
303
+ version: string;
304
+ sha256: string;
305
+ bytes: number;
306
+ reused: boolean;
307
+ pruned: string[];
310
308
  };
311
309
 
312
310
  export type ShellOptions = {
313
- /** 要加载的 URL(通常是本地回环地址) */
314
- url: string;
315
- title?: string;
316
- width?: number;
317
- height?: number;
318
- /** FLAG_* 位组合 */
319
- flags?: number;
320
- /** 启动后立即设置的最小尺寸(逻辑像素) */
321
- minWidth?: number;
322
- minHeight?: number;
323
- /**
324
- * 原生库路径。给定时跳过所有自动解析。
325
- * 用于测试或多副本部署。
326
- */
327
- nativeLibPath?: string;
328
- /**
329
- * 运行时数据目录名(`%LOCALAPPDATA%\<appName>\runtime`)。
330
- * 同一机器上多个应用共用一个包时应各自指定,避免互相清理 DLL 副本。
331
- */
332
- appName?: string;
333
- /**
334
- * 关闭哪些浏览器行为,取 `BROWSER_UI_*` 的按位或。缺省 0(保持 WebView2 默认)。
335
- *
336
- * 桌面应用一般传 `BROWSER_UI_ALL`:关掉右键 Edge 菜单、`Ctrl+P`/`Ctrl+F`/`Ctrl+S`、
337
- * 左下角状态栏、触控板滑动导航与表单自动填充。关掉默认右键菜单后若要右键有反应,
338
- * 用 `showContextMenu()` 弹原生菜单。
339
- */
340
- browserUi?: number;
341
- /**
342
- * 编译产物内嵌字节的提供者。
343
- * 使用 `bun build --compile` 时传入 `embeddedDll`(见 `bun-app-window/embedded`)。
344
- */
345
- embedded?: () => Promise<Uint8Array | null>;
311
+ /** 要加载的 URL(通常是本地回环地址) */
312
+ url: string;
313
+ title?: string;
314
+ width?: number;
315
+ height?: number;
316
+ /** FLAG_* 位组合 */
317
+ flags?: number;
318
+ /** 启动后立即设置的最小尺寸(逻辑像素) */
319
+ minWidth?: number;
320
+ minHeight?: number;
321
+ /**
322
+ * 原生库路径。给定时跳过所有自动解析。
323
+ * 用于测试或多副本部署。
324
+ */
325
+ nativeLibPath?: string;
326
+ /**
327
+ * 运行时数据目录名(`%LOCALAPPDATA%\<appName>\runtime`)。
328
+ * 同一机器上多个应用共用一个包时应各自指定,避免互相清理 DLL 副本。
329
+ */
330
+ appName?: string;
331
+ /**
332
+ * 关闭哪些浏览器行为,取 `BROWSER_UI_*` 的按位或。缺省 0(保持 WebView2 默认)。
333
+ *
334
+ * 桌面应用一般传 `BROWSER_UI_ALL`:关掉右键 Edge 菜单、`Ctrl+P`/`Ctrl+F`/`Ctrl+S`、
335
+ * 左下角状态栏、触控板滑动导航与表单自动填充。关掉默认右键菜单后若要右键有反应,
336
+ * 用 `showContextMenu()` 弹原生菜单。
337
+ */
338
+ browserUi?: number;
339
+ /**
340
+ * 编译产物内嵌字节的提供者。
341
+ * 使用 `bun build --compile` 时传入 `embeddedDll`(见 `bun-app-window/embedded`)。
342
+ */
343
+ embedded?: () => Promise<Uint8Array | null>;
346
344
  };
347
345
 
348
346
  export type StartResult = { rc: number; error: string };
@@ -479,8 +479,7 @@ export function mountTitleBar(opts: TitleBarOptions): TitleBarHandle {
479
479
  if (!nativeCaptions) {
480
480
  for (const action of ["minimize", "maximize", "close"] as const) {
481
481
  const btn = doc.createElement("button");
482
- btn.className =
483
- action === "close" ? "ws-titlebar__btn ws-titlebar__btn--close" : "ws-titlebar__btn";
482
+ btn.className = action === "close" ? "ws-titlebar__btn ws-titlebar__btn--close" : "ws-titlebar__btn";
484
483
  btn.setAttribute("type", "button");
485
484
  btn.setAttribute("data-act", action);
486
485
  btn.textContent = glyphFor(icons, action, opts.maximized === true);