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,3253 @@
|
|
|
1
|
+
import { join, dirname } from "path";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import sparkBunEventEmitter from "../events/eventEmitter";
|
|
4
|
+
import SparkBunEvent from "../events/event";
|
|
5
|
+
import { BrowserView } from "../core/BrowserView";
|
|
6
|
+
import { WGPUView } from "../core/WGPUView";
|
|
7
|
+
import {
|
|
8
|
+
preloadScript,
|
|
9
|
+
preloadScriptSandboxed,
|
|
10
|
+
} from "../preload/.generated/compiled";
|
|
11
|
+
|
|
12
|
+
// Menu data reference system to avoid serialization overhead
|
|
13
|
+
const menuDataRegistry = new Map<string, any>();
|
|
14
|
+
let menuDataCounter = 0;
|
|
15
|
+
function storeMenuData(data: any): string {
|
|
16
|
+
const id = `menuData_${++menuDataCounter}`;
|
|
17
|
+
menuDataRegistry.set(id, data);
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getMenuData(id: string): any {
|
|
22
|
+
return menuDataRegistry.get(id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function clearMenuData(id: string): void {
|
|
26
|
+
menuDataRegistry.delete(id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Shared methods for EB delimiter serialization/deserialization
|
|
30
|
+
const SPARKBUN_DELIMITER = "|EB|";
|
|
31
|
+
|
|
32
|
+
function serializeMenuAction(action: string, data: any): string {
|
|
33
|
+
const dataId = storeMenuData(data);
|
|
34
|
+
return `${SPARKBUN_DELIMITER}${dataId}|${action}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function deserializeMenuAction(encodedAction: string): {
|
|
38
|
+
action: string;
|
|
39
|
+
data: any;
|
|
40
|
+
} {
|
|
41
|
+
let actualAction = encodedAction;
|
|
42
|
+
let data = undefined;
|
|
43
|
+
|
|
44
|
+
if (encodedAction.startsWith(SPARKBUN_DELIMITER)) {
|
|
45
|
+
const parts = encodedAction.split("|");
|
|
46
|
+
if (parts.length >= 4) {
|
|
47
|
+
// ['', 'EB', 'dataId', 'actualAction', ...]
|
|
48
|
+
const dataId = parts[2]!;
|
|
49
|
+
actualAction = parts.slice(3).join("|"); // Rejoin in case action contains |
|
|
50
|
+
data = getMenuData(dataId);
|
|
51
|
+
|
|
52
|
+
// Clean up data from registry after use
|
|
53
|
+
clearMenuData(dataId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { action: actualAction, data };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// todo: set up FFI, this is already in the webworker.
|
|
61
|
+
|
|
62
|
+
import {
|
|
63
|
+
dlopen,
|
|
64
|
+
suffix,
|
|
65
|
+
JSCallback,
|
|
66
|
+
CString,
|
|
67
|
+
ptr,
|
|
68
|
+
FFIType,
|
|
69
|
+
toArrayBuffer,
|
|
70
|
+
type Pointer,
|
|
71
|
+
} from "bun:ffi";
|
|
72
|
+
|
|
73
|
+
function getWindowPtr(winId: number) {
|
|
74
|
+
return core?.symbols.getWindowPointer(winId) || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getCoreLastError(): string | null {
|
|
78
|
+
const error = core?.symbols.electrobun_core_last_error();
|
|
79
|
+
if (!error) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const message = error.toString();
|
|
84
|
+
return message.length > 0 ? message : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let webviewRuntimeConfigured = false;
|
|
88
|
+
|
|
89
|
+
function ensureWebviewRuntimeConfigured() {
|
|
90
|
+
if (webviewRuntimeConfigured) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const configured = core?.symbols.configureWebviewRuntime(
|
|
95
|
+
0,
|
|
96
|
+
toCString(preloadScript),
|
|
97
|
+
toCString(preloadScriptSandboxed),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (!configured) {
|
|
101
|
+
throw getCoreLastError() || "Failed to configure webview runtime";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
webviewRuntimeConfigured = true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const core = (() => {
|
|
108
|
+
try {
|
|
109
|
+
const binDir = dirname(process.argv0);
|
|
110
|
+
const corePath = join(
|
|
111
|
+
binDir,
|
|
112
|
+
process.platform === "win32"
|
|
113
|
+
? "ElectrobunCore.dll"
|
|
114
|
+
: `libElectrobunCore.${suffix}`,
|
|
115
|
+
);
|
|
116
|
+
return dlopen(corePath, {
|
|
117
|
+
electrobun_core_last_error: {
|
|
118
|
+
args: [],
|
|
119
|
+
returns: FFIType.cstring,
|
|
120
|
+
},
|
|
121
|
+
getWindowStyle: {
|
|
122
|
+
args: [
|
|
123
|
+
FFIType.bool,
|
|
124
|
+
FFIType.bool,
|
|
125
|
+
FFIType.bool,
|
|
126
|
+
FFIType.bool,
|
|
127
|
+
FFIType.bool,
|
|
128
|
+
FFIType.bool,
|
|
129
|
+
FFIType.bool,
|
|
130
|
+
FFIType.bool,
|
|
131
|
+
FFIType.bool,
|
|
132
|
+
FFIType.bool,
|
|
133
|
+
FFIType.bool,
|
|
134
|
+
FFIType.bool,
|
|
135
|
+
],
|
|
136
|
+
returns: FFIType.u32,
|
|
137
|
+
},
|
|
138
|
+
createWindow: {
|
|
139
|
+
args: [
|
|
140
|
+
FFIType.f64,
|
|
141
|
+
FFIType.f64,
|
|
142
|
+
FFIType.f64,
|
|
143
|
+
FFIType.f64,
|
|
144
|
+
FFIType.u32,
|
|
145
|
+
FFIType.cstring,
|
|
146
|
+
FFIType.bool,
|
|
147
|
+
FFIType.cstring,
|
|
148
|
+
FFIType.bool,
|
|
149
|
+
FFIType.bool,
|
|
150
|
+
FFIType.f64,
|
|
151
|
+
FFIType.f64,
|
|
152
|
+
FFIType.function,
|
|
153
|
+
FFIType.function,
|
|
154
|
+
FFIType.function,
|
|
155
|
+
FFIType.function,
|
|
156
|
+
FFIType.function,
|
|
157
|
+
FFIType.function,
|
|
158
|
+
],
|
|
159
|
+
returns: FFIType.u32,
|
|
160
|
+
},
|
|
161
|
+
getWindowPointer: {
|
|
162
|
+
args: [FFIType.u32],
|
|
163
|
+
returns: FFIType.ptr,
|
|
164
|
+
},
|
|
165
|
+
setWindowTitle: {
|
|
166
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
167
|
+
returns: FFIType.void,
|
|
168
|
+
},
|
|
169
|
+
minimizeWindow: {
|
|
170
|
+
args: [FFIType.u32],
|
|
171
|
+
returns: FFIType.void,
|
|
172
|
+
},
|
|
173
|
+
restoreWindow: {
|
|
174
|
+
args: [FFIType.u32],
|
|
175
|
+
returns: FFIType.void,
|
|
176
|
+
},
|
|
177
|
+
isWindowMinimized: {
|
|
178
|
+
args: [FFIType.u32],
|
|
179
|
+
returns: FFIType.bool,
|
|
180
|
+
},
|
|
181
|
+
maximizeWindow: {
|
|
182
|
+
args: [FFIType.u32],
|
|
183
|
+
returns: FFIType.void,
|
|
184
|
+
},
|
|
185
|
+
unmaximizeWindow: {
|
|
186
|
+
args: [FFIType.u32],
|
|
187
|
+
returns: FFIType.void,
|
|
188
|
+
},
|
|
189
|
+
isWindowMaximized: {
|
|
190
|
+
args: [FFIType.u32],
|
|
191
|
+
returns: FFIType.bool,
|
|
192
|
+
},
|
|
193
|
+
showWindow: {
|
|
194
|
+
args: [FFIType.u32, FFIType.bool],
|
|
195
|
+
returns: FFIType.void,
|
|
196
|
+
},
|
|
197
|
+
activateWindow: {
|
|
198
|
+
args: [FFIType.u32],
|
|
199
|
+
returns: FFIType.void,
|
|
200
|
+
},
|
|
201
|
+
hideWindow: {
|
|
202
|
+
args: [FFIType.u32],
|
|
203
|
+
returns: FFIType.void,
|
|
204
|
+
},
|
|
205
|
+
closeWindow: {
|
|
206
|
+
args: [FFIType.u32],
|
|
207
|
+
returns: FFIType.void,
|
|
208
|
+
},
|
|
209
|
+
setWindowFullScreen: {
|
|
210
|
+
args: [FFIType.u32, FFIType.bool],
|
|
211
|
+
returns: FFIType.void,
|
|
212
|
+
},
|
|
213
|
+
isWindowFullScreen: {
|
|
214
|
+
args: [FFIType.u32],
|
|
215
|
+
returns: FFIType.bool,
|
|
216
|
+
},
|
|
217
|
+
setWindowAlwaysOnTop: {
|
|
218
|
+
args: [FFIType.u32, FFIType.bool],
|
|
219
|
+
returns: FFIType.void,
|
|
220
|
+
},
|
|
221
|
+
isWindowAlwaysOnTop: {
|
|
222
|
+
args: [FFIType.u32],
|
|
223
|
+
returns: FFIType.bool,
|
|
224
|
+
},
|
|
225
|
+
setWindowVisibleOnAllWorkspaces: {
|
|
226
|
+
args: [FFIType.u32, FFIType.bool],
|
|
227
|
+
returns: FFIType.void,
|
|
228
|
+
},
|
|
229
|
+
isWindowVisibleOnAllWorkspaces: {
|
|
230
|
+
args: [FFIType.u32],
|
|
231
|
+
returns: FFIType.bool,
|
|
232
|
+
},
|
|
233
|
+
setWindowPosition: {
|
|
234
|
+
args: [FFIType.u32, FFIType.f64, FFIType.f64],
|
|
235
|
+
returns: FFIType.void,
|
|
236
|
+
},
|
|
237
|
+
setWindowButtonPosition: {
|
|
238
|
+
args: [FFIType.u32, FFIType.f64, FFIType.f64],
|
|
239
|
+
returns: FFIType.void,
|
|
240
|
+
},
|
|
241
|
+
setWindowSize: {
|
|
242
|
+
args: [FFIType.u32, FFIType.f64, FFIType.f64],
|
|
243
|
+
returns: FFIType.void,
|
|
244
|
+
},
|
|
245
|
+
setWindowFrame: {
|
|
246
|
+
args: [FFIType.u32, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64],
|
|
247
|
+
returns: FFIType.void,
|
|
248
|
+
},
|
|
249
|
+
getWindowFrame: {
|
|
250
|
+
args: [FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
251
|
+
returns: FFIType.void,
|
|
252
|
+
},
|
|
253
|
+
configureWebviewRuntime: {
|
|
254
|
+
args: [
|
|
255
|
+
FFIType.u32,
|
|
256
|
+
FFIType.cstring,
|
|
257
|
+
FFIType.cstring,
|
|
258
|
+
],
|
|
259
|
+
returns: FFIType.bool,
|
|
260
|
+
},
|
|
261
|
+
createWebview: {
|
|
262
|
+
args: [
|
|
263
|
+
FFIType.u32,
|
|
264
|
+
FFIType.u32,
|
|
265
|
+
FFIType.cstring,
|
|
266
|
+
FFIType.cstring,
|
|
267
|
+
FFIType.f64,
|
|
268
|
+
FFIType.f64,
|
|
269
|
+
FFIType.f64,
|
|
270
|
+
FFIType.f64,
|
|
271
|
+
FFIType.bool,
|
|
272
|
+
FFIType.cstring,
|
|
273
|
+
FFIType.function,
|
|
274
|
+
FFIType.function,
|
|
275
|
+
FFIType.function,
|
|
276
|
+
FFIType.function,
|
|
277
|
+
FFIType.function,
|
|
278
|
+
FFIType.cstring,
|
|
279
|
+
FFIType.cstring,
|
|
280
|
+
FFIType.cstring,
|
|
281
|
+
FFIType.bool,
|
|
282
|
+
FFIType.bool,
|
|
283
|
+
FFIType.bool,
|
|
284
|
+
],
|
|
285
|
+
returns: FFIType.u32,
|
|
286
|
+
},
|
|
287
|
+
getWebviewPointer: {
|
|
288
|
+
args: [FFIType.u32],
|
|
289
|
+
returns: FFIType.ptr,
|
|
290
|
+
},
|
|
291
|
+
resizeWebview: {
|
|
292
|
+
args: [
|
|
293
|
+
FFIType.u32,
|
|
294
|
+
FFIType.f64,
|
|
295
|
+
FFIType.f64,
|
|
296
|
+
FFIType.f64,
|
|
297
|
+
FFIType.f64,
|
|
298
|
+
FFIType.cstring,
|
|
299
|
+
],
|
|
300
|
+
returns: FFIType.void,
|
|
301
|
+
},
|
|
302
|
+
loadURLInWebView: {
|
|
303
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
304
|
+
returns: FFIType.void,
|
|
305
|
+
},
|
|
306
|
+
loadHTMLInWebView: {
|
|
307
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
308
|
+
returns: FFIType.void,
|
|
309
|
+
},
|
|
310
|
+
updatePreloadScriptToWebView: {
|
|
311
|
+
args: [FFIType.u32, FFIType.cstring, FFIType.cstring, FFIType.bool],
|
|
312
|
+
returns: FFIType.void,
|
|
313
|
+
},
|
|
314
|
+
webviewCanGoBack: {
|
|
315
|
+
args: [FFIType.u32],
|
|
316
|
+
returns: FFIType.bool,
|
|
317
|
+
},
|
|
318
|
+
webviewCanGoForward: {
|
|
319
|
+
args: [FFIType.u32],
|
|
320
|
+
returns: FFIType.bool,
|
|
321
|
+
},
|
|
322
|
+
webviewGoBack: {
|
|
323
|
+
args: [FFIType.u32],
|
|
324
|
+
returns: FFIType.void,
|
|
325
|
+
},
|
|
326
|
+
webviewGoForward: {
|
|
327
|
+
args: [FFIType.u32],
|
|
328
|
+
returns: FFIType.void,
|
|
329
|
+
},
|
|
330
|
+
webviewReload: {
|
|
331
|
+
args: [FFIType.u32],
|
|
332
|
+
returns: FFIType.void,
|
|
333
|
+
},
|
|
334
|
+
webviewRemove: {
|
|
335
|
+
args: [FFIType.u32],
|
|
336
|
+
returns: FFIType.void,
|
|
337
|
+
},
|
|
338
|
+
setWebviewHTMLContent: {
|
|
339
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
340
|
+
returns: FFIType.void,
|
|
341
|
+
},
|
|
342
|
+
webviewSetTransparent: {
|
|
343
|
+
args: [FFIType.u32, FFIType.bool],
|
|
344
|
+
returns: FFIType.void,
|
|
345
|
+
},
|
|
346
|
+
webviewSetPassthrough: {
|
|
347
|
+
args: [FFIType.u32, FFIType.bool],
|
|
348
|
+
returns: FFIType.void,
|
|
349
|
+
},
|
|
350
|
+
webviewSetHidden: {
|
|
351
|
+
args: [FFIType.u32, FFIType.bool],
|
|
352
|
+
returns: FFIType.void,
|
|
353
|
+
},
|
|
354
|
+
setWebviewNavigationRules: {
|
|
355
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
356
|
+
returns: FFIType.void,
|
|
357
|
+
},
|
|
358
|
+
webviewFindInPage: {
|
|
359
|
+
args: [FFIType.u32, FFIType.cstring, FFIType.bool, FFIType.bool],
|
|
360
|
+
returns: FFIType.void,
|
|
361
|
+
},
|
|
362
|
+
webviewStopFind: {
|
|
363
|
+
args: [FFIType.u32],
|
|
364
|
+
returns: FFIType.void,
|
|
365
|
+
},
|
|
366
|
+
evaluateJavaScriptWithNoCompletion: {
|
|
367
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
368
|
+
returns: FFIType.void,
|
|
369
|
+
},
|
|
370
|
+
sendHostMessageToWebviewViaTransport: {
|
|
371
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
372
|
+
returns: FFIType.bool,
|
|
373
|
+
},
|
|
374
|
+
popNextQueuedHostMessage: {
|
|
375
|
+
args: [FFIType.ptr],
|
|
376
|
+
returns: FFIType.ptr,
|
|
377
|
+
},
|
|
378
|
+
getHostMessageWakeupReadFD: {
|
|
379
|
+
args: [],
|
|
380
|
+
returns: FFIType.int,
|
|
381
|
+
},
|
|
382
|
+
freeCoreString: {
|
|
383
|
+
args: [FFIType.ptr],
|
|
384
|
+
returns: FFIType.void,
|
|
385
|
+
},
|
|
386
|
+
clearWebviewHostTransport: {
|
|
387
|
+
args: [FFIType.u32],
|
|
388
|
+
returns: FFIType.void,
|
|
389
|
+
},
|
|
390
|
+
dispatchHostWebviewEvent: {
|
|
391
|
+
args: [FFIType.u32, FFIType.cstring, FFIType.cstring],
|
|
392
|
+
returns: FFIType.bool,
|
|
393
|
+
},
|
|
394
|
+
sendInternalMessageToWebview: {
|
|
395
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
396
|
+
returns: FFIType.bool,
|
|
397
|
+
},
|
|
398
|
+
webviewOpenDevTools: {
|
|
399
|
+
args: [FFIType.u32],
|
|
400
|
+
returns: FFIType.void,
|
|
401
|
+
},
|
|
402
|
+
webviewCloseDevTools: {
|
|
403
|
+
args: [FFIType.u32],
|
|
404
|
+
returns: FFIType.void,
|
|
405
|
+
},
|
|
406
|
+
webviewToggleDevTools: {
|
|
407
|
+
args: [FFIType.u32],
|
|
408
|
+
returns: FFIType.void,
|
|
409
|
+
},
|
|
410
|
+
webviewSetPageZoom: {
|
|
411
|
+
args: [FFIType.u32, FFIType.f64],
|
|
412
|
+
returns: FFIType.void,
|
|
413
|
+
},
|
|
414
|
+
webviewGetPageZoom: {
|
|
415
|
+
args: [FFIType.u32],
|
|
416
|
+
returns: FFIType.f64,
|
|
417
|
+
},
|
|
418
|
+
createWGPUView: {
|
|
419
|
+
args: [
|
|
420
|
+
FFIType.u32,
|
|
421
|
+
FFIType.f64,
|
|
422
|
+
FFIType.f64,
|
|
423
|
+
FFIType.f64,
|
|
424
|
+
FFIType.f64,
|
|
425
|
+
FFIType.bool,
|
|
426
|
+
FFIType.bool,
|
|
427
|
+
FFIType.bool,
|
|
428
|
+
],
|
|
429
|
+
returns: FFIType.u32,
|
|
430
|
+
},
|
|
431
|
+
getWGPUViewPointer: {
|
|
432
|
+
args: [FFIType.u32],
|
|
433
|
+
returns: FFIType.ptr,
|
|
434
|
+
},
|
|
435
|
+
setWGPUViewFrame: {
|
|
436
|
+
args: [FFIType.u32, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64],
|
|
437
|
+
returns: FFIType.void,
|
|
438
|
+
},
|
|
439
|
+
resizeWGPUView: {
|
|
440
|
+
args: [FFIType.u32, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.cstring],
|
|
441
|
+
returns: FFIType.void,
|
|
442
|
+
},
|
|
443
|
+
setWGPUViewTransparent: {
|
|
444
|
+
args: [FFIType.u32, FFIType.bool],
|
|
445
|
+
returns: FFIType.void,
|
|
446
|
+
},
|
|
447
|
+
setWGPUViewPassthrough: {
|
|
448
|
+
args: [FFIType.u32, FFIType.bool],
|
|
449
|
+
returns: FFIType.void,
|
|
450
|
+
},
|
|
451
|
+
setWGPUViewHidden: {
|
|
452
|
+
args: [FFIType.u32, FFIType.bool],
|
|
453
|
+
returns: FFIType.void,
|
|
454
|
+
},
|
|
455
|
+
removeWGPUView: {
|
|
456
|
+
args: [FFIType.u32],
|
|
457
|
+
returns: FFIType.void,
|
|
458
|
+
},
|
|
459
|
+
getWGPUViewNativeHandle: {
|
|
460
|
+
args: [FFIType.u32],
|
|
461
|
+
returns: FFIType.ptr,
|
|
462
|
+
},
|
|
463
|
+
runWGPUViewTest: {
|
|
464
|
+
args: [FFIType.u32],
|
|
465
|
+
returns: FFIType.void,
|
|
466
|
+
},
|
|
467
|
+
createTray: {
|
|
468
|
+
args: [
|
|
469
|
+
FFIType.cstring,
|
|
470
|
+
FFIType.cstring,
|
|
471
|
+
FFIType.bool,
|
|
472
|
+
FFIType.u32,
|
|
473
|
+
FFIType.u32,
|
|
474
|
+
FFIType.function,
|
|
475
|
+
],
|
|
476
|
+
returns: FFIType.u32,
|
|
477
|
+
},
|
|
478
|
+
showTray: {
|
|
479
|
+
args: [FFIType.u32],
|
|
480
|
+
returns: FFIType.bool,
|
|
481
|
+
},
|
|
482
|
+
hideTray: {
|
|
483
|
+
args: [FFIType.u32],
|
|
484
|
+
returns: FFIType.void,
|
|
485
|
+
},
|
|
486
|
+
setTrayTitle: {
|
|
487
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
488
|
+
returns: FFIType.void,
|
|
489
|
+
},
|
|
490
|
+
setTrayImage: {
|
|
491
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
492
|
+
returns: FFIType.void,
|
|
493
|
+
},
|
|
494
|
+
setTrayMenu: {
|
|
495
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
496
|
+
returns: FFIType.void,
|
|
497
|
+
},
|
|
498
|
+
removeTray: {
|
|
499
|
+
args: [FFIType.u32],
|
|
500
|
+
returns: FFIType.void,
|
|
501
|
+
},
|
|
502
|
+
getTrayBounds: {
|
|
503
|
+
args: [FFIType.u32],
|
|
504
|
+
returns: FFIType.cstring,
|
|
505
|
+
},
|
|
506
|
+
setApplicationMenu: {
|
|
507
|
+
args: [FFIType.cstring, FFIType.function],
|
|
508
|
+
returns: FFIType.void,
|
|
509
|
+
},
|
|
510
|
+
showContextMenu: {
|
|
511
|
+
args: [FFIType.cstring, FFIType.function],
|
|
512
|
+
returns: FFIType.void,
|
|
513
|
+
},
|
|
514
|
+
moveToTrash: {
|
|
515
|
+
args: [FFIType.cstring],
|
|
516
|
+
returns: FFIType.bool,
|
|
517
|
+
},
|
|
518
|
+
showItemInFolder: {
|
|
519
|
+
args: [FFIType.cstring],
|
|
520
|
+
returns: FFIType.void,
|
|
521
|
+
},
|
|
522
|
+
openExternal: {
|
|
523
|
+
args: [FFIType.cstring],
|
|
524
|
+
returns: FFIType.bool,
|
|
525
|
+
},
|
|
526
|
+
openPath: {
|
|
527
|
+
args: [FFIType.cstring],
|
|
528
|
+
returns: FFIType.bool,
|
|
529
|
+
},
|
|
530
|
+
showNotification: {
|
|
531
|
+
args: [
|
|
532
|
+
FFIType.cstring,
|
|
533
|
+
FFIType.cstring,
|
|
534
|
+
FFIType.cstring,
|
|
535
|
+
FFIType.bool,
|
|
536
|
+
],
|
|
537
|
+
returns: FFIType.void,
|
|
538
|
+
},
|
|
539
|
+
getAllDisplays: {
|
|
540
|
+
args: [],
|
|
541
|
+
returns: FFIType.cstring,
|
|
542
|
+
},
|
|
543
|
+
getPrimaryDisplay: {
|
|
544
|
+
args: [],
|
|
545
|
+
returns: FFIType.cstring,
|
|
546
|
+
},
|
|
547
|
+
getCursorScreenPoint: {
|
|
548
|
+
args: [],
|
|
549
|
+
returns: FFIType.cstring,
|
|
550
|
+
},
|
|
551
|
+
getMouseButtons: {
|
|
552
|
+
args: [],
|
|
553
|
+
returns: FFIType.u64,
|
|
554
|
+
},
|
|
555
|
+
openFileDialog: {
|
|
556
|
+
args: [
|
|
557
|
+
FFIType.cstring,
|
|
558
|
+
FFIType.cstring,
|
|
559
|
+
FFIType.int,
|
|
560
|
+
FFIType.int,
|
|
561
|
+
FFIType.int,
|
|
562
|
+
],
|
|
563
|
+
returns: FFIType.cstring,
|
|
564
|
+
},
|
|
565
|
+
showMessageBox: {
|
|
566
|
+
args: [
|
|
567
|
+
FFIType.cstring,
|
|
568
|
+
FFIType.cstring,
|
|
569
|
+
FFIType.cstring,
|
|
570
|
+
FFIType.cstring,
|
|
571
|
+
FFIType.cstring,
|
|
572
|
+
FFIType.int,
|
|
573
|
+
FFIType.int,
|
|
574
|
+
],
|
|
575
|
+
returns: FFIType.int,
|
|
576
|
+
},
|
|
577
|
+
clipboardReadText: {
|
|
578
|
+
args: [],
|
|
579
|
+
returns: FFIType.cstring,
|
|
580
|
+
},
|
|
581
|
+
clipboardWriteText: {
|
|
582
|
+
args: [FFIType.cstring],
|
|
583
|
+
returns: FFIType.void,
|
|
584
|
+
},
|
|
585
|
+
clipboardReadImage: {
|
|
586
|
+
args: [FFIType.ptr],
|
|
587
|
+
returns: FFIType.ptr,
|
|
588
|
+
},
|
|
589
|
+
clipboardWriteImage: {
|
|
590
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
591
|
+
returns: FFIType.void,
|
|
592
|
+
},
|
|
593
|
+
clipboardClear: {
|
|
594
|
+
args: [],
|
|
595
|
+
returns: FFIType.void,
|
|
596
|
+
},
|
|
597
|
+
clipboardAvailableFormats: {
|
|
598
|
+
args: [],
|
|
599
|
+
returns: FFIType.cstring,
|
|
600
|
+
},
|
|
601
|
+
setDockIconVisible: {
|
|
602
|
+
args: [FFIType.bool],
|
|
603
|
+
returns: FFIType.void,
|
|
604
|
+
},
|
|
605
|
+
isDockIconVisible: {
|
|
606
|
+
args: [],
|
|
607
|
+
returns: FFIType.bool,
|
|
608
|
+
},
|
|
609
|
+
setExitOnLastWindowClosed: {
|
|
610
|
+
args: [FFIType.bool],
|
|
611
|
+
returns: FFIType.void,
|
|
612
|
+
},
|
|
613
|
+
setQuitRequestedHandler: {
|
|
614
|
+
args: [FFIType.function],
|
|
615
|
+
returns: FFIType.void,
|
|
616
|
+
},
|
|
617
|
+
quitGracefully: {
|
|
618
|
+
args: [FFIType.i32, FFIType.i32],
|
|
619
|
+
returns: FFIType.void,
|
|
620
|
+
},
|
|
621
|
+
});
|
|
622
|
+
} catch {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
})();
|
|
626
|
+
|
|
627
|
+
export const native = (() => {
|
|
628
|
+
try {
|
|
629
|
+
const binDir = dirname(process.argv0);
|
|
630
|
+
const nativeWrapperPath = join(binDir, `libNativeWrapper.${suffix}`);
|
|
631
|
+
return dlopen(nativeWrapperPath, {
|
|
632
|
+
// webview
|
|
633
|
+
initWebview: {
|
|
634
|
+
args: [
|
|
635
|
+
FFIType.u32, // webviewId
|
|
636
|
+
FFIType.ptr, // windowPtr
|
|
637
|
+
FFIType.cstring, // renderer
|
|
638
|
+
FFIType.cstring, // url
|
|
639
|
+
FFIType.f64,
|
|
640
|
+
FFIType.f64, // x, y
|
|
641
|
+
FFIType.f64,
|
|
642
|
+
FFIType.f64, // width, height
|
|
643
|
+
FFIType.bool, // autoResize
|
|
644
|
+
FFIType.cstring, // partition
|
|
645
|
+
FFIType.function, // decideNavigation: *const fn (u32, [*:0]const u8) callconv(.C) bool,
|
|
646
|
+
FFIType.function, // webviewEventHandler: *const fn (u32, [*:0]const u8, [*:0]const u8) callconv(.C) void,
|
|
647
|
+
FFIType.function, // eventBridgeHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (events only, always active)
|
|
648
|
+
FFIType.function, // hostBridgePostmessageHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (user RPC, disabled in sandbox)
|
|
649
|
+
FFIType.function, // internalBridgeHandler: *const fn (u32, [*:0]const u8) callconv(.C) void (internal RPC, disabled in sandbox)
|
|
650
|
+
FFIType.cstring, // sparkbunPreloadScript
|
|
651
|
+
FFIType.cstring, // customPreloadScript
|
|
652
|
+
FFIType.cstring, // viewsRoot
|
|
653
|
+
FFIType.bool, // transparent
|
|
654
|
+
FFIType.bool, // sandbox - when true, hostBridge and internalBridge are not set up
|
|
655
|
+
],
|
|
656
|
+
returns: FFIType.ptr,
|
|
657
|
+
},
|
|
658
|
+
initWGPUView: {
|
|
659
|
+
args: [
|
|
660
|
+
FFIType.u32, // viewId
|
|
661
|
+
FFIType.ptr, // windowPtr
|
|
662
|
+
FFIType.f64,
|
|
663
|
+
FFIType.f64, // x, y
|
|
664
|
+
FFIType.f64,
|
|
665
|
+
FFIType.f64, // width, height
|
|
666
|
+
FFIType.bool, // autoResize
|
|
667
|
+
FFIType.bool, // startTransparent
|
|
668
|
+
FFIType.bool, // startPassthrough
|
|
669
|
+
],
|
|
670
|
+
returns: FFIType.ptr,
|
|
671
|
+
},
|
|
672
|
+
// Pre-set flags for the next initWebview call (workaround for FFI param count limits)
|
|
673
|
+
setNextWebviewFlags: {
|
|
674
|
+
args: [
|
|
675
|
+
FFIType.bool, // startTransparent
|
|
676
|
+
FFIType.bool, // startPassthrough
|
|
677
|
+
],
|
|
678
|
+
returns: FFIType.void,
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
// webviewtag
|
|
682
|
+
webviewCanGoBack: {
|
|
683
|
+
args: [FFIType.ptr],
|
|
684
|
+
returns: FFIType.bool,
|
|
685
|
+
},
|
|
686
|
+
|
|
687
|
+
webviewCanGoForward: {
|
|
688
|
+
args: [FFIType.ptr],
|
|
689
|
+
returns: FFIType.bool,
|
|
690
|
+
},
|
|
691
|
+
// Note: callAsyncJavaScript not implemented - CEF doesn't support this directly.
|
|
692
|
+
// Users can use RPC for JavaScript execution.
|
|
693
|
+
resizeWebview: {
|
|
694
|
+
args: [
|
|
695
|
+
FFIType.ptr, // webview handle
|
|
696
|
+
FFIType.f64, // x
|
|
697
|
+
FFIType.f64, // y
|
|
698
|
+
FFIType.f64, // width
|
|
699
|
+
FFIType.f64, // height
|
|
700
|
+
FFIType.cstring, // maskJson
|
|
701
|
+
],
|
|
702
|
+
returns: FFIType.void,
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
loadURLInWebView: {
|
|
706
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
707
|
+
returns: FFIType.void,
|
|
708
|
+
},
|
|
709
|
+
loadHTMLInWebView: {
|
|
710
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
711
|
+
returns: FFIType.void,
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
updatePreloadScriptToWebView: {
|
|
715
|
+
args: [
|
|
716
|
+
FFIType.ptr, // webview handle
|
|
717
|
+
FFIType.cstring, // script identifier
|
|
718
|
+
FFIType.cstring, // script
|
|
719
|
+
FFIType.bool, // allframes
|
|
720
|
+
],
|
|
721
|
+
returns: FFIType.void,
|
|
722
|
+
},
|
|
723
|
+
webviewGoBack: {
|
|
724
|
+
args: [FFIType.ptr],
|
|
725
|
+
returns: FFIType.void,
|
|
726
|
+
},
|
|
727
|
+
webviewGoForward: {
|
|
728
|
+
args: [FFIType.ptr],
|
|
729
|
+
returns: FFIType.void,
|
|
730
|
+
},
|
|
731
|
+
webviewReload: {
|
|
732
|
+
args: [FFIType.ptr],
|
|
733
|
+
returns: FFIType.void,
|
|
734
|
+
},
|
|
735
|
+
webviewRemove: {
|
|
736
|
+
args: [FFIType.ptr],
|
|
737
|
+
returns: FFIType.void,
|
|
738
|
+
},
|
|
739
|
+
setWebviewHTMLContent: {
|
|
740
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
741
|
+
returns: FFIType.void,
|
|
742
|
+
},
|
|
743
|
+
startWindowMove: {
|
|
744
|
+
args: [FFIType.ptr],
|
|
745
|
+
returns: FFIType.void,
|
|
746
|
+
},
|
|
747
|
+
stopWindowMove: {
|
|
748
|
+
args: [],
|
|
749
|
+
returns: FFIType.void,
|
|
750
|
+
},
|
|
751
|
+
webviewSetTransparent: {
|
|
752
|
+
args: [FFIType.ptr, FFIType.bool],
|
|
753
|
+
returns: FFIType.void,
|
|
754
|
+
},
|
|
755
|
+
webviewSetPassthrough: {
|
|
756
|
+
args: [FFIType.ptr, FFIType.bool],
|
|
757
|
+
returns: FFIType.void,
|
|
758
|
+
},
|
|
759
|
+
webviewSetHidden: {
|
|
760
|
+
args: [FFIType.ptr, FFIType.bool],
|
|
761
|
+
returns: FFIType.void,
|
|
762
|
+
},
|
|
763
|
+
setWebviewNavigationRules: {
|
|
764
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
765
|
+
returns: FFIType.void,
|
|
766
|
+
},
|
|
767
|
+
webviewFindInPage: {
|
|
768
|
+
args: [FFIType.ptr, FFIType.cstring, FFIType.bool, FFIType.bool],
|
|
769
|
+
returns: FFIType.void,
|
|
770
|
+
},
|
|
771
|
+
webviewStopFind: {
|
|
772
|
+
args: [FFIType.ptr],
|
|
773
|
+
returns: FFIType.void,
|
|
774
|
+
},
|
|
775
|
+
evaluateJavaScriptWithNoCompletion: {
|
|
776
|
+
args: [FFIType.ptr, FFIType.cstring],
|
|
777
|
+
returns: FFIType.void,
|
|
778
|
+
},
|
|
779
|
+
webviewOpenDevTools: {
|
|
780
|
+
args: [FFIType.ptr],
|
|
781
|
+
returns: FFIType.void,
|
|
782
|
+
},
|
|
783
|
+
webviewCloseDevTools: {
|
|
784
|
+
args: [FFIType.ptr],
|
|
785
|
+
returns: FFIType.void,
|
|
786
|
+
},
|
|
787
|
+
webviewToggleDevTools: {
|
|
788
|
+
args: [FFIType.ptr],
|
|
789
|
+
returns: FFIType.void,
|
|
790
|
+
},
|
|
791
|
+
webviewSetPageZoom: {
|
|
792
|
+
args: [FFIType.ptr, FFIType.f64],
|
|
793
|
+
returns: FFIType.void,
|
|
794
|
+
},
|
|
795
|
+
webviewGetPageZoom: {
|
|
796
|
+
args: [FFIType.ptr],
|
|
797
|
+
returns: FFIType.f64,
|
|
798
|
+
},
|
|
799
|
+
wgpuViewSetFrame: {
|
|
800
|
+
args: [
|
|
801
|
+
FFIType.ptr,
|
|
802
|
+
FFIType.f64,
|
|
803
|
+
FFIType.f64,
|
|
804
|
+
FFIType.f64,
|
|
805
|
+
FFIType.f64,
|
|
806
|
+
],
|
|
807
|
+
returns: FFIType.void,
|
|
808
|
+
},
|
|
809
|
+
wgpuViewSetTransparent: {
|
|
810
|
+
args: [FFIType.ptr, FFIType.bool],
|
|
811
|
+
returns: FFIType.void,
|
|
812
|
+
},
|
|
813
|
+
wgpuViewSetPassthrough: {
|
|
814
|
+
args: [FFIType.ptr, FFIType.bool],
|
|
815
|
+
returns: FFIType.void,
|
|
816
|
+
},
|
|
817
|
+
wgpuViewSetHidden: {
|
|
818
|
+
args: [FFIType.ptr, FFIType.bool],
|
|
819
|
+
returns: FFIType.void,
|
|
820
|
+
},
|
|
821
|
+
wgpuViewRemove: {
|
|
822
|
+
args: [FFIType.ptr],
|
|
823
|
+
returns: FFIType.void,
|
|
824
|
+
},
|
|
825
|
+
wgpuViewGetNativeHandle: {
|
|
826
|
+
args: [FFIType.ptr],
|
|
827
|
+
returns: FFIType.ptr,
|
|
828
|
+
},
|
|
829
|
+
wgpuInstanceCreateSurfaceMainThread: {
|
|
830
|
+
args: [FFIType.ptr, FFIType.ptr],
|
|
831
|
+
returns: FFIType.ptr,
|
|
832
|
+
},
|
|
833
|
+
wgpuSurfaceConfigureMainThread: {
|
|
834
|
+
args: [FFIType.ptr, FFIType.ptr],
|
|
835
|
+
returns: FFIType.void,
|
|
836
|
+
},
|
|
837
|
+
wgpuSurfaceGetCurrentTextureMainThread: {
|
|
838
|
+
args: [FFIType.ptr, FFIType.ptr],
|
|
839
|
+
returns: FFIType.void,
|
|
840
|
+
},
|
|
841
|
+
wgpuSurfacePresentMainThread: {
|
|
842
|
+
args: [FFIType.ptr],
|
|
843
|
+
returns: FFIType.i32,
|
|
844
|
+
},
|
|
845
|
+
wgpuQueueOnSubmittedWorkDoneShim: {
|
|
846
|
+
args: [FFIType.ptr, FFIType.ptr],
|
|
847
|
+
returns: FFIType.u64,
|
|
848
|
+
},
|
|
849
|
+
wgpuBufferMapAsyncShim: {
|
|
850
|
+
args: [FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.u64, FFIType.ptr],
|
|
851
|
+
returns: FFIType.u64,
|
|
852
|
+
},
|
|
853
|
+
wgpuInstanceWaitAnyShim: {
|
|
854
|
+
args: [FFIType.ptr, FFIType.u64, FFIType.u64],
|
|
855
|
+
returns: FFIType.i32,
|
|
856
|
+
},
|
|
857
|
+
wgpuBufferReadSyncShim: {
|
|
858
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.u64, FFIType.ptr],
|
|
859
|
+
returns: FFIType.ptr,
|
|
860
|
+
},
|
|
861
|
+
wgpuBufferReadSyncIntoShim: {
|
|
862
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.u64, FFIType.ptr],
|
|
863
|
+
returns: FFIType.i32,
|
|
864
|
+
},
|
|
865
|
+
wgpuBufferReadbackBeginShim: {
|
|
866
|
+
args: [FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.ptr],
|
|
867
|
+
returns: FFIType.ptr,
|
|
868
|
+
},
|
|
869
|
+
wgpuBufferReadbackStatusShim: {
|
|
870
|
+
args: [FFIType.ptr],
|
|
871
|
+
returns: FFIType.i32,
|
|
872
|
+
},
|
|
873
|
+
wgpuBufferReadbackFreeShim: {
|
|
874
|
+
args: [FFIType.ptr],
|
|
875
|
+
returns: FFIType.void,
|
|
876
|
+
},
|
|
877
|
+
wgpuRunGPUTest: {
|
|
878
|
+
args: [FFIType.ptr],
|
|
879
|
+
returns: FFIType.void,
|
|
880
|
+
},
|
|
881
|
+
wgpuCreateAdapterDeviceMainThread: {
|
|
882
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
883
|
+
returns: FFIType.void,
|
|
884
|
+
},
|
|
885
|
+
wgpuCreateSurfaceForView: {
|
|
886
|
+
args: [FFIType.ptr, FFIType.ptr],
|
|
887
|
+
returns: FFIType.ptr,
|
|
888
|
+
},
|
|
889
|
+
// Global keyboard shortcuts
|
|
890
|
+
setGlobalShortcutCallback: {
|
|
891
|
+
args: [FFIType.function],
|
|
892
|
+
returns: FFIType.void,
|
|
893
|
+
},
|
|
894
|
+
registerGlobalShortcut: {
|
|
895
|
+
args: [FFIType.cstring],
|
|
896
|
+
returns: FFIType.bool,
|
|
897
|
+
},
|
|
898
|
+
unregisterGlobalShortcut: {
|
|
899
|
+
args: [FFIType.cstring],
|
|
900
|
+
returns: FFIType.bool,
|
|
901
|
+
},
|
|
902
|
+
unregisterAllGlobalShortcuts: {
|
|
903
|
+
args: [],
|
|
904
|
+
returns: FFIType.void,
|
|
905
|
+
},
|
|
906
|
+
isGlobalShortcutRegistered: {
|
|
907
|
+
args: [FFIType.cstring],
|
|
908
|
+
returns: FFIType.bool,
|
|
909
|
+
},
|
|
910
|
+
|
|
911
|
+
// Session/Cookie API
|
|
912
|
+
sessionGetCookies: {
|
|
913
|
+
args: [FFIType.cstring, FFIType.cstring],
|
|
914
|
+
returns: FFIType.cstring,
|
|
915
|
+
},
|
|
916
|
+
sessionSetCookie: {
|
|
917
|
+
args: [FFIType.cstring, FFIType.cstring],
|
|
918
|
+
returns: FFIType.bool,
|
|
919
|
+
},
|
|
920
|
+
sessionRemoveCookie: {
|
|
921
|
+
args: [FFIType.cstring, FFIType.cstring, FFIType.cstring],
|
|
922
|
+
returns: FFIType.bool,
|
|
923
|
+
},
|
|
924
|
+
sessionClearCookies: {
|
|
925
|
+
args: [FFIType.cstring],
|
|
926
|
+
returns: FFIType.void,
|
|
927
|
+
},
|
|
928
|
+
sessionClearStorageData: {
|
|
929
|
+
args: [FFIType.cstring, FFIType.cstring],
|
|
930
|
+
returns: FFIType.void,
|
|
931
|
+
},
|
|
932
|
+
|
|
933
|
+
// URL scheme handler (macOS only)
|
|
934
|
+
setURLOpenHandler: {
|
|
935
|
+
args: [FFIType.function], // handler callback
|
|
936
|
+
returns: FFIType.void,
|
|
937
|
+
},
|
|
938
|
+
setAppReopenHandler: {
|
|
939
|
+
args: [FFIType.function],
|
|
940
|
+
returns: FFIType.void,
|
|
941
|
+
},
|
|
942
|
+
|
|
943
|
+
// JSCallback utils for native code to use
|
|
944
|
+
setJSUtils: {
|
|
945
|
+
args: [
|
|
946
|
+
FFIType.function, // get Mimetype from url/filename
|
|
947
|
+
FFIType.function, // get html property from webview
|
|
948
|
+
],
|
|
949
|
+
returns: FFIType.void,
|
|
950
|
+
},
|
|
951
|
+
setWindowIcon: {
|
|
952
|
+
args: [
|
|
953
|
+
FFIType.ptr, // window pointer
|
|
954
|
+
FFIType.cstring, // icon path
|
|
955
|
+
],
|
|
956
|
+
returns: FFIType.void,
|
|
957
|
+
},
|
|
958
|
+
killApp: {
|
|
959
|
+
args: [],
|
|
960
|
+
returns: FFIType.void,
|
|
961
|
+
},
|
|
962
|
+
stopEventLoop: {
|
|
963
|
+
args: [],
|
|
964
|
+
returns: FFIType.void,
|
|
965
|
+
},
|
|
966
|
+
waitForShutdownComplete: {
|
|
967
|
+
args: [FFIType.i32],
|
|
968
|
+
returns: FFIType.void,
|
|
969
|
+
},
|
|
970
|
+
forceExit: {
|
|
971
|
+
args: [FFIType.i32],
|
|
972
|
+
returns: FFIType.void,
|
|
973
|
+
},
|
|
974
|
+
setQuitRequestedHandler: {
|
|
975
|
+
args: [FFIType.function],
|
|
976
|
+
returns: FFIType.void,
|
|
977
|
+
},
|
|
978
|
+
testFFI2: {
|
|
979
|
+
args: [FFIType.function],
|
|
980
|
+
returns: FFIType.void,
|
|
981
|
+
},
|
|
982
|
+
});
|
|
983
|
+
} catch (err) {
|
|
984
|
+
throw new Error(`Failed to load native FFI libraries: ${err}`);
|
|
985
|
+
}
|
|
986
|
+
})();
|
|
987
|
+
|
|
988
|
+
// NOTE: Bun seems to hit limits on args or arg types. eg: trying to send 12 bools results
|
|
989
|
+
// in only about 8 going through then params after that. I think it may be similar to
|
|
990
|
+
// a zig bug I ran into last year. So check number of args in a signature when alignment issues occur.
|
|
991
|
+
|
|
992
|
+
// Non-null accessor for use inside _ffiImpl — these methods are only called when hasFFI is true.
|
|
993
|
+
const core_ = core!;
|
|
994
|
+
const native_ = native!;
|
|
995
|
+
const queuedHostMessageWebviewIdBuf = new Uint32Array(1);
|
|
996
|
+
|
|
997
|
+
const drainQueuedHostMessages = () => {
|
|
998
|
+
if (!core) {
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
for (;;) {
|
|
1003
|
+
const messagePtr = core_.symbols.popNextQueuedHostMessage(
|
|
1004
|
+
ptr(queuedHostMessageWebviewIdBuf),
|
|
1005
|
+
) as Pointer | null;
|
|
1006
|
+
|
|
1007
|
+
if (!messagePtr) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
try {
|
|
1012
|
+
const rawMessage = new CString(messagePtr).toString();
|
|
1013
|
+
if (!rawMessage) {
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const webview = BrowserView.ensureWrapped(
|
|
1018
|
+
queuedHostMessageWebviewIdBuf[0]!,
|
|
1019
|
+
);
|
|
1020
|
+
if (!webview) {
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
webview.rpcHandler?.(JSON.parse(rawMessage));
|
|
1025
|
+
} catch (err) {
|
|
1026
|
+
console.error("error draining queued host message:", err);
|
|
1027
|
+
} finally {
|
|
1028
|
+
core_.symbols.freeCoreString(messagePtr);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
if (core) {
|
|
1034
|
+
const wakeupReadFd = core_.symbols.getHostMessageWakeupReadFD();
|
|
1035
|
+
|
|
1036
|
+
if (typeof wakeupReadFd === "number" && wakeupReadFd >= 0) {
|
|
1037
|
+
try {
|
|
1038
|
+
const wakeupStream = createReadStream("/dev/null", {
|
|
1039
|
+
fd: wakeupReadFd,
|
|
1040
|
+
autoClose: false,
|
|
1041
|
+
});
|
|
1042
|
+
wakeupStream.on("data", () => {
|
|
1043
|
+
drainQueuedHostMessages();
|
|
1044
|
+
});
|
|
1045
|
+
wakeupStream.on("error", (error) => {
|
|
1046
|
+
console.error("host message wakeup stream failed, falling back to polling:", error);
|
|
1047
|
+
setInterval(drainQueuedHostMessages, 16);
|
|
1048
|
+
});
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
console.error("failed to start host message wakeup stream, falling back to polling:", error);
|
|
1051
|
+
setInterval(drainQueuedHostMessages, 16);
|
|
1052
|
+
}
|
|
1053
|
+
} else {
|
|
1054
|
+
setInterval(drainQueuedHostMessages, 16);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
drainQueuedHostMessages();
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const _ffiImpl = {
|
|
1061
|
+
request: {
|
|
1062
|
+
createWindow: (params: {
|
|
1063
|
+
url: string | null;
|
|
1064
|
+
title: string;
|
|
1065
|
+
frame: {
|
|
1066
|
+
width: number;
|
|
1067
|
+
height: number;
|
|
1068
|
+
x: number;
|
|
1069
|
+
y: number;
|
|
1070
|
+
};
|
|
1071
|
+
styleMask: {
|
|
1072
|
+
Borderless: boolean;
|
|
1073
|
+
Titled: boolean;
|
|
1074
|
+
Closable: boolean;
|
|
1075
|
+
Miniaturizable: boolean;
|
|
1076
|
+
Resizable: boolean;
|
|
1077
|
+
UnifiedTitleAndToolbar: boolean;
|
|
1078
|
+
FullScreen: boolean;
|
|
1079
|
+
FullSizeContentView: boolean;
|
|
1080
|
+
UtilityWindow: boolean;
|
|
1081
|
+
DocModalWindow: boolean;
|
|
1082
|
+
NonactivatingPanel: boolean;
|
|
1083
|
+
HUDWindow: boolean;
|
|
1084
|
+
};
|
|
1085
|
+
titleBarStyle: string;
|
|
1086
|
+
transparent: boolean;
|
|
1087
|
+
hidden?: boolean;
|
|
1088
|
+
activate?: boolean;
|
|
1089
|
+
trafficLightOffset?: {
|
|
1090
|
+
x: number;
|
|
1091
|
+
y: number;
|
|
1092
|
+
};
|
|
1093
|
+
}): number => {
|
|
1094
|
+
const {
|
|
1095
|
+
url: _url,
|
|
1096
|
+
title,
|
|
1097
|
+
frame: { x, y, width, height },
|
|
1098
|
+
styleMask: {
|
|
1099
|
+
Borderless,
|
|
1100
|
+
Titled,
|
|
1101
|
+
Closable,
|
|
1102
|
+
Miniaturizable,
|
|
1103
|
+
Resizable,
|
|
1104
|
+
UnifiedTitleAndToolbar,
|
|
1105
|
+
FullScreen,
|
|
1106
|
+
FullSizeContentView,
|
|
1107
|
+
UtilityWindow,
|
|
1108
|
+
DocModalWindow,
|
|
1109
|
+
NonactivatingPanel,
|
|
1110
|
+
HUDWindow,
|
|
1111
|
+
},
|
|
1112
|
+
titleBarStyle,
|
|
1113
|
+
transparent,
|
|
1114
|
+
hidden = false,
|
|
1115
|
+
activate = true,
|
|
1116
|
+
trafficLightOffset = { x: 0, y: 0 },
|
|
1117
|
+
} = params;
|
|
1118
|
+
|
|
1119
|
+
const styleMask = core_.symbols.getWindowStyle(
|
|
1120
|
+
Borderless,
|
|
1121
|
+
Titled,
|
|
1122
|
+
Closable,
|
|
1123
|
+
Miniaturizable,
|
|
1124
|
+
Resizable,
|
|
1125
|
+
UnifiedTitleAndToolbar,
|
|
1126
|
+
FullScreen,
|
|
1127
|
+
FullSizeContentView,
|
|
1128
|
+
UtilityWindow,
|
|
1129
|
+
DocModalWindow,
|
|
1130
|
+
NonactivatingPanel,
|
|
1131
|
+
HUDWindow,
|
|
1132
|
+
);
|
|
1133
|
+
|
|
1134
|
+
const windowId = core_.symbols.createWindow(
|
|
1135
|
+
// frame
|
|
1136
|
+
x,
|
|
1137
|
+
y,
|
|
1138
|
+
width,
|
|
1139
|
+
height,
|
|
1140
|
+
styleMask,
|
|
1141
|
+
// style
|
|
1142
|
+
toCString(titleBarStyle),
|
|
1143
|
+
transparent,
|
|
1144
|
+
toCString(title),
|
|
1145
|
+
hidden,
|
|
1146
|
+
activate,
|
|
1147
|
+
trafficLightOffset.x,
|
|
1148
|
+
trafficLightOffset.y,
|
|
1149
|
+
// callbacks
|
|
1150
|
+
windowCloseCallback,
|
|
1151
|
+
windowMoveCallback,
|
|
1152
|
+
windowResizeCallback,
|
|
1153
|
+
windowFocusCallback,
|
|
1154
|
+
windowBlurCallback,
|
|
1155
|
+
windowKeyCallback,
|
|
1156
|
+
);
|
|
1157
|
+
|
|
1158
|
+
if (!windowId) {
|
|
1159
|
+
throw getCoreLastError() || "Failed to create window";
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
return windowId;
|
|
1163
|
+
},
|
|
1164
|
+
getWindowPointer: (params: { winId: number }): Pointer | null => {
|
|
1165
|
+
return getWindowPtr(params.winId);
|
|
1166
|
+
},
|
|
1167
|
+
setTitle: (params: { winId: number; title: string }) => {
|
|
1168
|
+
const { winId, title } = params;
|
|
1169
|
+
const windowPtr = getWindowPtr(winId);
|
|
1170
|
+
|
|
1171
|
+
if (!windowPtr) {
|
|
1172
|
+
throw `Can't set window title. Window no longer exists`;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
core_.symbols.setWindowTitle(winId, toCString(title));
|
|
1176
|
+
},
|
|
1177
|
+
|
|
1178
|
+
closeWindow: (params: { winId: number }) => {
|
|
1179
|
+
const { winId } = params;
|
|
1180
|
+
const windowPtr = getWindowPtr(winId);
|
|
1181
|
+
|
|
1182
|
+
if (!windowPtr) {
|
|
1183
|
+
// Window already closed — silently ignore the race condition
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
core_.symbols.closeWindow(winId);
|
|
1188
|
+
// Note: Cleanup of BrowserWindowMap happens in the windowCloseCallback
|
|
1189
|
+
},
|
|
1190
|
+
|
|
1191
|
+
showWindow: (params: { winId: number; activate?: boolean }) => {
|
|
1192
|
+
const { winId } = params;
|
|
1193
|
+
const windowPtr = getWindowPtr(winId);
|
|
1194
|
+
|
|
1195
|
+
if (!windowPtr) {
|
|
1196
|
+
throw `Can't show window. Window no longer exists`;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
core_.symbols.showWindow(winId, params.activate ?? true);
|
|
1200
|
+
},
|
|
1201
|
+
|
|
1202
|
+
activateWindow: (params: { winId: number }) => {
|
|
1203
|
+
const { winId } = params;
|
|
1204
|
+
const windowPtr = getWindowPtr(winId);
|
|
1205
|
+
|
|
1206
|
+
if (!windowPtr) {
|
|
1207
|
+
throw `Can't activate window. Window no longer exists`;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
core_.symbols.activateWindow(winId);
|
|
1211
|
+
},
|
|
1212
|
+
|
|
1213
|
+
hideWindow: (params: { winId: number }) => {
|
|
1214
|
+
const { winId } = params;
|
|
1215
|
+
const windowPtr = getWindowPtr(winId);
|
|
1216
|
+
|
|
1217
|
+
if (!windowPtr) {
|
|
1218
|
+
throw `Can't hide window. Window no longer exists`;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
core_.symbols.hideWindow(winId);
|
|
1222
|
+
},
|
|
1223
|
+
|
|
1224
|
+
minimizeWindow: (params: { winId: number }) => {
|
|
1225
|
+
const { winId } = params;
|
|
1226
|
+
const windowPtr = getWindowPtr(winId);
|
|
1227
|
+
|
|
1228
|
+
if (!windowPtr) {
|
|
1229
|
+
throw `Can't minimize window. Window no longer exists`;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
core_.symbols.minimizeWindow(winId);
|
|
1233
|
+
},
|
|
1234
|
+
|
|
1235
|
+
restoreWindow: (params: { winId: number }) => {
|
|
1236
|
+
const { winId } = params;
|
|
1237
|
+
const windowPtr = getWindowPtr(winId);
|
|
1238
|
+
|
|
1239
|
+
if (!windowPtr) {
|
|
1240
|
+
throw `Can't restore window. Window no longer exists`;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
core_.symbols.restoreWindow(winId);
|
|
1244
|
+
},
|
|
1245
|
+
|
|
1246
|
+
isWindowMinimized: (params: { winId: number }): boolean => {
|
|
1247
|
+
const { winId } = params;
|
|
1248
|
+
const windowPtr = getWindowPtr(winId);
|
|
1249
|
+
|
|
1250
|
+
if (!windowPtr) {
|
|
1251
|
+
return false;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
return core_.symbols.isWindowMinimized(winId);
|
|
1255
|
+
},
|
|
1256
|
+
|
|
1257
|
+
maximizeWindow: (params: { winId: number }) => {
|
|
1258
|
+
const { winId } = params;
|
|
1259
|
+
const windowPtr = getWindowPtr(winId);
|
|
1260
|
+
|
|
1261
|
+
if (!windowPtr) {
|
|
1262
|
+
throw `Can't maximize window. Window no longer exists`;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
core_.symbols.maximizeWindow(winId);
|
|
1266
|
+
},
|
|
1267
|
+
|
|
1268
|
+
unmaximizeWindow: (params: { winId: number }) => {
|
|
1269
|
+
const { winId } = params;
|
|
1270
|
+
const windowPtr = getWindowPtr(winId);
|
|
1271
|
+
|
|
1272
|
+
if (!windowPtr) {
|
|
1273
|
+
throw `Can't unmaximize window. Window no longer exists`;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
core_.symbols.unmaximizeWindow(winId);
|
|
1277
|
+
},
|
|
1278
|
+
|
|
1279
|
+
isWindowMaximized: (params: { winId: number }): boolean => {
|
|
1280
|
+
const { winId } = params;
|
|
1281
|
+
const windowPtr = getWindowPtr(winId);
|
|
1282
|
+
|
|
1283
|
+
if (!windowPtr) {
|
|
1284
|
+
return false;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
return core_.symbols.isWindowMaximized(winId);
|
|
1288
|
+
},
|
|
1289
|
+
|
|
1290
|
+
setWindowFullScreen: (params: { winId: number; fullScreen: boolean }) => {
|
|
1291
|
+
const { winId, fullScreen } = params;
|
|
1292
|
+
const windowPtr = getWindowPtr(winId);
|
|
1293
|
+
|
|
1294
|
+
if (!windowPtr) {
|
|
1295
|
+
throw `Can't set fullscreen. Window no longer exists`;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
core_.symbols.setWindowFullScreen(winId, fullScreen);
|
|
1299
|
+
},
|
|
1300
|
+
|
|
1301
|
+
isWindowFullScreen: (params: { winId: number }): boolean => {
|
|
1302
|
+
const { winId } = params;
|
|
1303
|
+
const windowPtr = getWindowPtr(winId);
|
|
1304
|
+
|
|
1305
|
+
if (!windowPtr) {
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
return core_.symbols.isWindowFullScreen(winId);
|
|
1310
|
+
},
|
|
1311
|
+
|
|
1312
|
+
setWindowAlwaysOnTop: (params: { winId: number; alwaysOnTop: boolean }) => {
|
|
1313
|
+
const { winId, alwaysOnTop } = params;
|
|
1314
|
+
const windowPtr = getWindowPtr(winId);
|
|
1315
|
+
|
|
1316
|
+
if (!windowPtr) {
|
|
1317
|
+
throw `Can't set always on top. Window no longer exists`;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
core_.symbols.setWindowAlwaysOnTop(winId, alwaysOnTop);
|
|
1321
|
+
},
|
|
1322
|
+
|
|
1323
|
+
isWindowAlwaysOnTop: (params: { winId: number }): boolean => {
|
|
1324
|
+
const { winId } = params;
|
|
1325
|
+
const windowPtr = getWindowPtr(winId);
|
|
1326
|
+
|
|
1327
|
+
if (!windowPtr) {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
return core_.symbols.isWindowAlwaysOnTop(winId);
|
|
1332
|
+
},
|
|
1333
|
+
|
|
1334
|
+
setWindowVisibleOnAllWorkspaces: (params: {
|
|
1335
|
+
winId: number;
|
|
1336
|
+
visibleOnAllWorkspaces: boolean;
|
|
1337
|
+
}) => {
|
|
1338
|
+
const { winId, visibleOnAllWorkspaces } = params;
|
|
1339
|
+
const windowPtr = getWindowPtr(winId);
|
|
1340
|
+
|
|
1341
|
+
if (!windowPtr) {
|
|
1342
|
+
throw `Can't set visible on all workspaces. Window no longer exists`;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
core_.symbols.setWindowVisibleOnAllWorkspaces(
|
|
1346
|
+
winId,
|
|
1347
|
+
visibleOnAllWorkspaces,
|
|
1348
|
+
);
|
|
1349
|
+
},
|
|
1350
|
+
|
|
1351
|
+
isWindowVisibleOnAllWorkspaces: (params: { winId: number }): boolean => {
|
|
1352
|
+
const { winId } = params;
|
|
1353
|
+
const windowPtr = getWindowPtr(winId);
|
|
1354
|
+
|
|
1355
|
+
if (!windowPtr) {
|
|
1356
|
+
return false;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
return core_.symbols.isWindowVisibleOnAllWorkspaces(winId);
|
|
1360
|
+
},
|
|
1361
|
+
|
|
1362
|
+
setWindowPosition: (params: { winId: number; x: number; y: number }) => {
|
|
1363
|
+
const { winId, x, y } = params;
|
|
1364
|
+
const windowPtr = getWindowPtr(winId);
|
|
1365
|
+
|
|
1366
|
+
if (!windowPtr) {
|
|
1367
|
+
throw `Can't set window position. Window no longer exists`;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
core_.symbols.setWindowPosition(winId, x, y);
|
|
1371
|
+
},
|
|
1372
|
+
|
|
1373
|
+
setWindowButtonPosition: (params: { winId: number; x: number; y: number }) => {
|
|
1374
|
+
const { winId, x, y } = params;
|
|
1375
|
+
const windowPtr = getWindowPtr(winId);
|
|
1376
|
+
|
|
1377
|
+
if (!windowPtr) {
|
|
1378
|
+
throw `Can't set window button position. Window no longer exists`;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
core_.symbols.setWindowButtonPosition(winId, x, y);
|
|
1382
|
+
},
|
|
1383
|
+
|
|
1384
|
+
setWindowSize: (params: {
|
|
1385
|
+
winId: number;
|
|
1386
|
+
width: number;
|
|
1387
|
+
height: number;
|
|
1388
|
+
}) => {
|
|
1389
|
+
const { winId, width, height } = params;
|
|
1390
|
+
const windowPtr = getWindowPtr(winId);
|
|
1391
|
+
|
|
1392
|
+
if (!windowPtr) {
|
|
1393
|
+
throw `Can't set window size. Window no longer exists`;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
core_.symbols.setWindowSize(winId, width, height);
|
|
1397
|
+
},
|
|
1398
|
+
|
|
1399
|
+
setWindowFrame: (params: {
|
|
1400
|
+
winId: number;
|
|
1401
|
+
x: number;
|
|
1402
|
+
y: number;
|
|
1403
|
+
width: number;
|
|
1404
|
+
height: number;
|
|
1405
|
+
}) => {
|
|
1406
|
+
const { winId, x, y, width, height } = params;
|
|
1407
|
+
const windowPtr = getWindowPtr(winId);
|
|
1408
|
+
|
|
1409
|
+
if (!windowPtr) {
|
|
1410
|
+
throw `Can't set window frame. Window no longer exists`;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
core_.symbols.setWindowFrame(winId, x, y, width, height);
|
|
1414
|
+
},
|
|
1415
|
+
|
|
1416
|
+
getWindowFrame: (params: {
|
|
1417
|
+
winId: number;
|
|
1418
|
+
}): { x: number; y: number; width: number; height: number } => {
|
|
1419
|
+
const { winId } = params;
|
|
1420
|
+
const windowPtr = getWindowPtr(winId);
|
|
1421
|
+
|
|
1422
|
+
if (!windowPtr) {
|
|
1423
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// Create buffers to receive the output values
|
|
1427
|
+
const xBuf = new Float64Array(1);
|
|
1428
|
+
const yBuf = new Float64Array(1);
|
|
1429
|
+
const widthBuf = new Float64Array(1);
|
|
1430
|
+
const heightBuf = new Float64Array(1);
|
|
1431
|
+
|
|
1432
|
+
core_.symbols.getWindowFrame(
|
|
1433
|
+
winId,
|
|
1434
|
+
ptr(xBuf),
|
|
1435
|
+
ptr(yBuf),
|
|
1436
|
+
ptr(widthBuf),
|
|
1437
|
+
ptr(heightBuf),
|
|
1438
|
+
);
|
|
1439
|
+
|
|
1440
|
+
return {
|
|
1441
|
+
x: xBuf[0]!,
|
|
1442
|
+
y: yBuf[0]!,
|
|
1443
|
+
width: widthBuf[0]!,
|
|
1444
|
+
height: heightBuf[0]!,
|
|
1445
|
+
};
|
|
1446
|
+
},
|
|
1447
|
+
createWebview: (params: {
|
|
1448
|
+
windowId: number;
|
|
1449
|
+
hostWebviewId: number | null;
|
|
1450
|
+
renderer: "cef" | "native";
|
|
1451
|
+
secretKey: string;
|
|
1452
|
+
url: string | null;
|
|
1453
|
+
partition: string | null;
|
|
1454
|
+
preload: string | null;
|
|
1455
|
+
viewsRoot: string | null;
|
|
1456
|
+
frame: {
|
|
1457
|
+
x: number;
|
|
1458
|
+
y: number;
|
|
1459
|
+
width: number;
|
|
1460
|
+
height: number;
|
|
1461
|
+
};
|
|
1462
|
+
autoResize: boolean;
|
|
1463
|
+
navigationRules: string | null;
|
|
1464
|
+
sandbox: boolean;
|
|
1465
|
+
startTransparent: boolean;
|
|
1466
|
+
startPassthrough: boolean;
|
|
1467
|
+
}): number => {
|
|
1468
|
+
const {
|
|
1469
|
+
windowId,
|
|
1470
|
+
hostWebviewId,
|
|
1471
|
+
renderer,
|
|
1472
|
+
secretKey,
|
|
1473
|
+
url,
|
|
1474
|
+
partition,
|
|
1475
|
+
preload,
|
|
1476
|
+
viewsRoot,
|
|
1477
|
+
frame: { x, y, width, height },
|
|
1478
|
+
autoResize,
|
|
1479
|
+
sandbox,
|
|
1480
|
+
startTransparent,
|
|
1481
|
+
startPassthrough,
|
|
1482
|
+
} = params;
|
|
1483
|
+
ensureWebviewRuntimeConfigured();
|
|
1484
|
+
|
|
1485
|
+
const webviewId = core_.symbols.createWebview(
|
|
1486
|
+
windowId,
|
|
1487
|
+
hostWebviewId || 0,
|
|
1488
|
+
toCString(renderer),
|
|
1489
|
+
toCString(url || ""),
|
|
1490
|
+
x,
|
|
1491
|
+
y,
|
|
1492
|
+
width,
|
|
1493
|
+
height,
|
|
1494
|
+
autoResize,
|
|
1495
|
+
toCString(partition || "persist:default"),
|
|
1496
|
+
webviewDecideNavigation,
|
|
1497
|
+
webviewEventJSCallback,
|
|
1498
|
+
eventBridgeHandler,
|
|
1499
|
+
hostBridgePostmessageHandler,
|
|
1500
|
+
internalBridgeHandler,
|
|
1501
|
+
toCString(secretKey),
|
|
1502
|
+
toCString(preload || ""),
|
|
1503
|
+
toCString(viewsRoot || ""),
|
|
1504
|
+
sandbox, // When true, hostBridge and internalBridge are not set up in native code
|
|
1505
|
+
startTransparent,
|
|
1506
|
+
startPassthrough,
|
|
1507
|
+
);
|
|
1508
|
+
|
|
1509
|
+
if (!webviewId) {
|
|
1510
|
+
throw getCoreLastError() || "Failed to create webview";
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
return webviewId;
|
|
1514
|
+
},
|
|
1515
|
+
getWebviewPointer: (params: { id: number }): Pointer | null => {
|
|
1516
|
+
return core_.symbols.getWebviewPointer(params.id) || null;
|
|
1517
|
+
},
|
|
1518
|
+
resizeWebview: (params: {
|
|
1519
|
+
id: number;
|
|
1520
|
+
frame: { x: number; y: number; width: number; height: number };
|
|
1521
|
+
masks?: string;
|
|
1522
|
+
}) => {
|
|
1523
|
+
const { id, frame: { x, y, width, height }, masks = "[]" } = params;
|
|
1524
|
+
core_.symbols.resizeWebview(id, x, y, width, height, toCString(masks));
|
|
1525
|
+
},
|
|
1526
|
+
loadURLInWebView: (params: { id: number; url: string }) => {
|
|
1527
|
+
core_.symbols.loadURLInWebView(params.id, toCString(params.url));
|
|
1528
|
+
},
|
|
1529
|
+
loadHTMLInWebView: (params: { id: number; html: string }) => {
|
|
1530
|
+
core_.symbols.loadHTMLInWebView(params.id, toCString(params.html));
|
|
1531
|
+
},
|
|
1532
|
+
updatePreloadScriptToWebView: (params: {
|
|
1533
|
+
id: number;
|
|
1534
|
+
scriptIdentifier: string;
|
|
1535
|
+
script: string;
|
|
1536
|
+
allFrames: boolean;
|
|
1537
|
+
}) => {
|
|
1538
|
+
core_.symbols.updatePreloadScriptToWebView(
|
|
1539
|
+
params.id,
|
|
1540
|
+
toCString(params.scriptIdentifier),
|
|
1541
|
+
toCString(params.script),
|
|
1542
|
+
params.allFrames,
|
|
1543
|
+
);
|
|
1544
|
+
},
|
|
1545
|
+
webviewCanGoBack: (params: { id: number }) => {
|
|
1546
|
+
return core_.symbols.webviewCanGoBack(params.id);
|
|
1547
|
+
},
|
|
1548
|
+
webviewCanGoForward: (params: { id: number }) => {
|
|
1549
|
+
return core_.symbols.webviewCanGoForward(params.id);
|
|
1550
|
+
},
|
|
1551
|
+
webviewGoBack: (params: { id: number }) => {
|
|
1552
|
+
core_.symbols.webviewGoBack(params.id);
|
|
1553
|
+
},
|
|
1554
|
+
webviewGoForward: (params: { id: number }) => {
|
|
1555
|
+
core_.symbols.webviewGoForward(params.id);
|
|
1556
|
+
},
|
|
1557
|
+
webviewReload: (params: { id: number }) => {
|
|
1558
|
+
core_.symbols.webviewReload(params.id);
|
|
1559
|
+
},
|
|
1560
|
+
webviewRemove: (params: { id: number }) => {
|
|
1561
|
+
core_.symbols.webviewRemove(params.id);
|
|
1562
|
+
},
|
|
1563
|
+
setWebviewHTMLContent: (params: { id: number; html: string }) => {
|
|
1564
|
+
core_.symbols.setWebviewHTMLContent(params.id, toCString(params.html));
|
|
1565
|
+
},
|
|
1566
|
+
webviewSetTransparent: (params: { id: number; transparent: boolean }) => {
|
|
1567
|
+
core_.symbols.webviewSetTransparent(params.id, params.transparent);
|
|
1568
|
+
},
|
|
1569
|
+
webviewSetPassthrough: (params: { id: number; passthrough: boolean }) => {
|
|
1570
|
+
core_.symbols.webviewSetPassthrough(params.id, params.passthrough);
|
|
1571
|
+
},
|
|
1572
|
+
webviewSetHidden: (params: { id: number; hidden: boolean }) => {
|
|
1573
|
+
core_.symbols.webviewSetHidden(params.id, params.hidden);
|
|
1574
|
+
},
|
|
1575
|
+
setWebviewNavigationRules: (params: { id: number; rulesJson: string }) => {
|
|
1576
|
+
core_.symbols.setWebviewNavigationRules(params.id, toCString(params.rulesJson));
|
|
1577
|
+
},
|
|
1578
|
+
webviewFindInPage: (params: {
|
|
1579
|
+
id: number;
|
|
1580
|
+
searchText: string;
|
|
1581
|
+
forward: boolean;
|
|
1582
|
+
matchCase: boolean;
|
|
1583
|
+
}) => {
|
|
1584
|
+
core_.symbols.webviewFindInPage(
|
|
1585
|
+
params.id,
|
|
1586
|
+
toCString(params.searchText),
|
|
1587
|
+
params.forward,
|
|
1588
|
+
params.matchCase,
|
|
1589
|
+
);
|
|
1590
|
+
},
|
|
1591
|
+
webviewStopFind: (params: { id: number }) => {
|
|
1592
|
+
core_.symbols.webviewStopFind(params.id);
|
|
1593
|
+
},
|
|
1594
|
+
|
|
1595
|
+
createWGPUView: (params: {
|
|
1596
|
+
windowId: number;
|
|
1597
|
+
frame: {
|
|
1598
|
+
x: number;
|
|
1599
|
+
y: number;
|
|
1600
|
+
width: number;
|
|
1601
|
+
height: number;
|
|
1602
|
+
};
|
|
1603
|
+
autoResize: boolean;
|
|
1604
|
+
startTransparent: boolean;
|
|
1605
|
+
startPassthrough: boolean;
|
|
1606
|
+
}): number => {
|
|
1607
|
+
const {
|
|
1608
|
+
windowId,
|
|
1609
|
+
frame: { x, y, width, height },
|
|
1610
|
+
autoResize,
|
|
1611
|
+
startTransparent,
|
|
1612
|
+
startPassthrough,
|
|
1613
|
+
} = params;
|
|
1614
|
+
|
|
1615
|
+
const viewId = core_.symbols.createWGPUView(
|
|
1616
|
+
windowId,
|
|
1617
|
+
x,
|
|
1618
|
+
y,
|
|
1619
|
+
width,
|
|
1620
|
+
height,
|
|
1621
|
+
autoResize,
|
|
1622
|
+
startTransparent,
|
|
1623
|
+
startPassthrough,
|
|
1624
|
+
);
|
|
1625
|
+
|
|
1626
|
+
if (!viewId) {
|
|
1627
|
+
throw "Failed to create WGPUView";
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
return viewId;
|
|
1631
|
+
},
|
|
1632
|
+
getWGPUViewPointer: (params: { id: number }): Pointer | null => {
|
|
1633
|
+
return core_.symbols.getWGPUViewPointer(params.id) || null;
|
|
1634
|
+
},
|
|
1635
|
+
|
|
1636
|
+
wgpuViewSetFrame: (params: {
|
|
1637
|
+
id: number;
|
|
1638
|
+
x: number;
|
|
1639
|
+
y: number;
|
|
1640
|
+
width: number;
|
|
1641
|
+
height: number;
|
|
1642
|
+
}) => {
|
|
1643
|
+
core_.symbols.setWGPUViewFrame(
|
|
1644
|
+
params.id,
|
|
1645
|
+
params.x,
|
|
1646
|
+
params.y,
|
|
1647
|
+
params.width,
|
|
1648
|
+
params.height,
|
|
1649
|
+
);
|
|
1650
|
+
},
|
|
1651
|
+
|
|
1652
|
+
wgpuViewSetTransparent: (params: { id: number; transparent: boolean }) => {
|
|
1653
|
+
core_.symbols.setWGPUViewTransparent(params.id, params.transparent);
|
|
1654
|
+
},
|
|
1655
|
+
|
|
1656
|
+
wgpuViewSetPassthrough: (params: {
|
|
1657
|
+
id: number;
|
|
1658
|
+
passthrough: boolean;
|
|
1659
|
+
}) => {
|
|
1660
|
+
core_.symbols.setWGPUViewPassthrough(params.id, params.passthrough);
|
|
1661
|
+
},
|
|
1662
|
+
|
|
1663
|
+
wgpuViewSetHidden: (params: { id: number; hidden: boolean }) => {
|
|
1664
|
+
core_.symbols.setWGPUViewHidden(params.id, params.hidden);
|
|
1665
|
+
},
|
|
1666
|
+
|
|
1667
|
+
wgpuViewRemove: (params: { id: number }) => {
|
|
1668
|
+
core_.symbols.removeWGPUView(params.id);
|
|
1669
|
+
},
|
|
1670
|
+
wgpuViewGetNativeHandle: (params: { id: number }): Pointer | null => {
|
|
1671
|
+
return core_.symbols.getWGPUViewNativeHandle(params.id) || null;
|
|
1672
|
+
},
|
|
1673
|
+
runWGPUViewTest: (params: { id: number }) => {
|
|
1674
|
+
core_.symbols.runWGPUViewTest(params.id);
|
|
1675
|
+
},
|
|
1676
|
+
|
|
1677
|
+
evaluateJavascriptWithNoCompletion: (params: {
|
|
1678
|
+
id: number;
|
|
1679
|
+
js: string;
|
|
1680
|
+
}) => {
|
|
1681
|
+
core_.symbols.evaluateJavaScriptWithNoCompletion(
|
|
1682
|
+
params.id,
|
|
1683
|
+
toCString(params.js),
|
|
1684
|
+
);
|
|
1685
|
+
},
|
|
1686
|
+
sendHostMessageToWebviewViaTransport: (params: {
|
|
1687
|
+
id: number;
|
|
1688
|
+
messageJson: string;
|
|
1689
|
+
}): boolean => {
|
|
1690
|
+
return core_.symbols.sendHostMessageToWebviewViaTransport(
|
|
1691
|
+
params.id,
|
|
1692
|
+
toCString(params.messageJson),
|
|
1693
|
+
);
|
|
1694
|
+
},
|
|
1695
|
+
clearWebviewHostTransport: (params: { id: number }) => {
|
|
1696
|
+
core_.symbols.clearWebviewHostTransport(params.id);
|
|
1697
|
+
},
|
|
1698
|
+
webviewOpenDevTools: (params: { id: number }) => {
|
|
1699
|
+
core_.symbols.webviewOpenDevTools(params.id);
|
|
1700
|
+
},
|
|
1701
|
+
webviewCloseDevTools: (params: { id: number }) => {
|
|
1702
|
+
core_.symbols.webviewCloseDevTools(params.id);
|
|
1703
|
+
},
|
|
1704
|
+
webviewToggleDevTools: (params: { id: number }) => {
|
|
1705
|
+
core_.symbols.webviewToggleDevTools(params.id);
|
|
1706
|
+
},
|
|
1707
|
+
webviewSetPageZoom: (params: { id: number; zoomLevel: number }) => {
|
|
1708
|
+
core_.symbols.webviewSetPageZoom(params.id, params.zoomLevel);
|
|
1709
|
+
},
|
|
1710
|
+
webviewGetPageZoom: (params: { id: number }): number => {
|
|
1711
|
+
return core_.symbols.webviewGetPageZoom(params.id);
|
|
1712
|
+
},
|
|
1713
|
+
setExitOnLastWindowClosed: (params: { enabled: boolean }) => {
|
|
1714
|
+
core_.symbols.setExitOnLastWindowClosed(params.enabled);
|
|
1715
|
+
},
|
|
1716
|
+
quitGracefully: (params: { code: number; timeoutMs: number }) => {
|
|
1717
|
+
core_.symbols.quitGracefully(params.code, params.timeoutMs);
|
|
1718
|
+
},
|
|
1719
|
+
|
|
1720
|
+
createTray: (params: {
|
|
1721
|
+
title: string;
|
|
1722
|
+
image: string;
|
|
1723
|
+
template: boolean;
|
|
1724
|
+
width: number;
|
|
1725
|
+
height: number;
|
|
1726
|
+
}): number => {
|
|
1727
|
+
const { title, image, template, width, height } = params;
|
|
1728
|
+
|
|
1729
|
+
const trayId = core_.symbols.createTray(
|
|
1730
|
+
toCString(title),
|
|
1731
|
+
toCString(image),
|
|
1732
|
+
template,
|
|
1733
|
+
width,
|
|
1734
|
+
height,
|
|
1735
|
+
trayItemHandler,
|
|
1736
|
+
);
|
|
1737
|
+
|
|
1738
|
+
if (!trayId) {
|
|
1739
|
+
throw "Failed to create tray";
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
return trayId;
|
|
1743
|
+
},
|
|
1744
|
+
showTray: (params: { id: number }): boolean => {
|
|
1745
|
+
return core_.symbols.showTray(params.id);
|
|
1746
|
+
},
|
|
1747
|
+
hideTray: (params: { id: number }): void => {
|
|
1748
|
+
core_.symbols.hideTray(params.id);
|
|
1749
|
+
},
|
|
1750
|
+
setTrayTitle: (params: { id: number; title: string }): void => {
|
|
1751
|
+
const { id, title } = params;
|
|
1752
|
+
core_.symbols.setTrayTitle(id, toCString(title));
|
|
1753
|
+
},
|
|
1754
|
+
setTrayImage: (params: { id: number; image: string }): void => {
|
|
1755
|
+
const { id, image } = params;
|
|
1756
|
+
core_.symbols.setTrayImage(id, toCString(image));
|
|
1757
|
+
},
|
|
1758
|
+
setTrayMenu: (params: {
|
|
1759
|
+
id: number;
|
|
1760
|
+
// json string of config
|
|
1761
|
+
menuConfig: string;
|
|
1762
|
+
}): void => {
|
|
1763
|
+
const { id, menuConfig } = params;
|
|
1764
|
+
core_.symbols.setTrayMenu(id, toCString(menuConfig));
|
|
1765
|
+
},
|
|
1766
|
+
|
|
1767
|
+
removeTray: (params: { id: number }): void => {
|
|
1768
|
+
core_.symbols.removeTray(params.id);
|
|
1769
|
+
},
|
|
1770
|
+
getTrayBounds: (params: { id: number }): Rectangle => {
|
|
1771
|
+
const jsonStr = core_.symbols.getTrayBounds(params.id);
|
|
1772
|
+
if (!jsonStr) {
|
|
1773
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
try {
|
|
1777
|
+
return JSON.parse(jsonStr.toString());
|
|
1778
|
+
} catch {
|
|
1779
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
1780
|
+
}
|
|
1781
|
+
},
|
|
1782
|
+
setApplicationMenu: (params: { menuConfig: string }): void => {
|
|
1783
|
+
const { menuConfig } = params;
|
|
1784
|
+
|
|
1785
|
+
core_.symbols.setApplicationMenu(
|
|
1786
|
+
toCString(menuConfig),
|
|
1787
|
+
applicationMenuHandler,
|
|
1788
|
+
);
|
|
1789
|
+
},
|
|
1790
|
+
showContextMenu: (params: { menuConfig: string }): void => {
|
|
1791
|
+
const { menuConfig } = params;
|
|
1792
|
+
|
|
1793
|
+
core_.symbols.showContextMenu(toCString(menuConfig), contextMenuHandler);
|
|
1794
|
+
},
|
|
1795
|
+
moveToTrash: (params: { path: string }): boolean => {
|
|
1796
|
+
const { path } = params;
|
|
1797
|
+
|
|
1798
|
+
return core_.symbols.moveToTrash(toCString(path));
|
|
1799
|
+
},
|
|
1800
|
+
showItemInFolder: (params: { path: string }): void => {
|
|
1801
|
+
const { path } = params;
|
|
1802
|
+
|
|
1803
|
+
core_.symbols.showItemInFolder(toCString(path));
|
|
1804
|
+
},
|
|
1805
|
+
openExternal: (params: { url: string }): boolean => {
|
|
1806
|
+
const { url } = params;
|
|
1807
|
+
return core_.symbols.openExternal(toCString(url));
|
|
1808
|
+
},
|
|
1809
|
+
openPath: (params: { path: string }): boolean => {
|
|
1810
|
+
const { path } = params;
|
|
1811
|
+
return core_.symbols.openPath(toCString(path));
|
|
1812
|
+
},
|
|
1813
|
+
showNotification: (params: {
|
|
1814
|
+
title: string;
|
|
1815
|
+
body?: string;
|
|
1816
|
+
subtitle?: string;
|
|
1817
|
+
silent?: boolean;
|
|
1818
|
+
}): void => {
|
|
1819
|
+
const { title, body = "", subtitle = "", silent = false } = params;
|
|
1820
|
+
core_.symbols.showNotification(
|
|
1821
|
+
toCString(title),
|
|
1822
|
+
toCString(body),
|
|
1823
|
+
toCString(subtitle),
|
|
1824
|
+
silent,
|
|
1825
|
+
);
|
|
1826
|
+
},
|
|
1827
|
+
setDockIconVisible: (params: { visible: boolean }): void => {
|
|
1828
|
+
core_.symbols.setDockIconVisible(params.visible);
|
|
1829
|
+
},
|
|
1830
|
+
isDockIconVisible: (): boolean => {
|
|
1831
|
+
return core_.symbols.isDockIconVisible();
|
|
1832
|
+
},
|
|
1833
|
+
openFileDialog: (params: {
|
|
1834
|
+
startingFolder: string;
|
|
1835
|
+
allowedFileTypes: string;
|
|
1836
|
+
canChooseFiles: boolean;
|
|
1837
|
+
canChooseDirectory: boolean;
|
|
1838
|
+
allowsMultipleSelection: boolean;
|
|
1839
|
+
}): string => {
|
|
1840
|
+
const {
|
|
1841
|
+
startingFolder,
|
|
1842
|
+
allowedFileTypes,
|
|
1843
|
+
canChooseFiles,
|
|
1844
|
+
canChooseDirectory,
|
|
1845
|
+
allowsMultipleSelection,
|
|
1846
|
+
} = params;
|
|
1847
|
+
const filePath = core_.symbols.openFileDialog(
|
|
1848
|
+
toCString(startingFolder),
|
|
1849
|
+
toCString(allowedFileTypes),
|
|
1850
|
+
canChooseFiles ? 1 : 0,
|
|
1851
|
+
canChooseDirectory ? 1 : 0,
|
|
1852
|
+
allowsMultipleSelection ? 1 : 0,
|
|
1853
|
+
);
|
|
1854
|
+
|
|
1855
|
+
return filePath.toString();
|
|
1856
|
+
},
|
|
1857
|
+
showMessageBox: (params: {
|
|
1858
|
+
type?: string;
|
|
1859
|
+
title?: string;
|
|
1860
|
+
message?: string;
|
|
1861
|
+
detail?: string;
|
|
1862
|
+
buttons?: string[];
|
|
1863
|
+
defaultId?: number;
|
|
1864
|
+
cancelId?: number;
|
|
1865
|
+
}): number => {
|
|
1866
|
+
const {
|
|
1867
|
+
type = "info",
|
|
1868
|
+
title = "",
|
|
1869
|
+
message = "",
|
|
1870
|
+
detail = "",
|
|
1871
|
+
buttons = ["OK"],
|
|
1872
|
+
defaultId = 0,
|
|
1873
|
+
cancelId = -1,
|
|
1874
|
+
} = params;
|
|
1875
|
+
// Convert buttons array to comma-separated string
|
|
1876
|
+
const buttonsStr = buttons.join(",");
|
|
1877
|
+
return core_.symbols.showMessageBox(
|
|
1878
|
+
toCString(type),
|
|
1879
|
+
toCString(title),
|
|
1880
|
+
toCString(message),
|
|
1881
|
+
toCString(detail),
|
|
1882
|
+
toCString(buttonsStr),
|
|
1883
|
+
defaultId,
|
|
1884
|
+
cancelId,
|
|
1885
|
+
);
|
|
1886
|
+
},
|
|
1887
|
+
|
|
1888
|
+
// Clipboard API
|
|
1889
|
+
clipboardReadText: (): string | null => {
|
|
1890
|
+
const result = core_.symbols.clipboardReadText();
|
|
1891
|
+
if (!result) return null;
|
|
1892
|
+
return result.toString();
|
|
1893
|
+
},
|
|
1894
|
+
clipboardWriteText: (params: { text: string }): void => {
|
|
1895
|
+
core_.symbols.clipboardWriteText(toCString(params.text));
|
|
1896
|
+
},
|
|
1897
|
+
clipboardReadImage: (): Uint8Array | null => {
|
|
1898
|
+
// Allocate a buffer for the size output
|
|
1899
|
+
const sizeBuffer = new BigUint64Array(1);
|
|
1900
|
+
const dataPtr = core_.symbols.clipboardReadImage(ptr(sizeBuffer));
|
|
1901
|
+
|
|
1902
|
+
if (!dataPtr) return null;
|
|
1903
|
+
|
|
1904
|
+
const size = Number(sizeBuffer[0]);
|
|
1905
|
+
if (size === 0) return null;
|
|
1906
|
+
|
|
1907
|
+
// Copy the data to a Uint8Array
|
|
1908
|
+
const result = new Uint8Array(size);
|
|
1909
|
+
const sourceView = new Uint8Array(toArrayBuffer(dataPtr, 0, size));
|
|
1910
|
+
result.set(sourceView);
|
|
1911
|
+
|
|
1912
|
+
// Note: The native code allocated this memory with malloc
|
|
1913
|
+
// We should free it, but Bun's FFI doesn't expose free directly
|
|
1914
|
+
// The memory will be reclaimed when the process exits
|
|
1915
|
+
|
|
1916
|
+
return result;
|
|
1917
|
+
},
|
|
1918
|
+
clipboardWriteImage: (params: { pngData: Uint8Array }): void => {
|
|
1919
|
+
const { pngData } = params;
|
|
1920
|
+
core_.symbols.clipboardWriteImage(ptr(pngData), BigInt(pngData.length));
|
|
1921
|
+
},
|
|
1922
|
+
clipboardClear: (): void => {
|
|
1923
|
+
core_.symbols.clipboardClear();
|
|
1924
|
+
},
|
|
1925
|
+
clipboardAvailableFormats: (): string[] => {
|
|
1926
|
+
const result = core_.symbols.clipboardAvailableFormats();
|
|
1927
|
+
if (!result) return [];
|
|
1928
|
+
const formatsStr = result.toString();
|
|
1929
|
+
if (!formatsStr) return [];
|
|
1930
|
+
return formatsStr.split(",").filter((f) => f.length > 0);
|
|
1931
|
+
},
|
|
1932
|
+
|
|
1933
|
+
// ffifunc: (params: {}): void => {
|
|
1934
|
+
// const {
|
|
1935
|
+
|
|
1936
|
+
// } = params;
|
|
1937
|
+
|
|
1938
|
+
// native_.symbols.ffifunc(
|
|
1939
|
+
|
|
1940
|
+
// );
|
|
1941
|
+
// },
|
|
1942
|
+
},
|
|
1943
|
+
// Internal functions for menu data management
|
|
1944
|
+
internal: {
|
|
1945
|
+
storeMenuData,
|
|
1946
|
+
getMenuData,
|
|
1947
|
+
clearMenuData,
|
|
1948
|
+
serializeMenuAction,
|
|
1949
|
+
deserializeMenuAction,
|
|
1950
|
+
},
|
|
1951
|
+
};
|
|
1952
|
+
|
|
1953
|
+
export const ffi = {
|
|
1954
|
+
request: _ffiImpl.request,
|
|
1955
|
+
internal: _ffiImpl.internal,
|
|
1956
|
+
};
|
|
1957
|
+
|
|
1958
|
+
export const WGPUBridge = {
|
|
1959
|
+
available: !!native?.symbols?.wgpuInstanceCreateSurfaceMainThread,
|
|
1960
|
+
instanceCreateSurface: (instancePtr: Pointer, descriptorPtr: Pointer): Pointer =>
|
|
1961
|
+
native_.symbols.wgpuInstanceCreateSurfaceMainThread(
|
|
1962
|
+
instancePtr as any,
|
|
1963
|
+
descriptorPtr as any,
|
|
1964
|
+
) as Pointer,
|
|
1965
|
+
surfaceConfigure: (surfacePtr: Pointer, configPtr: Pointer) =>
|
|
1966
|
+
native_.symbols.wgpuSurfaceConfigureMainThread(
|
|
1967
|
+
surfacePtr as any,
|
|
1968
|
+
configPtr as any,
|
|
1969
|
+
),
|
|
1970
|
+
surfaceGetCurrentTexture: (surfacePtr: Pointer, surfaceTexturePtr: Pointer) =>
|
|
1971
|
+
native_.symbols.wgpuSurfaceGetCurrentTextureMainThread(
|
|
1972
|
+
surfacePtr as any,
|
|
1973
|
+
surfaceTexturePtr as any,
|
|
1974
|
+
),
|
|
1975
|
+
surfacePresent: (surfacePtr: Pointer): number =>
|
|
1976
|
+
native_.symbols.wgpuSurfacePresentMainThread(surfacePtr as any),
|
|
1977
|
+
queueOnSubmittedWorkDone: (queuePtr: Pointer, callbackInfoPtr: Pointer): bigint =>
|
|
1978
|
+
native_.symbols.wgpuQueueOnSubmittedWorkDoneShim(
|
|
1979
|
+
queuePtr as any,
|
|
1980
|
+
callbackInfoPtr as any,
|
|
1981
|
+
),
|
|
1982
|
+
bufferMapAsync: (
|
|
1983
|
+
bufferPtr: Pointer,
|
|
1984
|
+
mode: bigint,
|
|
1985
|
+
offset: bigint,
|
|
1986
|
+
size: bigint,
|
|
1987
|
+
callbackInfoPtr: Pointer,
|
|
1988
|
+
): bigint =>
|
|
1989
|
+
native_.symbols.wgpuBufferMapAsyncShim(
|
|
1990
|
+
bufferPtr as any,
|
|
1991
|
+
mode as any,
|
|
1992
|
+
offset as any,
|
|
1993
|
+
size as any,
|
|
1994
|
+
callbackInfoPtr as any,
|
|
1995
|
+
),
|
|
1996
|
+
instanceWaitAny: (
|
|
1997
|
+
instancePtr: Pointer,
|
|
1998
|
+
futureId: bigint,
|
|
1999
|
+
timeoutNs: bigint,
|
|
2000
|
+
): number =>
|
|
2001
|
+
native_.symbols.wgpuInstanceWaitAnyShim(
|
|
2002
|
+
instancePtr as any,
|
|
2003
|
+
futureId as any,
|
|
2004
|
+
timeoutNs as any,
|
|
2005
|
+
),
|
|
2006
|
+
bufferReadSync: (
|
|
2007
|
+
instancePtr: Pointer,
|
|
2008
|
+
bufferPtr: Pointer,
|
|
2009
|
+
offset: bigint,
|
|
2010
|
+
size: bigint,
|
|
2011
|
+
timeoutNs: bigint,
|
|
2012
|
+
outSizePtr: Pointer,
|
|
2013
|
+
): Pointer =>
|
|
2014
|
+
native_.symbols.wgpuBufferReadSyncShim(
|
|
2015
|
+
instancePtr as any,
|
|
2016
|
+
bufferPtr as any,
|
|
2017
|
+
offset as any,
|
|
2018
|
+
size as any,
|
|
2019
|
+
timeoutNs as any,
|
|
2020
|
+
outSizePtr as any,
|
|
2021
|
+
) as Pointer,
|
|
2022
|
+
bufferReadSyncInto: (
|
|
2023
|
+
instancePtr: Pointer,
|
|
2024
|
+
bufferPtr: Pointer,
|
|
2025
|
+
offset: bigint,
|
|
2026
|
+
size: bigint,
|
|
2027
|
+
timeoutNs: bigint,
|
|
2028
|
+
dstPtr: Pointer,
|
|
2029
|
+
): number =>
|
|
2030
|
+
native_.symbols.wgpuBufferReadSyncIntoShim(
|
|
2031
|
+
instancePtr as any,
|
|
2032
|
+
bufferPtr as any,
|
|
2033
|
+
offset as any,
|
|
2034
|
+
size as any,
|
|
2035
|
+
timeoutNs as any,
|
|
2036
|
+
dstPtr as any,
|
|
2037
|
+
),
|
|
2038
|
+
bufferReadbackBegin: (
|
|
2039
|
+
bufferPtr: Pointer,
|
|
2040
|
+
offset: bigint,
|
|
2041
|
+
size: bigint,
|
|
2042
|
+
dstPtr: Pointer,
|
|
2043
|
+
): Pointer =>
|
|
2044
|
+
native_.symbols.wgpuBufferReadbackBeginShim(
|
|
2045
|
+
bufferPtr as any,
|
|
2046
|
+
offset as any,
|
|
2047
|
+
size as any,
|
|
2048
|
+
dstPtr as any,
|
|
2049
|
+
) as Pointer,
|
|
2050
|
+
bufferReadbackStatus: (jobPtr: Pointer): number =>
|
|
2051
|
+
native_.symbols.wgpuBufferReadbackStatusShim(jobPtr as any),
|
|
2052
|
+
bufferReadbackFree: (jobPtr: Pointer) =>
|
|
2053
|
+
native_.symbols.wgpuBufferReadbackFreeShim(jobPtr as any),
|
|
2054
|
+
runTest: (viewId: number) => {
|
|
2055
|
+
const view = WGPUView.getById(viewId);
|
|
2056
|
+
if (!view?.ptr) {
|
|
2057
|
+
console.error(`wgpuRunGPUTest: WGPUView not found for id ${viewId}`);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
if (!native?.symbols?.wgpuRunGPUTest) {
|
|
2061
|
+
console.error("wgpuRunGPUTest not available");
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
native_.symbols.wgpuRunGPUTest(view.ptr);
|
|
2065
|
+
},
|
|
2066
|
+
createAdapterDeviceMainThread: (
|
|
2067
|
+
instancePtr: Pointer,
|
|
2068
|
+
surfacePtr: Pointer,
|
|
2069
|
+
outAdapterDevicePtr: Pointer,
|
|
2070
|
+
) =>
|
|
2071
|
+
native_.symbols.wgpuCreateAdapterDeviceMainThread(
|
|
2072
|
+
instancePtr as any,
|
|
2073
|
+
surfacePtr as any,
|
|
2074
|
+
outAdapterDevicePtr as any,
|
|
2075
|
+
),
|
|
2076
|
+
createSurfaceForView: (instancePtr: Pointer, viewPtr: Pointer): Pointer | null => {
|
|
2077
|
+
if (!native?.symbols?.wgpuCreateSurfaceForView) return null;
|
|
2078
|
+
return native_.symbols.wgpuCreateSurfaceForView(instancePtr as any, viewPtr as any) as Pointer;
|
|
2079
|
+
},
|
|
2080
|
+
};
|
|
2081
|
+
|
|
2082
|
+
|
|
2083
|
+
// Worker management. Move to a different file
|
|
2084
|
+
process.on("uncaughtException", (err) => {
|
|
2085
|
+
console.error("Uncaught exception in worker:", err);
|
|
2086
|
+
if (native) {
|
|
2087
|
+
native_.symbols.stopEventLoop();
|
|
2088
|
+
native_.symbols.waitForShutdownComplete(5000);
|
|
2089
|
+
native_.symbols.forceExit(1);
|
|
2090
|
+
} else {
|
|
2091
|
+
process.exit(1);
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
|
|
2095
|
+
process.on("unhandledRejection", (reason, _promise) => {
|
|
2096
|
+
console.error("Unhandled rejection in worker:", reason);
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
process.on("SIGINT", () => {
|
|
2100
|
+
console.log("[sparkbun] Received SIGINT, running quit sequence...");
|
|
2101
|
+
const { quit } = require("../core/Utils");
|
|
2102
|
+
quit();
|
|
2103
|
+
});
|
|
2104
|
+
|
|
2105
|
+
process.on("SIGTERM", () => {
|
|
2106
|
+
console.log("[sparkbun] Received SIGTERM, running quit sequence...");
|
|
2107
|
+
const { quit } = require("../core/Utils");
|
|
2108
|
+
quit();
|
|
2109
|
+
});
|
|
2110
|
+
|
|
2111
|
+
// const testCallback = new JSCallback(
|
|
2112
|
+
// (windowId, x, y) => {
|
|
2113
|
+
// console.log(`TEST FFI Callback reffed GLOBALLY in js`);
|
|
2114
|
+
// // Your window move handler implementation
|
|
2115
|
+
// },
|
|
2116
|
+
// {
|
|
2117
|
+
// args: [],
|
|
2118
|
+
// returns: "void",
|
|
2119
|
+
// threadsafe: true,
|
|
2120
|
+
|
|
2121
|
+
// }
|
|
2122
|
+
// );
|
|
2123
|
+
|
|
2124
|
+
const windowCloseCallback = new JSCallback(
|
|
2125
|
+
(id) => {
|
|
2126
|
+
const handler = sparkBunEventEmitter.events.window.close;
|
|
2127
|
+
const event = handler({
|
|
2128
|
+
id,
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
// emit specific event first so user per-window handlers run
|
|
2132
|
+
// before the global handler (e.g. exitOnLastWindowClosed)
|
|
2133
|
+
sparkBunEventEmitter.emitEvent(event, id);
|
|
2134
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2135
|
+
},
|
|
2136
|
+
{
|
|
2137
|
+
args: ["u32"],
|
|
2138
|
+
returns: "void",
|
|
2139
|
+
threadsafe: true,
|
|
2140
|
+
},
|
|
2141
|
+
);
|
|
2142
|
+
|
|
2143
|
+
const windowMoveCallback = new JSCallback(
|
|
2144
|
+
(id, x, y) => {
|
|
2145
|
+
const handler = sparkBunEventEmitter.events.window.move;
|
|
2146
|
+
const event = handler({
|
|
2147
|
+
id,
|
|
2148
|
+
x,
|
|
2149
|
+
y,
|
|
2150
|
+
});
|
|
2151
|
+
|
|
2152
|
+
// global event
|
|
2153
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2154
|
+
sparkBunEventEmitter.emitEvent(event, id);
|
|
2155
|
+
},
|
|
2156
|
+
{
|
|
2157
|
+
args: ["u32", "f64", "f64"],
|
|
2158
|
+
returns: "void",
|
|
2159
|
+
threadsafe: true,
|
|
2160
|
+
},
|
|
2161
|
+
);
|
|
2162
|
+
|
|
2163
|
+
const windowResizeCallback = new JSCallback(
|
|
2164
|
+
(id, x, y, width, height) => {
|
|
2165
|
+
const handler = sparkBunEventEmitter.events.window.resize;
|
|
2166
|
+
const event = handler({
|
|
2167
|
+
id,
|
|
2168
|
+
x,
|
|
2169
|
+
y,
|
|
2170
|
+
width,
|
|
2171
|
+
height,
|
|
2172
|
+
});
|
|
2173
|
+
|
|
2174
|
+
// global event
|
|
2175
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2176
|
+
sparkBunEventEmitter.emitEvent(event, id);
|
|
2177
|
+
},
|
|
2178
|
+
{
|
|
2179
|
+
args: ["u32", "f64", "f64", "f64", "f64"],
|
|
2180
|
+
returns: "void",
|
|
2181
|
+
threadsafe: true,
|
|
2182
|
+
},
|
|
2183
|
+
);
|
|
2184
|
+
|
|
2185
|
+
const windowFocusCallback = new JSCallback(
|
|
2186
|
+
(id) => {
|
|
2187
|
+
const handler = sparkBunEventEmitter.events.window.focus;
|
|
2188
|
+
const event = handler({
|
|
2189
|
+
id,
|
|
2190
|
+
});
|
|
2191
|
+
|
|
2192
|
+
// global event
|
|
2193
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2194
|
+
sparkBunEventEmitter.emitEvent(event, id);
|
|
2195
|
+
},
|
|
2196
|
+
{
|
|
2197
|
+
args: ["u32"],
|
|
2198
|
+
returns: "void",
|
|
2199
|
+
threadsafe: true,
|
|
2200
|
+
},
|
|
2201
|
+
);
|
|
2202
|
+
|
|
2203
|
+
const windowBlurCallback = new JSCallback(
|
|
2204
|
+
(id) => {
|
|
2205
|
+
const handler = sparkBunEventEmitter.events.window.blur;
|
|
2206
|
+
const event = handler({
|
|
2207
|
+
id,
|
|
2208
|
+
});
|
|
2209
|
+
|
|
2210
|
+
// global event
|
|
2211
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2212
|
+
sparkBunEventEmitter.emitEvent(event, id);
|
|
2213
|
+
},
|
|
2214
|
+
{
|
|
2215
|
+
args: ["u32"],
|
|
2216
|
+
returns: "void",
|
|
2217
|
+
threadsafe: true,
|
|
2218
|
+
},
|
|
2219
|
+
);
|
|
2220
|
+
|
|
2221
|
+
// global event
|
|
2222
|
+
const windowKeyCallback = new JSCallback(
|
|
2223
|
+
(id, keyCode, modifiers, isDown, isRepeat) => {
|
|
2224
|
+
const handler = isDown
|
|
2225
|
+
? sparkBunEventEmitter.events.window.keyDown
|
|
2226
|
+
: sparkBunEventEmitter.events.window.keyUp;
|
|
2227
|
+
const event = handler({
|
|
2228
|
+
id,
|
|
2229
|
+
keyCode,
|
|
2230
|
+
modifiers,
|
|
2231
|
+
isRepeat: !!isRepeat,
|
|
2232
|
+
});
|
|
2233
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2234
|
+
sparkBunEventEmitter.emitEvent(event, id);
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
args: ["u32", "u32", "u32", "u32", "u32"],
|
|
2238
|
+
returns: "void",
|
|
2239
|
+
threadsafe: true,
|
|
2240
|
+
},
|
|
2241
|
+
);
|
|
2242
|
+
|
|
2243
|
+
const getMimeType = new JSCallback(
|
|
2244
|
+
(filePath) => {
|
|
2245
|
+
const _filePath = new CString(filePath).toString();
|
|
2246
|
+
const mimeType = Bun.file(_filePath).type; // || "application/octet-stream";
|
|
2247
|
+
|
|
2248
|
+
// For this usecase we generally don't want the charset included in the mimetype
|
|
2249
|
+
// otherwise it can break. eg: for html with text/javascript;charset=utf-8 browsers
|
|
2250
|
+
// will tend to render the code/text instead of interpreting the html.
|
|
2251
|
+
|
|
2252
|
+
return toCString(mimeType.split(";")[0]!);
|
|
2253
|
+
},
|
|
2254
|
+
{
|
|
2255
|
+
args: [FFIType.cstring],
|
|
2256
|
+
returns: FFIType.cstring,
|
|
2257
|
+
// threadsafe: true
|
|
2258
|
+
},
|
|
2259
|
+
);
|
|
2260
|
+
|
|
2261
|
+
const getHTMLForWebviewSync = new JSCallback(
|
|
2262
|
+
(webviewId) => {
|
|
2263
|
+
const webview = BrowserView.ensureWrapped(webviewId);
|
|
2264
|
+
|
|
2265
|
+
return toCString(webview?.html || "");
|
|
2266
|
+
},
|
|
2267
|
+
{
|
|
2268
|
+
args: [FFIType.u32],
|
|
2269
|
+
returns: FFIType.cstring,
|
|
2270
|
+
// threadsafe: true
|
|
2271
|
+
},
|
|
2272
|
+
);
|
|
2273
|
+
|
|
2274
|
+
if (native) native_.symbols.setJSUtils(getMimeType, getHTMLForWebviewSync);
|
|
2275
|
+
|
|
2276
|
+
// Native-only init: URL scheme handlers, quit handler, global shortcuts.
|
|
2277
|
+
// Skipped when running without FFI (carrot mode).
|
|
2278
|
+
const globalShortcutHandlers = new Map<string, () => void>();
|
|
2279
|
+
|
|
2280
|
+
if (native) {
|
|
2281
|
+
const urlOpenCallback = new JSCallback(
|
|
2282
|
+
(urlPtr) => {
|
|
2283
|
+
const url = new CString(urlPtr).toString();
|
|
2284
|
+
const handler = sparkBunEventEmitter.events.app.openUrl;
|
|
2285
|
+
const event = handler({ url });
|
|
2286
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2287
|
+
},
|
|
2288
|
+
{ args: [FFIType.cstring], returns: "void", threadsafe: true },
|
|
2289
|
+
);
|
|
2290
|
+
if (process.platform === "darwin") {
|
|
2291
|
+
native_.symbols.setURLOpenHandler(urlOpenCallback);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
const appReopenCallback = new JSCallback(
|
|
2295
|
+
() => {
|
|
2296
|
+
if (process.platform === "darwin") {
|
|
2297
|
+
core_.symbols.setDockIconVisible(true);
|
|
2298
|
+
}
|
|
2299
|
+
const handler = sparkBunEventEmitter.events.app.reopen;
|
|
2300
|
+
const event = handler({});
|
|
2301
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2302
|
+
},
|
|
2303
|
+
{ args: [], returns: "void", threadsafe: true },
|
|
2304
|
+
);
|
|
2305
|
+
if (process.platform === "darwin") {
|
|
2306
|
+
native_.symbols.setAppReopenHandler(appReopenCallback);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
const quitRequestedCallback = new JSCallback(
|
|
2310
|
+
() => {
|
|
2311
|
+
const { quit } = require("../core/Utils");
|
|
2312
|
+
quit();
|
|
2313
|
+
},
|
|
2314
|
+
{ args: [], returns: "void", threadsafe: true },
|
|
2315
|
+
);
|
|
2316
|
+
core_.symbols.setQuitRequestedHandler(quitRequestedCallback);
|
|
2317
|
+
|
|
2318
|
+
const globalShortcutCallback = new JSCallback(
|
|
2319
|
+
(acceleratorPtr) => {
|
|
2320
|
+
const accelerator = new CString(acceleratorPtr).toString();
|
|
2321
|
+
const handler = globalShortcutHandlers.get(accelerator);
|
|
2322
|
+
if (handler) handler();
|
|
2323
|
+
},
|
|
2324
|
+
{ args: [FFIType.cstring], returns: "void", threadsafe: true },
|
|
2325
|
+
);
|
|
2326
|
+
native_.symbols.setGlobalShortcutCallback(globalShortcutCallback);
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// GlobalShortcut module for external use
|
|
2330
|
+
export const GlobalShortcut = {
|
|
2331
|
+
/**
|
|
2332
|
+
* Register a global keyboard shortcut
|
|
2333
|
+
* @param accelerator - The shortcut string (e.g., "CommandOrControl+Shift+Space")
|
|
2334
|
+
* @param callback - Function to call when the shortcut is triggered
|
|
2335
|
+
* @returns true if registered successfully, false otherwise
|
|
2336
|
+
*/
|
|
2337
|
+
register: (accelerator: string, callback: () => void): boolean => {
|
|
2338
|
+
if (!native || globalShortcutHandlers.has(accelerator)) return false;
|
|
2339
|
+
const result = native_.symbols.registerGlobalShortcut(toCString(accelerator));
|
|
2340
|
+
if (result) globalShortcutHandlers.set(accelerator, callback);
|
|
2341
|
+
return result;
|
|
2342
|
+
},
|
|
2343
|
+
unregister: (accelerator: string): boolean => {
|
|
2344
|
+
if (!native) return false;
|
|
2345
|
+
const result = native_.symbols.unregisterGlobalShortcut(toCString(accelerator));
|
|
2346
|
+
if (result) globalShortcutHandlers.delete(accelerator);
|
|
2347
|
+
return result;
|
|
2348
|
+
},
|
|
2349
|
+
unregisterAll: (): void => {
|
|
2350
|
+
if (native) native_.symbols.unregisterAllGlobalShortcuts();
|
|
2351
|
+
globalShortcutHandlers.clear();
|
|
2352
|
+
},
|
|
2353
|
+
isRegistered: (accelerator: string): boolean => {
|
|
2354
|
+
if (!native) return false;
|
|
2355
|
+
return native_.symbols.isGlobalShortcutRegistered(toCString(accelerator));
|
|
2356
|
+
},
|
|
2357
|
+
};
|
|
2358
|
+
|
|
2359
|
+
// Types for Screen API
|
|
2360
|
+
export interface Rectangle {
|
|
2361
|
+
x: number;
|
|
2362
|
+
y: number;
|
|
2363
|
+
width: number;
|
|
2364
|
+
height: number;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
export interface Display {
|
|
2368
|
+
id: number;
|
|
2369
|
+
bounds: Rectangle;
|
|
2370
|
+
workArea: Rectangle;
|
|
2371
|
+
scaleFactor: number;
|
|
2372
|
+
isPrimary: boolean;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
export interface Point {
|
|
2376
|
+
x: number;
|
|
2377
|
+
y: number;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// Screen module for display and cursor information
|
|
2381
|
+
export const Screen = {
|
|
2382
|
+
/**
|
|
2383
|
+
* Get the primary display
|
|
2384
|
+
* @returns Display object for the primary monitor
|
|
2385
|
+
*/
|
|
2386
|
+
getPrimaryDisplay: (): Display => {
|
|
2387
|
+
const jsonStr = hasFFI ? core_.symbols.getPrimaryDisplay() : null;
|
|
2388
|
+
if (!jsonStr) {
|
|
2389
|
+
return {
|
|
2390
|
+
id: 0,
|
|
2391
|
+
bounds: { x: 0, y: 0, width: 0, height: 0 },
|
|
2392
|
+
workArea: { x: 0, y: 0, width: 0, height: 0 },
|
|
2393
|
+
scaleFactor: 1,
|
|
2394
|
+
isPrimary: true,
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
try {
|
|
2398
|
+
return JSON.parse(jsonStr.toString());
|
|
2399
|
+
} catch {
|
|
2400
|
+
return {
|
|
2401
|
+
id: 0,
|
|
2402
|
+
bounds: { x: 0, y: 0, width: 0, height: 0 },
|
|
2403
|
+
workArea: { x: 0, y: 0, width: 0, height: 0 },
|
|
2404
|
+
scaleFactor: 1,
|
|
2405
|
+
isPrimary: true,
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
},
|
|
2409
|
+
|
|
2410
|
+
/**
|
|
2411
|
+
* Get all connected displays
|
|
2412
|
+
* @returns Array of Display objects
|
|
2413
|
+
*/
|
|
2414
|
+
getAllDisplays: (): Display[] => {
|
|
2415
|
+
const jsonStr = hasFFI ? core_.symbols.getAllDisplays() : null;
|
|
2416
|
+
if (!jsonStr) {
|
|
2417
|
+
return [];
|
|
2418
|
+
}
|
|
2419
|
+
try {
|
|
2420
|
+
return JSON.parse(jsonStr.toString());
|
|
2421
|
+
} catch {
|
|
2422
|
+
return [];
|
|
2423
|
+
}
|
|
2424
|
+
},
|
|
2425
|
+
|
|
2426
|
+
/**
|
|
2427
|
+
* Get the current cursor position in screen coordinates
|
|
2428
|
+
* @returns Point with x and y coordinates
|
|
2429
|
+
*/
|
|
2430
|
+
getCursorScreenPoint: (): Point => {
|
|
2431
|
+
const jsonStr = hasFFI ? core_.symbols.getCursorScreenPoint() : null;
|
|
2432
|
+
if (!jsonStr) {
|
|
2433
|
+
return { x: 0, y: 0 };
|
|
2434
|
+
}
|
|
2435
|
+
try {
|
|
2436
|
+
return JSON.parse(jsonStr.toString());
|
|
2437
|
+
} catch {
|
|
2438
|
+
return { x: 0, y: 0 };
|
|
2439
|
+
}
|
|
2440
|
+
},
|
|
2441
|
+
|
|
2442
|
+
/**
|
|
2443
|
+
* Get current mouse button bitmask (bit 0 = left, bit 1 = right, bit 2 = middle)
|
|
2444
|
+
*/
|
|
2445
|
+
getMouseButtons: (): bigint => {
|
|
2446
|
+
try {
|
|
2447
|
+
return hasFFI ? core_.symbols.getMouseButtons() : BigInt(0);
|
|
2448
|
+
} catch {
|
|
2449
|
+
return 0n;
|
|
2450
|
+
}
|
|
2451
|
+
},
|
|
2452
|
+
};
|
|
2453
|
+
|
|
2454
|
+
// Types for Session/Cookie API
|
|
2455
|
+
export interface Cookie {
|
|
2456
|
+
name: string;
|
|
2457
|
+
value: string;
|
|
2458
|
+
domain?: string;
|
|
2459
|
+
path?: string;
|
|
2460
|
+
secure?: boolean;
|
|
2461
|
+
httpOnly?: boolean;
|
|
2462
|
+
sameSite?: "no_restriction" | "lax" | "strict";
|
|
2463
|
+
expirationDate?: number; // Unix timestamp in seconds
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
export interface CookieFilter {
|
|
2467
|
+
url?: string;
|
|
2468
|
+
name?: string;
|
|
2469
|
+
domain?: string;
|
|
2470
|
+
path?: string;
|
|
2471
|
+
secure?: boolean;
|
|
2472
|
+
session?: boolean;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
export type StorageType =
|
|
2476
|
+
| "cookies"
|
|
2477
|
+
| "localStorage"
|
|
2478
|
+
| "sessionStorage"
|
|
2479
|
+
| "indexedDB"
|
|
2480
|
+
| "webSQL"
|
|
2481
|
+
| "cache"
|
|
2482
|
+
| "all";
|
|
2483
|
+
|
|
2484
|
+
// Cookies API for a session
|
|
2485
|
+
class SessionCookies {
|
|
2486
|
+
private partitionId: string;
|
|
2487
|
+
|
|
2488
|
+
constructor(partitionId: string) {
|
|
2489
|
+
this.partitionId = partitionId;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
/**
|
|
2493
|
+
* Get cookies matching the filter criteria
|
|
2494
|
+
* @param filter - Optional filter to match cookies
|
|
2495
|
+
* @returns Array of matching cookies
|
|
2496
|
+
*/
|
|
2497
|
+
get(filter?: CookieFilter): Cookie[] {
|
|
2498
|
+
const filterJson = JSON.stringify(filter || {});
|
|
2499
|
+
const result = native_.symbols.sessionGetCookies(
|
|
2500
|
+
toCString(this.partitionId),
|
|
2501
|
+
toCString(filterJson),
|
|
2502
|
+
);
|
|
2503
|
+
if (!result) return [];
|
|
2504
|
+
try {
|
|
2505
|
+
return JSON.parse(result.toString());
|
|
2506
|
+
} catch {
|
|
2507
|
+
return [];
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
/**
|
|
2512
|
+
* Set a cookie
|
|
2513
|
+
* @param cookie - The cookie to set
|
|
2514
|
+
* @returns true if the cookie was set successfully
|
|
2515
|
+
*/
|
|
2516
|
+
set(cookie: Cookie): boolean {
|
|
2517
|
+
const cookieJson = JSON.stringify(cookie);
|
|
2518
|
+
return native_.symbols.sessionSetCookie(
|
|
2519
|
+
toCString(this.partitionId),
|
|
2520
|
+
toCString(cookieJson),
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
/**
|
|
2525
|
+
* Remove a specific cookie
|
|
2526
|
+
* @param url - The URL associated with the cookie
|
|
2527
|
+
* @param name - The name of the cookie
|
|
2528
|
+
* @returns true if the cookie was removed successfully
|
|
2529
|
+
*/
|
|
2530
|
+
remove(url: string, name: string): boolean {
|
|
2531
|
+
return native_.symbols.sessionRemoveCookie(
|
|
2532
|
+
toCString(this.partitionId),
|
|
2533
|
+
toCString(url),
|
|
2534
|
+
toCString(name),
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
/**
|
|
2539
|
+
* Clear all cookies for this session
|
|
2540
|
+
*/
|
|
2541
|
+
clear(): void {
|
|
2542
|
+
native_.symbols.sessionClearCookies(toCString(this.partitionId));
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// Session class representing a storage partition
|
|
2547
|
+
class SessionInstance {
|
|
2548
|
+
readonly partition: string;
|
|
2549
|
+
readonly cookies: SessionCookies;
|
|
2550
|
+
|
|
2551
|
+
constructor(partition: string) {
|
|
2552
|
+
this.partition = partition;
|
|
2553
|
+
this.cookies = new SessionCookies(partition);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
/**
|
|
2557
|
+
* Clear storage data for this session
|
|
2558
|
+
* @param types - Array of storage types to clear, or 'all' to clear everything
|
|
2559
|
+
*/
|
|
2560
|
+
clearStorageData(types: StorageType[] | "all" = "all"): void {
|
|
2561
|
+
const typesArray = types === "all" ? ["all"] : types;
|
|
2562
|
+
native_.symbols.sessionClearStorageData(
|
|
2563
|
+
toCString(this.partition),
|
|
2564
|
+
toCString(JSON.stringify(typesArray)),
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// Cache of session instances
|
|
2570
|
+
const sessionCache = new Map<string, SessionInstance>();
|
|
2571
|
+
|
|
2572
|
+
// Session module for storage/cookie management
|
|
2573
|
+
export const Session = {
|
|
2574
|
+
/**
|
|
2575
|
+
* Get or create a session for a given partition
|
|
2576
|
+
* @param partition - The partition identifier (e.g., "persist:myapp" or "ephemeral")
|
|
2577
|
+
* @returns Session instance for the partition
|
|
2578
|
+
*/
|
|
2579
|
+
fromPartition: (partition: string): SessionInstance => {
|
|
2580
|
+
let session = sessionCache.get(partition);
|
|
2581
|
+
if (!session) {
|
|
2582
|
+
session = new SessionInstance(partition);
|
|
2583
|
+
sessionCache.set(partition, session);
|
|
2584
|
+
}
|
|
2585
|
+
return session;
|
|
2586
|
+
},
|
|
2587
|
+
|
|
2588
|
+
/**
|
|
2589
|
+
* Get the default session (persist:default partition)
|
|
2590
|
+
*/
|
|
2591
|
+
get defaultSession(): SessionInstance {
|
|
2592
|
+
return Session.fromPartition("persist:default");
|
|
2593
|
+
},
|
|
2594
|
+
};
|
|
2595
|
+
|
|
2596
|
+
// Stub: native side accepts this parameter but never calls it.
|
|
2597
|
+
// Remove when native wrappers are recompiled without it.
|
|
2598
|
+
const webviewDecideNavigation = null;
|
|
2599
|
+
|
|
2600
|
+
const webviewEventHandler = (id: number, eventName: string, detail: string) => {
|
|
2601
|
+
BrowserView.ensureWrapped(id);
|
|
2602
|
+
|
|
2603
|
+
core_.symbols.dispatchHostWebviewEvent(
|
|
2604
|
+
id,
|
|
2605
|
+
toCString(eventName),
|
|
2606
|
+
toCString(detail),
|
|
2607
|
+
);
|
|
2608
|
+
|
|
2609
|
+
const eventMap: Record<string, string> = {
|
|
2610
|
+
"will-navigate": "willNavigate",
|
|
2611
|
+
"did-navigate": "didNavigate",
|
|
2612
|
+
"did-navigate-in-page": "didNavigateInPage",
|
|
2613
|
+
"did-commit-navigation": "didCommitNavigation",
|
|
2614
|
+
"dom-ready": "domReady",
|
|
2615
|
+
"new-window-open": "newWindowOpen",
|
|
2616
|
+
"host-message": "hostMessage",
|
|
2617
|
+
"download-started": "downloadStarted",
|
|
2618
|
+
"download-progress": "downloadProgress",
|
|
2619
|
+
"download-completed": "downloadCompleted",
|
|
2620
|
+
"download-failed": "downloadFailed",
|
|
2621
|
+
"load-started": "loadStarted",
|
|
2622
|
+
"load-committed": "loadCommitted",
|
|
2623
|
+
"load-finished": "loadFinished",
|
|
2624
|
+
};
|
|
2625
|
+
|
|
2626
|
+
const mappedName = eventMap[eventName];
|
|
2627
|
+
const handler = mappedName
|
|
2628
|
+
? (sparkBunEventEmitter.events.webview as Record<string, unknown>)[
|
|
2629
|
+
mappedName
|
|
2630
|
+
]
|
|
2631
|
+
: undefined;
|
|
2632
|
+
|
|
2633
|
+
if (!handler) {
|
|
2634
|
+
// console.error(
|
|
2635
|
+
// "[webviewEventHandler] No handler found for event:",
|
|
2636
|
+
// eventName,
|
|
2637
|
+
// "(mapped to:",
|
|
2638
|
+
// mappedName,
|
|
2639
|
+
// ")",
|
|
2640
|
+
// );
|
|
2641
|
+
return { success: false };
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
// Parse JSON data for events that send JSON
|
|
2645
|
+
let parsedDetail = detail;
|
|
2646
|
+
if (
|
|
2647
|
+
eventName === "new-window-open" ||
|
|
2648
|
+
eventName === "host-message" ||
|
|
2649
|
+
eventName === "download-started" ||
|
|
2650
|
+
eventName === "download-progress" ||
|
|
2651
|
+
eventName === "download-completed" ||
|
|
2652
|
+
eventName === "download-failed"
|
|
2653
|
+
) {
|
|
2654
|
+
try {
|
|
2655
|
+
parsedDetail = JSON.parse(detail);
|
|
2656
|
+
} catch (e) {
|
|
2657
|
+
console.error("[webviewEventHandler] Failed to parse JSON:", e);
|
|
2658
|
+
// Fallback to string if parsing fails (backward compatibility)
|
|
2659
|
+
parsedDetail = detail;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
const event = (
|
|
2664
|
+
handler as (data: { detail: string }) => SparkBunEvent<unknown, unknown>
|
|
2665
|
+
)({
|
|
2666
|
+
detail: parsedDetail,
|
|
2667
|
+
});
|
|
2668
|
+
|
|
2669
|
+
// global event
|
|
2670
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2671
|
+
sparkBunEventEmitter.emitEvent(event, id);
|
|
2672
|
+
};
|
|
2673
|
+
|
|
2674
|
+
const webviewEventJSCallback = new JSCallback(
|
|
2675
|
+
(id, _eventName, _detail) => {
|
|
2676
|
+
let eventName = "";
|
|
2677
|
+
let detail = "";
|
|
2678
|
+
|
|
2679
|
+
try {
|
|
2680
|
+
// Convert cstring pointers to actual strings
|
|
2681
|
+
eventName = new CString(_eventName).toString();
|
|
2682
|
+
detail = new CString(_detail).toString();
|
|
2683
|
+
} catch (err) {
|
|
2684
|
+
console.error("[webviewEventJSCallback] Error converting strings:", err);
|
|
2685
|
+
console.error("[webviewEventJSCallback] Raw values:", {
|
|
2686
|
+
_eventName,
|
|
2687
|
+
_detail,
|
|
2688
|
+
});
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
webviewEventHandler(id, eventName, detail);
|
|
2693
|
+
},
|
|
2694
|
+
{
|
|
2695
|
+
args: [FFIType.u32, FFIType.cstring, FFIType.cstring],
|
|
2696
|
+
returns: FFIType.void,
|
|
2697
|
+
threadsafe: true,
|
|
2698
|
+
},
|
|
2699
|
+
);
|
|
2700
|
+
|
|
2701
|
+
const hostBridgePostmessageHandler = new JSCallback(
|
|
2702
|
+
(id, msg) => {
|
|
2703
|
+
try {
|
|
2704
|
+
const msgStr = new CString(msg);
|
|
2705
|
+
|
|
2706
|
+
if (!msgStr.length) {
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
const rawMessage = msgStr.toString().trim();
|
|
2710
|
+
if (!rawMessage || (rawMessage[0] !== "{" && rawMessage[0] !== "[")) {
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
const msgJson = JSON.parse(rawMessage);
|
|
2714
|
+
|
|
2715
|
+
const webview = BrowserView.ensureWrapped(id);
|
|
2716
|
+
if (!webview) {
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
webview.rpcHandler?.(msgJson);
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
console.error("error sending message to host: ", err);
|
|
2723
|
+
}
|
|
2724
|
+
},
|
|
2725
|
+
{
|
|
2726
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
2727
|
+
returns: FFIType.void,
|
|
2728
|
+
threadsafe: true,
|
|
2729
|
+
},
|
|
2730
|
+
);
|
|
2731
|
+
|
|
2732
|
+
// internalRPC (bun <-> browser internal stuff)
|
|
2733
|
+
// BrowserView.rpc (user defined bun <-> browser rpc unique to each webview)
|
|
2734
|
+
// nativeRPC (internal bun <-> native rpc)
|
|
2735
|
+
|
|
2736
|
+
// eventBridgeHandler: handles ONLY webview events (dom-ready, navigation, etc.)
|
|
2737
|
+
// This is available on ALL webviews including sandboxed ones.
|
|
2738
|
+
// It cannot process RPC requests - only event emission.
|
|
2739
|
+
const eventBridgeHandler = new JSCallback(
|
|
2740
|
+
(_id: number, msg: number) => {
|
|
2741
|
+
try {
|
|
2742
|
+
const message = new CString(msg as unknown as Pointer);
|
|
2743
|
+
const rawMessage = message.toString().trim();
|
|
2744
|
+
if (!rawMessage || (rawMessage[0] !== "{" && rawMessage[0] !== "[")) {
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
const jsonMessage = JSON.parse(rawMessage);
|
|
2748
|
+
|
|
2749
|
+
// Only handle webviewEvent messages - no RPC
|
|
2750
|
+
if (jsonMessage.id === "webviewEvent") {
|
|
2751
|
+
const { payload } = jsonMessage;
|
|
2752
|
+
webviewEventHandler(payload.id, payload.eventName, payload.detail);
|
|
2753
|
+
}
|
|
2754
|
+
// Silently ignore any other message types - sandboxed webviews shouldn't send them
|
|
2755
|
+
} catch (err) {
|
|
2756
|
+
console.error("error in eventBridgeHandler: ", err);
|
|
2757
|
+
}
|
|
2758
|
+
},
|
|
2759
|
+
{
|
|
2760
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
2761
|
+
returns: FFIType.void,
|
|
2762
|
+
threadsafe: true,
|
|
2763
|
+
},
|
|
2764
|
+
);
|
|
2765
|
+
|
|
2766
|
+
// internalBridgeHandler: handles internal RPC (webview tags, drag regions, etc.)
|
|
2767
|
+
// This is only available on trusted (non-sandboxed) webviews.
|
|
2768
|
+
const internalBridgeHandler = new JSCallback(
|
|
2769
|
+
(_id: number, msg: number) => {
|
|
2770
|
+
try {
|
|
2771
|
+
const batchMessage = new CString(msg as unknown as Pointer);
|
|
2772
|
+
const jsonBatch = JSON.parse(batchMessage.toString());
|
|
2773
|
+
|
|
2774
|
+
if (jsonBatch.id === "webviewEvent") {
|
|
2775
|
+
// Note: Some WebviewEvents from inside the webview are routed through here
|
|
2776
|
+
// Others call the JSCallback directly from native code.
|
|
2777
|
+
const { payload } = jsonBatch;
|
|
2778
|
+
webviewEventHandler(payload.id, payload.eventName, payload.detail);
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
jsonBatch.forEach((msgStr: string) => {
|
|
2783
|
+
// if (!msgStr.length) {
|
|
2784
|
+
// console.error('WEBVIEW EVENT SENT TO WEBVIEW TAG BRIDGE HANDLER?', )
|
|
2785
|
+
// return;
|
|
2786
|
+
// }
|
|
2787
|
+
const msgJson = JSON.parse(msgStr);
|
|
2788
|
+
|
|
2789
|
+
if (msgJson.type === "message") {
|
|
2790
|
+
const handler = (
|
|
2791
|
+
internalRpcHandlers.message as Record<
|
|
2792
|
+
string,
|
|
2793
|
+
(params: unknown) => void
|
|
2794
|
+
>
|
|
2795
|
+
)[msgJson.id];
|
|
2796
|
+
handler?.(msgJson.payload);
|
|
2797
|
+
} else if (msgJson.type === "request") {
|
|
2798
|
+
const handler = (
|
|
2799
|
+
internalRpcHandlers.request as Record<
|
|
2800
|
+
string,
|
|
2801
|
+
(params: unknown) => unknown
|
|
2802
|
+
>
|
|
2803
|
+
)[msgJson.method];
|
|
2804
|
+
|
|
2805
|
+
const payload = handler?.(msgJson.params);
|
|
2806
|
+
|
|
2807
|
+
const resultObj = {
|
|
2808
|
+
type: "response",
|
|
2809
|
+
id: msgJson.id,
|
|
2810
|
+
success: true,
|
|
2811
|
+
payload,
|
|
2812
|
+
};
|
|
2813
|
+
core_.symbols.sendInternalMessageToWebview(
|
|
2814
|
+
msgJson.hostWebviewId,
|
|
2815
|
+
toCString(JSON.stringify(resultObj)),
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
});
|
|
2819
|
+
} catch (err) {
|
|
2820
|
+
console.error("error in internalBridgeHandler: ", err);
|
|
2821
|
+
// console.log('msgStr: ', id, new CString(msg));
|
|
2822
|
+
}
|
|
2823
|
+
},
|
|
2824
|
+
{
|
|
2825
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
2826
|
+
returns: FFIType.void,
|
|
2827
|
+
threadsafe: true,
|
|
2828
|
+
},
|
|
2829
|
+
);
|
|
2830
|
+
|
|
2831
|
+
const trayItemHandler = new JSCallback(
|
|
2832
|
+
(id, action) => {
|
|
2833
|
+
// Note: Some invisible character that doesn't appear in .length
|
|
2834
|
+
// is causing issues
|
|
2835
|
+
const actionString = (new CString(action).toString() || "").trim();
|
|
2836
|
+
|
|
2837
|
+
// Use shared deserialization method
|
|
2838
|
+
const { action: actualAction, data } = deserializeMenuAction(actionString);
|
|
2839
|
+
|
|
2840
|
+
const event = sparkBunEventEmitter.events.tray.trayClicked({
|
|
2841
|
+
id,
|
|
2842
|
+
action: actualAction,
|
|
2843
|
+
data, // Always include data property (undefined if no data)
|
|
2844
|
+
});
|
|
2845
|
+
|
|
2846
|
+
// global event
|
|
2847
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2848
|
+
sparkBunEventEmitter.emitEvent(event, id);
|
|
2849
|
+
},
|
|
2850
|
+
{
|
|
2851
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
2852
|
+
returns: FFIType.void,
|
|
2853
|
+
threadsafe: true,
|
|
2854
|
+
},
|
|
2855
|
+
);
|
|
2856
|
+
|
|
2857
|
+
const applicationMenuHandler = new JSCallback(
|
|
2858
|
+
(id, action) => {
|
|
2859
|
+
const actionString = new CString(action).toString();
|
|
2860
|
+
|
|
2861
|
+
// Use shared deserialization method
|
|
2862
|
+
const { action: actualAction, data } = deserializeMenuAction(actionString);
|
|
2863
|
+
|
|
2864
|
+
const event = sparkBunEventEmitter.events.app.applicationMenuClicked({
|
|
2865
|
+
id,
|
|
2866
|
+
action: actualAction,
|
|
2867
|
+
data, // Always include data property (undefined if no data)
|
|
2868
|
+
});
|
|
2869
|
+
|
|
2870
|
+
// global event
|
|
2871
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2872
|
+
},
|
|
2873
|
+
{
|
|
2874
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
2875
|
+
returns: FFIType.void,
|
|
2876
|
+
threadsafe: true,
|
|
2877
|
+
},
|
|
2878
|
+
);
|
|
2879
|
+
|
|
2880
|
+
const contextMenuHandler = new JSCallback(
|
|
2881
|
+
(_id, action) => {
|
|
2882
|
+
const actionString = new CString(action).toString();
|
|
2883
|
+
|
|
2884
|
+
// Use shared deserialization method
|
|
2885
|
+
const { action: actualAction, data } = deserializeMenuAction(actionString);
|
|
2886
|
+
|
|
2887
|
+
const event = sparkBunEventEmitter.events.app.contextMenuClicked({
|
|
2888
|
+
action: actualAction,
|
|
2889
|
+
data, // Always include data property (undefined if no data)
|
|
2890
|
+
});
|
|
2891
|
+
|
|
2892
|
+
sparkBunEventEmitter.emitEvent(event);
|
|
2893
|
+
},
|
|
2894
|
+
{
|
|
2895
|
+
args: [FFIType.u32, FFIType.cstring],
|
|
2896
|
+
returns: FFIType.void,
|
|
2897
|
+
threadsafe: true,
|
|
2898
|
+
},
|
|
2899
|
+
);
|
|
2900
|
+
|
|
2901
|
+
// Note: When passed over FFI JS will GC the buffer/pointer. Make sure to use strdup() or something
|
|
2902
|
+
// on the c side to duplicate the string so objc/c++ gc can own it
|
|
2903
|
+
export function toCString(
|
|
2904
|
+
jsString: string,
|
|
2905
|
+
addNullTerminator: boolean = true,
|
|
2906
|
+
): CString {
|
|
2907
|
+
let appendWith = "";
|
|
2908
|
+
|
|
2909
|
+
if (addNullTerminator && !jsString.endsWith("\0")) {
|
|
2910
|
+
appendWith = "\0";
|
|
2911
|
+
}
|
|
2912
|
+
const buff = Buffer.from(jsString + appendWith, "utf8");
|
|
2913
|
+
|
|
2914
|
+
// @ts-ignore - This is valid in Bun
|
|
2915
|
+
return ptr(buff);
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
type WebviewTagInitParams = {
|
|
2919
|
+
url: string | null;
|
|
2920
|
+
html: string | null;
|
|
2921
|
+
preload: string | null;
|
|
2922
|
+
renderer: "native" | "cef";
|
|
2923
|
+
partition: string | null;
|
|
2924
|
+
frame: { x: number; y: number; width: number; height: number };
|
|
2925
|
+
hostWebviewId: number;
|
|
2926
|
+
windowId: number;
|
|
2927
|
+
navigationRules: string | null;
|
|
2928
|
+
sandbox: boolean;
|
|
2929
|
+
transparent: boolean;
|
|
2930
|
+
passthrough: boolean;
|
|
2931
|
+
};
|
|
2932
|
+
|
|
2933
|
+
type WgpuTagInitParams = {
|
|
2934
|
+
windowId: number;
|
|
2935
|
+
frame: { x: number; y: number; width: number; height: number };
|
|
2936
|
+
transparent: boolean;
|
|
2937
|
+
passthrough: boolean;
|
|
2938
|
+
};
|
|
2939
|
+
|
|
2940
|
+
export const internalRpcHandlers = {
|
|
2941
|
+
request: {
|
|
2942
|
+
// todo: this shouldn't be getting method, just params.
|
|
2943
|
+
webviewTagInit: (params: WebviewTagInitParams) => {
|
|
2944
|
+
const {
|
|
2945
|
+
hostWebviewId,
|
|
2946
|
+
windowId,
|
|
2947
|
+
renderer,
|
|
2948
|
+
html,
|
|
2949
|
+
preload,
|
|
2950
|
+
partition,
|
|
2951
|
+
frame,
|
|
2952
|
+
navigationRules,
|
|
2953
|
+
sandbox,
|
|
2954
|
+
transparent,
|
|
2955
|
+
passthrough,
|
|
2956
|
+
} = params;
|
|
2957
|
+
|
|
2958
|
+
const url = !params.url && !html ? "https://sparkbun.dev" : params.url;
|
|
2959
|
+
|
|
2960
|
+
const webviewForTag = new BrowserView({
|
|
2961
|
+
url,
|
|
2962
|
+
html,
|
|
2963
|
+
preload,
|
|
2964
|
+
partition,
|
|
2965
|
+
frame,
|
|
2966
|
+
hostWebviewId,
|
|
2967
|
+
autoResize: false,
|
|
2968
|
+
windowId,
|
|
2969
|
+
renderer, //: "cef",
|
|
2970
|
+
navigationRules,
|
|
2971
|
+
sandbox,
|
|
2972
|
+
startTransparent: transparent,
|
|
2973
|
+
startPassthrough: passthrough,
|
|
2974
|
+
});
|
|
2975
|
+
|
|
2976
|
+
return webviewForTag.id;
|
|
2977
|
+
},
|
|
2978
|
+
wgpuTagInit: (params: WgpuTagInitParams) => {
|
|
2979
|
+
const {
|
|
2980
|
+
windowId,
|
|
2981
|
+
frame,
|
|
2982
|
+
transparent,
|
|
2983
|
+
passthrough,
|
|
2984
|
+
} = params;
|
|
2985
|
+
|
|
2986
|
+
const viewForTag = new WGPUView({
|
|
2987
|
+
windowId,
|
|
2988
|
+
frame,
|
|
2989
|
+
autoResize: false,
|
|
2990
|
+
startTransparent: transparent,
|
|
2991
|
+
startPassthrough: passthrough,
|
|
2992
|
+
});
|
|
2993
|
+
|
|
2994
|
+
return viewForTag.id;
|
|
2995
|
+
},
|
|
2996
|
+
webviewTagCanGoBack: (params: { id: number }) => {
|
|
2997
|
+
return core_.symbols.webviewCanGoBack(params.id);
|
|
2998
|
+
},
|
|
2999
|
+
webviewTagCanGoForward: (params: { id: number }) => {
|
|
3000
|
+
return core_.symbols.webviewCanGoForward(params.id);
|
|
3001
|
+
},
|
|
3002
|
+
},
|
|
3003
|
+
message: {
|
|
3004
|
+
webviewTagResize: (params: {
|
|
3005
|
+
id: number;
|
|
3006
|
+
frame: { x: number; y: number; width: number; height: number };
|
|
3007
|
+
masks: string;
|
|
3008
|
+
}) => {
|
|
3009
|
+
const { x, y, width, height } = params.frame;
|
|
3010
|
+
core_.symbols.resizeWebview(
|
|
3011
|
+
params.id,
|
|
3012
|
+
x,
|
|
3013
|
+
y,
|
|
3014
|
+
width,
|
|
3015
|
+
height,
|
|
3016
|
+
toCString(params.masks ?? "[]"),
|
|
3017
|
+
);
|
|
3018
|
+
},
|
|
3019
|
+
wgpuTagResize: (params: {
|
|
3020
|
+
id: number;
|
|
3021
|
+
frame: { x: number; y: number; width: number; height: number };
|
|
3022
|
+
masks: string;
|
|
3023
|
+
}) => {
|
|
3024
|
+
const view = WGPUView.getById(params.id);
|
|
3025
|
+
if (!view?.ptr) {
|
|
3026
|
+
console.error(
|
|
3027
|
+
`wgpuTagResize: WGPUView not found or has no ptr for id ${params.id}`,
|
|
3028
|
+
);
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
const { x, y, width, height } = params.frame;
|
|
3033
|
+
native_.symbols.resizeWebview(
|
|
3034
|
+
view.ptr,
|
|
3035
|
+
x,
|
|
3036
|
+
y,
|
|
3037
|
+
width,
|
|
3038
|
+
height,
|
|
3039
|
+
toCString(params.masks ?? "[]"),
|
|
3040
|
+
);
|
|
3041
|
+
},
|
|
3042
|
+
webviewTagUpdateSrc: (params: { id: number; url: string }) => {
|
|
3043
|
+
const webview = BrowserView.ensureWrapped(params.id);
|
|
3044
|
+
if (webview) {
|
|
3045
|
+
webview.url = params.url;
|
|
3046
|
+
}
|
|
3047
|
+
core_.symbols.loadURLInWebView(params.id, toCString(params.url));
|
|
3048
|
+
},
|
|
3049
|
+
webviewTagUpdateHtml: (params: { id: number; html: string }) => {
|
|
3050
|
+
const webview = BrowserView.ensureWrapped(params.id);
|
|
3051
|
+
if (!webview) {
|
|
3052
|
+
console.error(`webviewTagUpdateHtml: BrowserView not found for id ${params.id}`);
|
|
3053
|
+
return;
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
webview.loadHTML(params.html);
|
|
3057
|
+
webview.html = params.html;
|
|
3058
|
+
},
|
|
3059
|
+
webviewTagUpdatePreload: (params: { id: number; preload: string }) => {
|
|
3060
|
+
const webview = BrowserView.ensureWrapped(params.id);
|
|
3061
|
+
if (webview) {
|
|
3062
|
+
webview.preload = params.preload;
|
|
3063
|
+
}
|
|
3064
|
+
core_.symbols.updatePreloadScriptToWebView(
|
|
3065
|
+
params.id,
|
|
3066
|
+
toCString("sparkbun_custom_preload_script"),
|
|
3067
|
+
toCString(params.preload),
|
|
3068
|
+
true,
|
|
3069
|
+
);
|
|
3070
|
+
},
|
|
3071
|
+
webviewTagGoBack: (params: { id: number }) => {
|
|
3072
|
+
core_.symbols.webviewGoBack(params.id);
|
|
3073
|
+
},
|
|
3074
|
+
webviewTagGoForward: (params: { id: number }) => {
|
|
3075
|
+
core_.symbols.webviewGoForward(params.id);
|
|
3076
|
+
},
|
|
3077
|
+
webviewTagReload: (params: { id: number }) => {
|
|
3078
|
+
core_.symbols.webviewReload(params.id);
|
|
3079
|
+
},
|
|
3080
|
+
webviewTagRemove: (params: { id: number }) => {
|
|
3081
|
+
const webview = BrowserView.ensureWrapped(params.id);
|
|
3082
|
+
if (!webview) {
|
|
3083
|
+
console.error(`webviewTagRemove: BrowserView not found for id ${params.id}`);
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
webview.remove();
|
|
3087
|
+
},
|
|
3088
|
+
startWindowMove: (params: { id: number }) => {
|
|
3089
|
+
const windowPtr = getWindowPtr(params.id);
|
|
3090
|
+
if (!windowPtr) return;
|
|
3091
|
+
native_.symbols.startWindowMove(windowPtr);
|
|
3092
|
+
},
|
|
3093
|
+
stopWindowMove: (_params: unknown) => {
|
|
3094
|
+
native_.symbols.stopWindowMove();
|
|
3095
|
+
},
|
|
3096
|
+
webviewTagSetTransparent: (params: {
|
|
3097
|
+
id: number;
|
|
3098
|
+
transparent: boolean;
|
|
3099
|
+
}) => {
|
|
3100
|
+
core_.symbols.webviewSetTransparent(params.id, params.transparent);
|
|
3101
|
+
},
|
|
3102
|
+
wgpuTagSetTransparent: (params: {
|
|
3103
|
+
id: number;
|
|
3104
|
+
transparent: boolean;
|
|
3105
|
+
}) => {
|
|
3106
|
+
const view = WGPUView.getById(params.id);
|
|
3107
|
+
if (!view?.ptr) {
|
|
3108
|
+
console.error(
|
|
3109
|
+
`wgpuTagSetTransparent: WGPUView not found or has no ptr for id ${params.id}`,
|
|
3110
|
+
);
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
native_.symbols.wgpuViewSetTransparent(view.ptr, params.transparent);
|
|
3114
|
+
},
|
|
3115
|
+
webviewTagSetPassthrough: (params: {
|
|
3116
|
+
id: number;
|
|
3117
|
+
enablePassthrough: boolean;
|
|
3118
|
+
}) => {
|
|
3119
|
+
core_.symbols.webviewSetPassthrough(params.id, params.enablePassthrough);
|
|
3120
|
+
},
|
|
3121
|
+
wgpuTagSetPassthrough: (params: { id: number; passthrough: boolean }) => {
|
|
3122
|
+
const view = WGPUView.getById(params.id);
|
|
3123
|
+
if (!view?.ptr) {
|
|
3124
|
+
console.error(
|
|
3125
|
+
`wgpuTagSetPassthrough: WGPUView not found or has no ptr for id ${params.id}`,
|
|
3126
|
+
);
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
native_.symbols.wgpuViewSetPassthrough(view.ptr, params.passthrough);
|
|
3130
|
+
},
|
|
3131
|
+
webviewTagSetHidden: (params: { id: number; hidden: boolean }) => {
|
|
3132
|
+
core_.symbols.webviewSetHidden(params.id, params.hidden);
|
|
3133
|
+
},
|
|
3134
|
+
wgpuTagSetHidden: (params: { id: number; hidden: boolean }) => {
|
|
3135
|
+
const view = WGPUView.getById(params.id);
|
|
3136
|
+
if (!view?.ptr) {
|
|
3137
|
+
console.error(
|
|
3138
|
+
`wgpuTagSetHidden: WGPUView not found or has no ptr for id ${params.id}`,
|
|
3139
|
+
);
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
native_.symbols.wgpuViewSetHidden(view.ptr, params.hidden);
|
|
3143
|
+
},
|
|
3144
|
+
wgpuTagRemove: (params: { id: number }) => {
|
|
3145
|
+
const view = WGPUView.getById(params.id);
|
|
3146
|
+
if (!view?.ptr) {
|
|
3147
|
+
console.error(
|
|
3148
|
+
`wgpuTagRemove: WGPUView not found or has no ptr for id ${params.id}`,
|
|
3149
|
+
);
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
view.remove();
|
|
3153
|
+
},
|
|
3154
|
+
wgpuTagRunTest: (params: { id: number }) => {
|
|
3155
|
+
const view = WGPUView.getById(params.id);
|
|
3156
|
+
if (!view?.ptr) {
|
|
3157
|
+
console.error(
|
|
3158
|
+
`wgpuTagRunTest: WGPUView not found or has no ptr for id ${params.id}`,
|
|
3159
|
+
);
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
if (!native?.symbols?.wgpuRunGPUTest) {
|
|
3163
|
+
console.error("wgpuTagRunTest: wgpuRunGPUTest not available");
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
native_.symbols.wgpuRunGPUTest(view.ptr);
|
|
3167
|
+
},
|
|
3168
|
+
webviewTagSetNavigationRules: (params: { id: number; rules: string[] }) => {
|
|
3169
|
+
const rulesJson = JSON.stringify(params.rules);
|
|
3170
|
+
const webview = BrowserView.ensureWrapped(params.id);
|
|
3171
|
+
if (webview) {
|
|
3172
|
+
webview.navigationRules = rulesJson;
|
|
3173
|
+
}
|
|
3174
|
+
core_.symbols.setWebviewNavigationRules(params.id, toCString(rulesJson));
|
|
3175
|
+
},
|
|
3176
|
+
webviewTagFindInPage: (params: {
|
|
3177
|
+
id: number;
|
|
3178
|
+
searchText: string;
|
|
3179
|
+
forward: boolean;
|
|
3180
|
+
matchCase: boolean;
|
|
3181
|
+
}) => {
|
|
3182
|
+
core_.symbols.webviewFindInPage(
|
|
3183
|
+
params.id,
|
|
3184
|
+
toCString(params.searchText),
|
|
3185
|
+
params.forward,
|
|
3186
|
+
params.matchCase,
|
|
3187
|
+
);
|
|
3188
|
+
},
|
|
3189
|
+
webviewTagStopFind: (params: { id: number }) => {
|
|
3190
|
+
core_.symbols.webviewStopFind(params.id);
|
|
3191
|
+
},
|
|
3192
|
+
webviewTagOpenDevTools: (params: { id: number }) => {
|
|
3193
|
+
core_.symbols.webviewOpenDevTools(params.id);
|
|
3194
|
+
},
|
|
3195
|
+
webviewTagCloseDevTools: (params: { id: number }) => {
|
|
3196
|
+
core_.symbols.webviewCloseDevTools(params.id);
|
|
3197
|
+
},
|
|
3198
|
+
webviewTagToggleDevTools: (params: { id: number }) => {
|
|
3199
|
+
core_.symbols.webviewToggleDevTools(params.id);
|
|
3200
|
+
},
|
|
3201
|
+
webviewTagExecuteJavascript: (params: { id: number; js: string }) => {
|
|
3202
|
+
core_.symbols.evaluateJavaScriptWithNoCompletion(
|
|
3203
|
+
params.id,
|
|
3204
|
+
toCString(params.js),
|
|
3205
|
+
);
|
|
3206
|
+
},
|
|
3207
|
+
webviewEvent: (params: unknown) => {
|
|
3208
|
+
console.log("-----------------+webviewEvent", params);
|
|
3209
|
+
},
|
|
3210
|
+
},
|
|
3211
|
+
};
|
|
3212
|
+
|
|
3213
|
+
// todo: consider renaming to TrayMenuItemConfig
|
|
3214
|
+
export type MenuItemConfig =
|
|
3215
|
+
| { type: "divider" | "separator" }
|
|
3216
|
+
| {
|
|
3217
|
+
type: "normal";
|
|
3218
|
+
label: string;
|
|
3219
|
+
tooltip?: string;
|
|
3220
|
+
action?: string;
|
|
3221
|
+
data?: any;
|
|
3222
|
+
submenu?: Array<MenuItemConfig>;
|
|
3223
|
+
enabled?: boolean;
|
|
3224
|
+
checked?: boolean;
|
|
3225
|
+
hidden?: boolean;
|
|
3226
|
+
};
|
|
3227
|
+
|
|
3228
|
+
export type ApplicationMenuItemConfig =
|
|
3229
|
+
| { type: "divider" | "separator" }
|
|
3230
|
+
| {
|
|
3231
|
+
type?: "normal";
|
|
3232
|
+
label: string;
|
|
3233
|
+
tooltip?: string;
|
|
3234
|
+
action?: string;
|
|
3235
|
+
data?: any;
|
|
3236
|
+
submenu?: Array<ApplicationMenuItemConfig>;
|
|
3237
|
+
enabled?: boolean;
|
|
3238
|
+
checked?: boolean;
|
|
3239
|
+
hidden?: boolean;
|
|
3240
|
+
accelerator?: string;
|
|
3241
|
+
}
|
|
3242
|
+
| {
|
|
3243
|
+
type?: "normal";
|
|
3244
|
+
label?: string;
|
|
3245
|
+
tooltip?: string;
|
|
3246
|
+
role?: string;
|
|
3247
|
+
data?: any;
|
|
3248
|
+
submenu?: Array<ApplicationMenuItemConfig>;
|
|
3249
|
+
enabled?: boolean;
|
|
3250
|
+
checked?: boolean;
|
|
3251
|
+
hidden?: boolean;
|
|
3252
|
+
accelerator?: string;
|
|
3253
|
+
};
|