bun-app-window 0.6.2 → 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.
- package/CHANGELOG.md +302 -63
- package/README.md +265 -146
- package/README.zh-CN.md +249 -147
- package/README.zh-TW.md +252 -146
- package/dist/libshell.dll +0 -0
- package/package.json +9 -5
- package/src/shell/app.ts +255 -2
- package/src/shell/assets.d.ts +4 -4
- package/src/shell/embedded.ts +9 -10
- package/src/shell/index.ts +280 -279
- package/src/shell/ipc.ts +91 -33
- package/src/shell/native.ts +64 -62
- package/src/shell/pack.ts +247 -28
- package/src/shell/port.ts +37 -38
- package/src/shell/resolve.ts +121 -140
- package/src/shell/state.ts +8 -19
- package/src/shell/types.ts +79 -81
- package/src/ui/titlebar.ts +1 -2
package/src/shell/resolve.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
47
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
59
48
|
}
|
|
60
49
|
|
|
61
50
|
function runtimeDir(appName: string): string {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
98
|
+
return { path: finalPath, reused: false };
|
|
114
99
|
}
|
|
115
100
|
|
|
116
101
|
/** 删掉与当前哈希不符的旧副本;正被其它实例占用的删除失败则忽略 */
|
|
117
102
|
function pruneOldVersions(dir: string, keepName: string): string[] {
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
}
|
package/src/shell/state.ts
CHANGED
|
@@ -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
|
|
281
|
-
|
|
282
|
-
|
|
267
|
+
pending = pending
|
|
268
|
+
.then(() => savePlacement(file, now))
|
|
269
|
+
.catch(() => {
|
|
270
|
+
// 写盘失败不该打断应用;下一次变化会重试
|
|
271
|
+
});
|
|
283
272
|
};
|
|
284
273
|
|
|
285
274
|
const scheduleWrite = (): void => {
|
package/src/shell/types.ts
CHANGED
|
@@ -171,14 +171,14 @@ export const BROWSER_UI_ALL =
|
|
|
171
171
|
* `Shell.beginResize()` 在 TS 层查表转换,本常量成为真正的入参契约。
|
|
172
172
|
*/
|
|
173
173
|
export const RESIZE_EDGE = {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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 };
|
package/src/ui/titlebar.ts
CHANGED
|
@@ -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);
|