sparkbun 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.
- package/bin/sparkbun.cjs +18 -0
- package/dist-linux-arm64/bsdiff +0 -0
- package/dist-linux-arm64/bspatch +0 -0
- package/dist-linux-arm64/libElectrobunCore.so +0 -0
- package/dist-linux-arm64/libNativeWrapper.so +0 -0
- package/dist-linux-arm64/libasar.so +0 -0
- package/dist-linux-x64/bsdiff +0 -0
- package/dist-linux-x64/bspatch +0 -0
- package/dist-linux-x64/libElectrobunCore.so +0 -0
- package/dist-linux-x64/libNativeWrapper.so +0 -0
- package/dist-linux-x64/libasar.so +0 -0
- package/dist-macos-arm64/bsdiff +0 -0
- package/dist-macos-arm64/bspatch +0 -0
- package/dist-macos-arm64/libElectrobunCore.dylib +0 -0
- package/dist-macos-arm64/libNativeWrapper.dylib +0 -0
- package/dist-macos-arm64/libasar.dylib +0 -0
- package/dist-macos-arm64/libwebgpu_dawn.dylib +0 -0
- package/dist-macos-arm64/preload-full.js +885 -0
- package/dist-macos-arm64/preload-sandboxed.js +111 -0
- package/dist-macos-arm64/process_helper +0 -0
- package/dist-win-x64/ElectrobunCore.dll +0 -0
- package/dist-win-x64/WebView2Loader.dll +0 -0
- package/dist-win-x64/bsdiff.exe +0 -0
- package/dist-win-x64/bspatch.exe +0 -0
- package/dist-win-x64/libNativeWrapper.dll +0 -0
- package/dist-win-x64/zig-asar/arm64/libasar.dll +0 -0
- package/dist-win-x64/zig-asar/x64/libasar.dll +0 -0
- package/package.json +47 -0
- package/scripts/build-and-upload-artifacts.js +207 -0
- package/scripts/gen-webgpu-ffi.mjs +162 -0
- package/scripts/install-windows-deps.ps1 +80 -0
- package/scripts/package-release.js +237 -0
- package/scripts/push-version.js +84 -0
- package/scripts/update-bun-version.ts +122 -0
- package/scripts/update-cef-version.ts +145 -0
- package/src/browser/builtinrpcSchema.ts +19 -0
- package/src/browser/global.d.ts +36 -0
- package/src/browser/index.ts +234 -0
- package/src/browser/webviewtag.ts +88 -0
- package/src/browser/wgputag.ts +48 -0
- package/src/bun/SparkBunConfig.ts +497 -0
- package/src/bun/__tests__/ffi-contract.test.ts +105 -0
- package/src/bun/core/ApplicationMenu.ts +70 -0
- package/src/bun/core/BrowserView.ts +416 -0
- package/src/bun/core/BrowserWindow.ts +396 -0
- package/src/bun/core/BuildConfig.ts +71 -0
- package/src/bun/core/ContextMenu.ts +75 -0
- package/src/bun/core/GpuWindow.ts +289 -0
- package/src/bun/core/Paths.ts +5 -0
- package/src/bun/core/Socket.ts +22 -0
- package/src/bun/core/Tray.ts +197 -0
- package/src/bun/core/Updater.ts +1131 -0
- package/src/bun/core/Utils.ts +487 -0
- package/src/bun/core/WGPUView.ts +167 -0
- package/src/bun/core/menuRoles.ts +181 -0
- package/src/bun/events/ApplicationEvents.ts +22 -0
- package/src/bun/events/event.ts +27 -0
- package/src/bun/events/eventEmitter.ts +45 -0
- package/src/bun/events/trayEvents.ts +11 -0
- package/src/bun/events/webviewEvents.ts +39 -0
- package/src/bun/events/windowEvents.ts +23 -0
- package/src/bun/index.ts +120 -0
- package/src/bun/preload/.generated/compiled.ts +2 -0
- package/src/bun/preload/build.ts +65 -0
- package/src/bun/preload/dragRegions.ts +41 -0
- package/src/bun/preload/encryption.ts +86 -0
- package/src/bun/preload/events.ts +171 -0
- package/src/bun/preload/globals.d.ts +45 -0
- package/src/bun/preload/index-sandboxed.ts +28 -0
- package/src/bun/preload/index.ts +77 -0
- package/src/bun/preload/internalRpc.ts +80 -0
- package/src/bun/preload/overlaySync.ts +107 -0
- package/src/bun/preload/webviewTag.ts +451 -0
- package/src/bun/preload/wgpuTag.ts +246 -0
- package/src/bun/proc/linux.md +43 -0
- package/src/bun/proc/native.ts +3253 -0
- package/src/bun/webGPU.ts +346 -0
- package/src/bun/webgpuAdapter.ts +3011 -0
- package/src/cli/bun.lockb +0 -0
- package/src/cli/index.ts +4653 -0
- package/src/cli/package-lock.json +81 -0
- package/src/cli/package.json +11 -0
- package/src/cli/templates/embedded.ts +2 -0
- package/src/core/build.zig +16 -0
- package/src/core/main.zig +3378 -0
- package/src/extractor/build.zig +22 -0
- package/src/installer/installer-template.ts +216 -0
- package/src/launcher/main.ts +221 -0
- package/src/native/build/libNativeWrapper.so +0 -0
- package/src/native/linux/build/nativeWrapper.o +0 -0
- package/src/native/linux/cef_loader.cpp +110 -0
- package/src/native/linux/cef_loader.h +28 -0
- package/src/native/linux/cef_process_helper_linux.cpp +160 -0
- package/src/native/linux/nativeWrapper.cpp +11768 -0
- package/src/native/macos/cef_process_helper_mac.cc +160 -0
- package/src/native/macos/nativeWrapper.mm +9172 -0
- package/src/native/shared/accelerator_parser.h +72 -0
- package/src/native/shared/app_paths.h +110 -0
- package/src/native/shared/asar.h +35 -0
- package/src/native/shared/cache_migration.h +244 -0
- package/src/native/shared/callbacks.h +57 -0
- package/src/native/shared/cef_response_filter.h +189 -0
- package/src/native/shared/chromium_flags.h +181 -0
- package/src/native/shared/config.h +66 -0
- package/src/native/shared/download_event.h +197 -0
- package/src/native/shared/ffi_helpers.h +139 -0
- package/src/native/shared/glob_match.h +59 -0
- package/src/native/shared/json_menu_parser.h +223 -0
- package/src/native/shared/mime_types.h +101 -0
- package/src/native/shared/navigation_rules.h +98 -0
- package/src/native/shared/partition_context.h +137 -0
- package/src/native/shared/pending_resize_queue.h +45 -0
- package/src/native/shared/permissions.h +118 -0
- package/src/native/shared/permissions_cef.h +74 -0
- package/src/native/shared/preload_script.h +71 -0
- package/src/native/shared/shutdown_guard.h +134 -0
- package/src/native/shared/thread_safe_map.h +138 -0
- package/src/native/shared/webview_storage.h +91 -0
- package/src/native/win/cef_process_helper_win.cpp +143 -0
- package/src/native/win/dcomp_compositor.h +352 -0
- package/src/native/win/nativeWrapper.cpp +12434 -0
- package/src/npmbin/index.js +34 -0
- package/src/shared/bun-version.ts +3 -0
- package/src/shared/cef-version.ts +5 -0
- package/src/shared/naming.test.ts +327 -0
- package/src/shared/naming.ts +188 -0
- package/src/shared/platform.ts +48 -0
- package/src/shared/rpc.ts +541 -0
- package/src/shared/sparkbun-version.ts +2 -0
- package/src/types/three.d.ts +1 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { ffi } from "../proc/native";
|
|
2
|
+
import sparkBunEventEmitter from "../events/eventEmitter";
|
|
3
|
+
import { BrowserView } from "./BrowserView";
|
|
4
|
+
import { type Pointer } from "bun:ffi";
|
|
5
|
+
import { BuildConfig } from "./BuildConfig";
|
|
6
|
+
import { type RPCWithTransport } from "../../shared/rpc.js";
|
|
7
|
+
import { WGPUView } from "./WGPUView";
|
|
8
|
+
|
|
9
|
+
const buildConfig = BuildConfig.getSync();
|
|
10
|
+
ffi.request.setExitOnLastWindowClosed({
|
|
11
|
+
enabled: buildConfig.runtime?.exitOnLastWindowClosed ?? true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type WindowOptionsType<T = undefined> = {
|
|
15
|
+
trafficLightOffset?: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
};
|
|
19
|
+
activate?: boolean;
|
|
20
|
+
title: string;
|
|
21
|
+
frame: {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
};
|
|
27
|
+
url: string | null;
|
|
28
|
+
html: string | null;
|
|
29
|
+
preload: string | null;
|
|
30
|
+
viewsRoot: string | null;
|
|
31
|
+
renderer: "native" | "cef";
|
|
32
|
+
rpc?: T;
|
|
33
|
+
styleMask?: {};
|
|
34
|
+
// titleBarStyle options:
|
|
35
|
+
// - 'default': normal titlebar with native window controls
|
|
36
|
+
// - 'hidden': no titlebar, no native window controls (for fully custom chrome)
|
|
37
|
+
// - 'hiddenInset': transparent titlebar with inset native controls
|
|
38
|
+
titleBarStyle: "hidden" | "hiddenInset" | "default";
|
|
39
|
+
// transparent: when true, window background is transparent (see-through)
|
|
40
|
+
transparent: boolean;
|
|
41
|
+
// passthrough: when true, mouse events pass through transparent regions
|
|
42
|
+
passthrough: boolean;
|
|
43
|
+
hidden?: boolean;
|
|
44
|
+
navigationRules: string | null;
|
|
45
|
+
// Sandbox mode: when true, disables RPC and only allows event emission
|
|
46
|
+
// Use for untrusted content (remote URLs) to prevent malicious sites from
|
|
47
|
+
// accessing internal APIs, creating OOPIFs, or communicating with Bun
|
|
48
|
+
sandbox: boolean;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const defaultOptions: WindowOptionsType = {
|
|
52
|
+
title: "SparkBun",
|
|
53
|
+
frame: {
|
|
54
|
+
x: 0,
|
|
55
|
+
y: 0,
|
|
56
|
+
width: 800,
|
|
57
|
+
height: 600,
|
|
58
|
+
},
|
|
59
|
+
url: "https://sparkbun.dev",
|
|
60
|
+
html: null,
|
|
61
|
+
preload: null,
|
|
62
|
+
viewsRoot: null,
|
|
63
|
+
renderer: buildConfig.defaultRenderer,
|
|
64
|
+
titleBarStyle: "default",
|
|
65
|
+
transparent: false,
|
|
66
|
+
passthrough: false,
|
|
67
|
+
hidden: false,
|
|
68
|
+
navigationRules: null,
|
|
69
|
+
sandbox: false,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const BrowserWindowMap: {
|
|
73
|
+
[id: number]: BrowserWindow<RPCWithTransport>;
|
|
74
|
+
} = {};
|
|
75
|
+
|
|
76
|
+
// Clean up JS wrapper state when a window closes. Native child cleanup is core-owned.
|
|
77
|
+
sparkBunEventEmitter.on("close", (event: { data: { id: number } }) => {
|
|
78
|
+
const windowId = event.data.id;
|
|
79
|
+
delete BrowserWindowMap[windowId];
|
|
80
|
+
|
|
81
|
+
// Clean up all webviews associated with this window
|
|
82
|
+
for (const view of BrowserView.getAll()) {
|
|
83
|
+
if (view.windowId === windowId) {
|
|
84
|
+
view.remove();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Clean up all WGPU views associated with this window
|
|
89
|
+
const wgpuViews = WGPUView.getAll().filter(v => v.windowId === windowId);
|
|
90
|
+
for (const view of wgpuViews) {
|
|
91
|
+
try {
|
|
92
|
+
view.remove();
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error(`Error cleaning up WGPU view ${view.id}:`, e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
|
|
101
|
+
id = 0;
|
|
102
|
+
title: string = "SparkBun";
|
|
103
|
+
state: "creating" | "created" = "creating";
|
|
104
|
+
url: string | null = null;
|
|
105
|
+
html: string | null = null;
|
|
106
|
+
preload: string | null = null;
|
|
107
|
+
viewsRoot: string | null = null;
|
|
108
|
+
renderer: "native" | "cef" = "native";
|
|
109
|
+
transparent: boolean = false;
|
|
110
|
+
passthrough: boolean = false;
|
|
111
|
+
hidden: boolean = false;
|
|
112
|
+
trafficLightOffset: { x: number; y: number } = { x: 0, y: 0 };
|
|
113
|
+
navigationRules: string | null = null;
|
|
114
|
+
// Sandbox mode disables RPC and only allows event emission (for untrusted content)
|
|
115
|
+
sandbox: boolean = false;
|
|
116
|
+
frame: {
|
|
117
|
+
x: number;
|
|
118
|
+
y: number;
|
|
119
|
+
width: number;
|
|
120
|
+
height: number;
|
|
121
|
+
} = {
|
|
122
|
+
x: 0,
|
|
123
|
+
y: 0,
|
|
124
|
+
width: 800,
|
|
125
|
+
height: 600,
|
|
126
|
+
};
|
|
127
|
+
webviewId!: number;
|
|
128
|
+
|
|
129
|
+
get ptr(): Pointer | null {
|
|
130
|
+
return ffi.request.getWindowPointer({ winId: this.id }) as Pointer | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
constructor(options: Partial<WindowOptionsType<T>> = defaultOptions) {
|
|
134
|
+
this.title = options.title || "New Window";
|
|
135
|
+
this.frame = options.frame
|
|
136
|
+
? { ...defaultOptions.frame, ...options.frame }
|
|
137
|
+
: { ...defaultOptions.frame };
|
|
138
|
+
this.url = options.url || null;
|
|
139
|
+
this.html = options.html || null;
|
|
140
|
+
this.preload = options.preload || null;
|
|
141
|
+
this.viewsRoot = options.viewsRoot || null;
|
|
142
|
+
this.renderer = options.renderer || defaultOptions.renderer;
|
|
143
|
+
this.transparent = options.transparent ?? false;
|
|
144
|
+
this.passthrough = options.passthrough ?? false;
|
|
145
|
+
this.hidden = options.hidden ?? false;
|
|
146
|
+
this.trafficLightOffset = {
|
|
147
|
+
x: options.trafficLightOffset?.x ?? 0,
|
|
148
|
+
y: options.trafficLightOffset?.y ?? 0,
|
|
149
|
+
};
|
|
150
|
+
this.navigationRules = options.navigationRules || null;
|
|
151
|
+
this.sandbox = options.sandbox ?? false;
|
|
152
|
+
|
|
153
|
+
this.init(options);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
init({
|
|
157
|
+
rpc,
|
|
158
|
+
styleMask,
|
|
159
|
+
titleBarStyle,
|
|
160
|
+
transparent,
|
|
161
|
+
hidden,
|
|
162
|
+
activate,
|
|
163
|
+
}: Partial<WindowOptionsType<T>>) {
|
|
164
|
+
const windowId = ffi.request.createWindow({
|
|
165
|
+
title: this.title,
|
|
166
|
+
url: this.url || "",
|
|
167
|
+
frame: {
|
|
168
|
+
width: this.frame.width,
|
|
169
|
+
height: this.frame.height,
|
|
170
|
+
x: this.frame.x,
|
|
171
|
+
y: this.frame.y,
|
|
172
|
+
},
|
|
173
|
+
styleMask: {
|
|
174
|
+
Borderless: false,
|
|
175
|
+
Titled: true,
|
|
176
|
+
Closable: true,
|
|
177
|
+
Miniaturizable: true,
|
|
178
|
+
Resizable: true,
|
|
179
|
+
UnifiedTitleAndToolbar: false,
|
|
180
|
+
FullScreen: false,
|
|
181
|
+
FullSizeContentView: false,
|
|
182
|
+
UtilityWindow: false,
|
|
183
|
+
DocModalWindow: false,
|
|
184
|
+
NonactivatingPanel: false,
|
|
185
|
+
HUDWindow: false,
|
|
186
|
+
...(styleMask || {}),
|
|
187
|
+
// hiddenInset: transparent titlebar with inset native controls
|
|
188
|
+
...(titleBarStyle === "hiddenInset"
|
|
189
|
+
? {
|
|
190
|
+
Titled: true,
|
|
191
|
+
FullSizeContentView: true,
|
|
192
|
+
}
|
|
193
|
+
: {}),
|
|
194
|
+
// hidden: no titlebar, no native controls (for fully custom chrome)
|
|
195
|
+
...(titleBarStyle === "hidden"
|
|
196
|
+
? {
|
|
197
|
+
Titled: false,
|
|
198
|
+
FullSizeContentView: true,
|
|
199
|
+
}
|
|
200
|
+
: {}),
|
|
201
|
+
},
|
|
202
|
+
titleBarStyle: titleBarStyle || "default",
|
|
203
|
+
transparent: transparent ?? false,
|
|
204
|
+
hidden: hidden ?? false,
|
|
205
|
+
activate: activate ?? true,
|
|
206
|
+
trafficLightOffset: this.trafficLightOffset,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (!windowId) {
|
|
210
|
+
throw "Failed to create window";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.id = windowId as number;
|
|
214
|
+
|
|
215
|
+
BrowserWindowMap[this.id] = this;
|
|
216
|
+
|
|
217
|
+
// own webview instance, or instances for attaching to the window.
|
|
218
|
+
const webview = new BrowserView({
|
|
219
|
+
// TODO: decide whether we want to keep sending url/html
|
|
220
|
+
// here, if we're manually calling loadURL/loadHTML below
|
|
221
|
+
// then we can remove it from the api here
|
|
222
|
+
url: this.url,
|
|
223
|
+
html: this.html,
|
|
224
|
+
preload: this.preload,
|
|
225
|
+
viewsRoot: this.viewsRoot,
|
|
226
|
+
// frame: this.frame,
|
|
227
|
+
renderer: this.renderer,
|
|
228
|
+
frame: {
|
|
229
|
+
x: 0,
|
|
230
|
+
y: 0,
|
|
231
|
+
width: this.frame.width,
|
|
232
|
+
height: this.frame.height,
|
|
233
|
+
},
|
|
234
|
+
rpc,
|
|
235
|
+
// todo: we need to send the window here and attach it in one go
|
|
236
|
+
// then the view creation code in objc can toggle between offscreen
|
|
237
|
+
// or on screen views depending on if windowId is null
|
|
238
|
+
// does this mean browserView needs to track the windowId or handle it ephemerally?
|
|
239
|
+
windowId: this.id,
|
|
240
|
+
navigationRules: this.navigationRules,
|
|
241
|
+
sandbox: this.sandbox,
|
|
242
|
+
startPassthrough: this.passthrough,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
this.webviewId = webview.id;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
get webview() {
|
|
249
|
+
// link directly to the browserview object instead of a getter
|
|
250
|
+
return BrowserView.getById(this.webviewId) as BrowserView<T>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
static getById(id: number) {
|
|
254
|
+
return BrowserWindowMap[id];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
setTitle(title: string) {
|
|
258
|
+
this.title = title;
|
|
259
|
+
return ffi.request.setTitle({ winId: this.id, title });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
close() {
|
|
263
|
+
return ffi.request.closeWindow({ winId: this.id });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
activate() {
|
|
267
|
+
return ffi.request.activateWindow({ winId: this.id });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
focus() {
|
|
271
|
+
console.log(
|
|
272
|
+
"[sparkbun] BrowserWindow.focus() is deprecated. Use window.activate() instead.",
|
|
273
|
+
);
|
|
274
|
+
return this.activate();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
show() {
|
|
278
|
+
return ffi.request.showWindow({ winId: this.id, activate: true });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
showInactive() {
|
|
282
|
+
return ffi.request.showWindow({ winId: this.id, activate: false });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
hide() {
|
|
286
|
+
return ffi.request.hideWindow({ winId: this.id });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
minimize() {
|
|
290
|
+
return ffi.request.minimizeWindow({ winId: this.id });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
unminimize() {
|
|
294
|
+
return ffi.request.restoreWindow({ winId: this.id });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
isMinimized(): boolean {
|
|
298
|
+
return ffi.request.isWindowMinimized({ winId: this.id });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
maximize() {
|
|
302
|
+
return ffi.request.maximizeWindow({ winId: this.id });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
unmaximize() {
|
|
306
|
+
return ffi.request.unmaximizeWindow({ winId: this.id });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
isMaximized(): boolean {
|
|
310
|
+
return ffi.request.isWindowMaximized({ winId: this.id });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
setFullScreen(fullScreen: boolean) {
|
|
314
|
+
return ffi.request.setWindowFullScreen({ winId: this.id, fullScreen });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
isFullScreen(): boolean {
|
|
318
|
+
return ffi.request.isWindowFullScreen({ winId: this.id });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
setAlwaysOnTop(alwaysOnTop: boolean) {
|
|
322
|
+
return ffi.request.setWindowAlwaysOnTop({ winId: this.id, alwaysOnTop });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
isAlwaysOnTop(): boolean {
|
|
326
|
+
return ffi.request.isWindowAlwaysOnTop({ winId: this.id });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
setVisibleOnAllWorkspaces(visibleOnAllWorkspaces: boolean) {
|
|
330
|
+
return ffi.request.setWindowVisibleOnAllWorkspaces({ winId: this.id, visibleOnAllWorkspaces });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
isVisibleOnAllWorkspaces(): boolean {
|
|
334
|
+
return ffi.request.isWindowVisibleOnAllWorkspaces({ winId: this.id });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
setPosition(x: number, y: number) {
|
|
338
|
+
this.frame.x = x;
|
|
339
|
+
this.frame.y = y;
|
|
340
|
+
return ffi.request.setWindowPosition({ winId: this.id, x, y });
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
setWindowButtonPosition(x: number, y: number) {
|
|
344
|
+
return ffi.request.setWindowButtonPosition({ winId: this.id, x, y });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
setSize(width: number, height: number) {
|
|
348
|
+
this.frame.width = width;
|
|
349
|
+
this.frame.height = height;
|
|
350
|
+
return ffi.request.setWindowSize({ winId: this.id, width, height });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
setFrame(x: number, y: number, width: number, height: number) {
|
|
354
|
+
this.frame = { x, y, width, height };
|
|
355
|
+
return ffi.request.setWindowFrame({ winId: this.id, x, y, width, height });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
getFrame(): { x: number; y: number; width: number; height: number } {
|
|
359
|
+
const frame = ffi.request.getWindowFrame({ winId: this.id });
|
|
360
|
+
// Update internal state
|
|
361
|
+
this.frame = frame;
|
|
362
|
+
return frame;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
getPosition(): { x: number; y: number } {
|
|
366
|
+
const frame = this.getFrame();
|
|
367
|
+
return { x: frame.x, y: frame.y };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
getSize(): { width: number; height: number } {
|
|
371
|
+
const frame = this.getFrame();
|
|
372
|
+
return { width: frame.width, height: frame.height };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Set the page zoom level for the window's webview (WebKit only).
|
|
377
|
+
* @param zoomLevel - The zoom level (1.0 = 100%, 1.5 = 150%, etc.)
|
|
378
|
+
*/
|
|
379
|
+
setPageZoom(zoomLevel: number) {
|
|
380
|
+
this.webview?.setPageZoom(zoomLevel);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get the current page zoom level for the window's webview.
|
|
385
|
+
* @returns The current zoom level (1.0 = 100%)
|
|
386
|
+
*/
|
|
387
|
+
getPageZoom(): number {
|
|
388
|
+
return this.webview?.getPageZoom() ?? 1.0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// name should only allow browserWindow events
|
|
392
|
+
on(name: string, handler: (event: unknown) => void) {
|
|
393
|
+
const specificName = `${name}-${this.id}`;
|
|
394
|
+
sparkBunEventEmitter.on(specificName, handler);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
|
|
3
|
+
export type BuildConfigType = {
|
|
4
|
+
defaultRenderer: "native" | "cef";
|
|
5
|
+
availableRenderers: ("native" | "cef")[];
|
|
6
|
+
cefVersion?: string;
|
|
7
|
+
bunVersion?: string;
|
|
8
|
+
runtime?: {
|
|
9
|
+
exitOnLastWindowClosed?: boolean;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let buildConfig: BuildConfigType | null = null;
|
|
15
|
+
|
|
16
|
+
function fallbackBuildConfig(): BuildConfigType {
|
|
17
|
+
return {
|
|
18
|
+
defaultRenderer: "native",
|
|
19
|
+
availableRenderers: ["native"],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const BuildConfig = {
|
|
24
|
+
/**
|
|
25
|
+
* Get the build configuration. Loads from build.json on first call, then returns cached value.
|
|
26
|
+
*/
|
|
27
|
+
get: async (): Promise<BuildConfigType> => {
|
|
28
|
+
if (buildConfig) {
|
|
29
|
+
return buildConfig;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const resourcesDir = "Resources";
|
|
34
|
+
buildConfig = await Bun.file(`../${resourcesDir}/build.json`).json();
|
|
35
|
+
return buildConfig!;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Fallback for dev mode or missing file
|
|
38
|
+
buildConfig = fallbackBuildConfig();
|
|
39
|
+
return buildConfig;
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the build configuration synchronously.
|
|
45
|
+
* Useful for modules that cannot use top-level await.
|
|
46
|
+
*/
|
|
47
|
+
getSync: (): BuildConfigType => {
|
|
48
|
+
if (buildConfig) {
|
|
49
|
+
return buildConfig;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const resourcesDir = "Resources";
|
|
54
|
+
buildConfig = JSON.parse(
|
|
55
|
+
readFileSync(`../${resourcesDir}/build.json`, "utf8"),
|
|
56
|
+
) as BuildConfigType;
|
|
57
|
+
return buildConfig;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
buildConfig = fallbackBuildConfig();
|
|
60
|
+
return buildConfig;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the cached build configuration synchronously.
|
|
66
|
+
* Returns null if config hasn't been loaded yet.
|
|
67
|
+
*/
|
|
68
|
+
getCached: (): BuildConfigType | null => buildConfig,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export { BuildConfig };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Context menus intentionally share ApplicationMenuItemConfig for now:
|
|
2
|
+
// - macOS supports role-based context menu items.
|
|
3
|
+
// - Windows has partial role behavior.
|
|
4
|
+
// - Linux showContextMenu is currently unsupported.
|
|
5
|
+
// Keep role typed here to avoid removing working macOS capability.
|
|
6
|
+
import { ffi, type ApplicationMenuItemConfig } from "../proc/native";
|
|
7
|
+
import sparkBunEventEmitter from "../events/eventEmitter";
|
|
8
|
+
import { roleLabelMap } from "./menuRoles";
|
|
9
|
+
|
|
10
|
+
type NonDividerMenuItem = {
|
|
11
|
+
type?: "normal";
|
|
12
|
+
label?: string;
|
|
13
|
+
tooltip?: string;
|
|
14
|
+
action?: string;
|
|
15
|
+
role?: string;
|
|
16
|
+
data?: unknown;
|
|
17
|
+
submenu?: Array<ApplicationMenuItemConfig>;
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
checked?: boolean;
|
|
20
|
+
hidden?: boolean;
|
|
21
|
+
accelerator?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const showContextMenu = (menu: Array<ApplicationMenuItemConfig>) => {
|
|
25
|
+
const menuWithDefaults = menuConfigWithDefaults(menu);
|
|
26
|
+
ffi.request.showContextMenu({
|
|
27
|
+
menuConfig: JSON.stringify(menuWithDefaults),
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const on = (
|
|
32
|
+
name: "context-menu-clicked",
|
|
33
|
+
handler: (event: unknown) => void,
|
|
34
|
+
) => {
|
|
35
|
+
const specificName = `${name}`;
|
|
36
|
+
sparkBunEventEmitter.on(specificName, handler);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const menuConfigWithDefaults = (
|
|
40
|
+
menu: Array<ApplicationMenuItemConfig>,
|
|
41
|
+
): Array<ApplicationMenuItemConfig> => {
|
|
42
|
+
return menu.map((item) => {
|
|
43
|
+
if (item.type === "divider" || item.type === "separator") {
|
|
44
|
+
return { type: "divider" } as const;
|
|
45
|
+
} else {
|
|
46
|
+
const menuItem = item as NonDividerMenuItem;
|
|
47
|
+
// Use shared serialization method
|
|
48
|
+
const actionWithDataId = ffi.internal.serializeMenuAction(
|
|
49
|
+
menuItem.action || "",
|
|
50
|
+
menuItem.data,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
label:
|
|
55
|
+
menuItem.label ||
|
|
56
|
+
roleLabelMap[menuItem.role as keyof typeof roleLabelMap] ||
|
|
57
|
+
"",
|
|
58
|
+
type: menuItem.type || "normal",
|
|
59
|
+
// application menus can either have an action or a role. not both.
|
|
60
|
+
...(menuItem.role
|
|
61
|
+
? { role: menuItem.role }
|
|
62
|
+
: { action: actionWithDataId }),
|
|
63
|
+
// default enabled to true unless explicitly set to false
|
|
64
|
+
enabled: menuItem.enabled === false ? false : true,
|
|
65
|
+
checked: Boolean(menuItem.checked),
|
|
66
|
+
hidden: Boolean(menuItem.hidden),
|
|
67
|
+
tooltip: menuItem.tooltip || undefined,
|
|
68
|
+
...(menuItem.accelerator ? { accelerator: menuItem.accelerator } : {}),
|
|
69
|
+
...(menuItem.submenu
|
|
70
|
+
? { submenu: menuConfigWithDefaults(menuItem.submenu) }
|
|
71
|
+
: {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
};
|