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,487 @@
|
|
|
1
|
+
import { ffi, native } from "../proc/native";
|
|
2
|
+
import { sparkBunEventEmitter } from "../events/eventEmitter";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { OS } from "../../shared/platform";
|
|
7
|
+
|
|
8
|
+
export const moveToTrash = (path: string) => {
|
|
9
|
+
return ffi.request.moveToTrash({ path });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const showItemInFolder = (path: string) => {
|
|
13
|
+
return ffi.request.showItemInFolder({ path });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Open a URL in the default browser or appropriate application.
|
|
18
|
+
* Works with http/https URLs, mailto: links, custom URL schemes, etc.
|
|
19
|
+
*
|
|
20
|
+
* @param url - The URL to open (e.g., "https://example.com", "mailto:test@example.com")
|
|
21
|
+
* @returns true if the URL was opened successfully, false otherwise
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Open a website
|
|
25
|
+
* openExternal("https://example.com");
|
|
26
|
+
*
|
|
27
|
+
* // Open an email
|
|
28
|
+
* openExternal("mailto:support@example.com?subject=Help");
|
|
29
|
+
*
|
|
30
|
+
* // Open a custom URL scheme
|
|
31
|
+
* openExternal("slack://open");
|
|
32
|
+
*/
|
|
33
|
+
export const openExternal = (url: string): boolean => {
|
|
34
|
+
return ffi.request.openExternal({ url });
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Open a file or folder with the default application.
|
|
39
|
+
* For files, opens with the associated application (e.g., .pdf with PDF reader).
|
|
40
|
+
* For folders, opens in the file manager.
|
|
41
|
+
*
|
|
42
|
+
* @param path - The absolute path to the file or folder
|
|
43
|
+
* @returns true if the path was opened successfully, false otherwise
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // Open a document with default app
|
|
47
|
+
* openPath("/Users/me/Documents/report.pdf");
|
|
48
|
+
*
|
|
49
|
+
* // Open a folder in file manager
|
|
50
|
+
* openPath("/Users/me/Downloads");
|
|
51
|
+
*/
|
|
52
|
+
export const openPath = (path: string): boolean => {
|
|
53
|
+
return ffi.request.openPath({ path });
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const setDockIconVisible = (visible: boolean): void => {
|
|
57
|
+
ffi.request.setDockIconVisible({ visible });
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const isDockIconVisible = (): boolean => {
|
|
61
|
+
return ffi.request.isDockIconVisible();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type NotificationOptions = {
|
|
65
|
+
/**
|
|
66
|
+
* The title of the notification (required)
|
|
67
|
+
*/
|
|
68
|
+
title: string;
|
|
69
|
+
/**
|
|
70
|
+
* The main body text of the notification
|
|
71
|
+
*/
|
|
72
|
+
body?: string;
|
|
73
|
+
/**
|
|
74
|
+
* A subtitle displayed below the title (macOS only, shown as additional line on other platforms)
|
|
75
|
+
*/
|
|
76
|
+
subtitle?: string;
|
|
77
|
+
/**
|
|
78
|
+
* If true, the notification will not play a sound
|
|
79
|
+
*/
|
|
80
|
+
silent?: boolean;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Show a native desktop notification.
|
|
85
|
+
*
|
|
86
|
+
* @param options - Notification options
|
|
87
|
+
* @param options.title - The title of the notification (required)
|
|
88
|
+
* @param options.body - The main body text
|
|
89
|
+
* @param options.subtitle - A subtitle (macOS shows this between title and body)
|
|
90
|
+
* @param options.silent - If true, no sound will be played
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // Simple notification
|
|
94
|
+
* showNotification({ title: "Download Complete" });
|
|
95
|
+
*
|
|
96
|
+
* // Notification with body
|
|
97
|
+
* showNotification({
|
|
98
|
+
* title: "New Message",
|
|
99
|
+
* body: "You have a new message from John"
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* // Full notification
|
|
103
|
+
* showNotification({
|
|
104
|
+
* title: "Reminder",
|
|
105
|
+
* subtitle: "Calendar Event",
|
|
106
|
+
* body: "Team meeting in 15 minutes",
|
|
107
|
+
* silent: false
|
|
108
|
+
* });
|
|
109
|
+
*
|
|
110
|
+
* // Silent notification
|
|
111
|
+
* showNotification({
|
|
112
|
+
* title: "Sync Complete",
|
|
113
|
+
* body: "All files have been synchronized",
|
|
114
|
+
* silent: true
|
|
115
|
+
* });
|
|
116
|
+
*/
|
|
117
|
+
export const showNotification = (options: NotificationOptions): void => {
|
|
118
|
+
const { title, body, subtitle, silent } = options;
|
|
119
|
+
ffi.request.showNotification({ title, body, subtitle, silent });
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
let isQuitting = false;
|
|
123
|
+
|
|
124
|
+
export const quit = () => {
|
|
125
|
+
if (isQuitting) return;
|
|
126
|
+
isQuitting = true;
|
|
127
|
+
|
|
128
|
+
const beforeQuitEvent = sparkBunEventEmitter.events.app.beforeQuit({});
|
|
129
|
+
sparkBunEventEmitter.emitEvent(beforeQuitEvent);
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
beforeQuitEvent.responseWasSet &&
|
|
133
|
+
beforeQuitEvent.response?.allow === false
|
|
134
|
+
) {
|
|
135
|
+
isQuitting = false;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (native) {
|
|
140
|
+
ffi.request.quitGracefully({ code: 0, timeoutMs: 5000 });
|
|
141
|
+
} else {
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Override process.exit so that calling it triggers proper native cleanup
|
|
147
|
+
const _originalProcessExit = process.exit;
|
|
148
|
+
process.exit = ((code?: number) => {
|
|
149
|
+
if (native) {
|
|
150
|
+
if (isQuitting) {
|
|
151
|
+
ffi.request.quitGracefully({ code: code ?? 0, timeoutMs: 0 });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
quit();
|
|
155
|
+
} else {
|
|
156
|
+
_originalProcessExit(code ?? 0);
|
|
157
|
+
}
|
|
158
|
+
}) as typeof process.exit;
|
|
159
|
+
|
|
160
|
+
export const openFileDialog = async (
|
|
161
|
+
opts: {
|
|
162
|
+
startingFolder?: string;
|
|
163
|
+
allowedFileTypes?: string;
|
|
164
|
+
canChooseFiles?: boolean;
|
|
165
|
+
canChooseDirectory?: boolean;
|
|
166
|
+
allowsMultipleSelection?: boolean;
|
|
167
|
+
} = {},
|
|
168
|
+
): Promise<string[]> => {
|
|
169
|
+
const optsWithDefault = {
|
|
170
|
+
...{
|
|
171
|
+
startingFolder: "~/",
|
|
172
|
+
allowedFileTypes: "*",
|
|
173
|
+
canChooseFiles: true,
|
|
174
|
+
canChooseDirectory: true,
|
|
175
|
+
allowsMultipleSelection: true,
|
|
176
|
+
},
|
|
177
|
+
...opts,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const result = await ffi.request.openFileDialog({
|
|
181
|
+
startingFolder: optsWithDefault.startingFolder,
|
|
182
|
+
allowedFileTypes: optsWithDefault.allowedFileTypes,
|
|
183
|
+
canChooseFiles: optsWithDefault.canChooseFiles,
|
|
184
|
+
canChooseDirectory: optsWithDefault.canChooseDirectory,
|
|
185
|
+
allowsMultipleSelection: optsWithDefault.allowsMultipleSelection,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const filePaths = result.split(",");
|
|
189
|
+
return filePaths;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export type MessageBoxOptions = {
|
|
193
|
+
type?: "info" | "warning" | "error" | "question";
|
|
194
|
+
title?: string;
|
|
195
|
+
message?: string;
|
|
196
|
+
detail?: string;
|
|
197
|
+
buttons?: string[];
|
|
198
|
+
defaultId?: number;
|
|
199
|
+
cancelId?: number;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export type MessageBoxResponse = {
|
|
203
|
+
response: number; // Index of the clicked button
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Shows a message box dialog and returns which button was clicked.
|
|
208
|
+
* Similar to Electron's dialog.showMessageBox()
|
|
209
|
+
*
|
|
210
|
+
* @param opts - Options for the message box
|
|
211
|
+
* @param opts.type - The type of dialog: "info", "warning", "error", or "question"
|
|
212
|
+
* @param opts.title - The title of the dialog window
|
|
213
|
+
* @param opts.message - The main message to display
|
|
214
|
+
* @param opts.detail - Additional detail text (displayed smaller on some platforms)
|
|
215
|
+
* @param opts.buttons - Array of button labels (e.g., ["OK", "Cancel"])
|
|
216
|
+
* @param opts.defaultId - Index of the default button (focused on open)
|
|
217
|
+
* @param opts.cancelId - Index of the button to trigger on Escape key or dialog close
|
|
218
|
+
* @returns Promise resolving to an object with `response` (0-based button index clicked)
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* const { response } = await showMessageBox({
|
|
222
|
+
* type: "question",
|
|
223
|
+
* title: "Confirm",
|
|
224
|
+
* message: "Are you sure you want to delete this file?",
|
|
225
|
+
* buttons: ["Delete", "Cancel"],
|
|
226
|
+
* defaultId: 1,
|
|
227
|
+
* cancelId: 1
|
|
228
|
+
* });
|
|
229
|
+
* if (response === 0) {
|
|
230
|
+
* // User clicked Delete
|
|
231
|
+
* }
|
|
232
|
+
*/
|
|
233
|
+
export const showMessageBox = async (
|
|
234
|
+
opts: MessageBoxOptions = {},
|
|
235
|
+
): Promise<MessageBoxResponse> => {
|
|
236
|
+
const {
|
|
237
|
+
type = "info",
|
|
238
|
+
title = "",
|
|
239
|
+
message = "",
|
|
240
|
+
detail = "",
|
|
241
|
+
buttons = ["OK"],
|
|
242
|
+
defaultId = 0,
|
|
243
|
+
cancelId = -1,
|
|
244
|
+
} = opts;
|
|
245
|
+
|
|
246
|
+
const response = ffi.request.showMessageBox({
|
|
247
|
+
type,
|
|
248
|
+
title,
|
|
249
|
+
message,
|
|
250
|
+
detail,
|
|
251
|
+
buttons,
|
|
252
|
+
defaultId,
|
|
253
|
+
cancelId,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return { response };
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// Clipboard API
|
|
261
|
+
// ============================================================================
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Read text from the system clipboard.
|
|
265
|
+
* @returns The clipboard text, or null if no text is available
|
|
266
|
+
*/
|
|
267
|
+
export const clipboardReadText = (): string | null => {
|
|
268
|
+
return ffi.request.clipboardReadText();
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Write text to the system clipboard.
|
|
273
|
+
* @param text - The text to write to the clipboard
|
|
274
|
+
*/
|
|
275
|
+
export const clipboardWriteText = (text: string): void => {
|
|
276
|
+
ffi.request.clipboardWriteText({ text });
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Read image from the system clipboard as PNG data.
|
|
281
|
+
* @returns PNG image data as Uint8Array, or null if no image is available
|
|
282
|
+
*/
|
|
283
|
+
export const clipboardReadImage = (): Uint8Array | null => {
|
|
284
|
+
return ffi.request.clipboardReadImage();
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Write PNG image data to the system clipboard.
|
|
289
|
+
* @param pngData - PNG image data as Uint8Array
|
|
290
|
+
*/
|
|
291
|
+
export const clipboardWriteImage = (pngData: Uint8Array): void => {
|
|
292
|
+
ffi.request.clipboardWriteImage({ pngData });
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Clear the system clipboard.
|
|
297
|
+
*/
|
|
298
|
+
export const clipboardClear = (): void => {
|
|
299
|
+
ffi.request.clipboardClear();
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get the available formats in the clipboard.
|
|
304
|
+
* @returns Array of format names (e.g., ["text", "image", "files", "html"])
|
|
305
|
+
*/
|
|
306
|
+
export const clipboardAvailableFormats = (): string[] => {
|
|
307
|
+
return ffi.request.clipboardAvailableFormats();
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// ============================================================================
|
|
311
|
+
// Paths API — cross-platform OS directories and app-scoped directories
|
|
312
|
+
// ============================================================================
|
|
313
|
+
|
|
314
|
+
const home = homedir();
|
|
315
|
+
|
|
316
|
+
function getLinuxXdgUserDirs(): Record<string, string> {
|
|
317
|
+
try {
|
|
318
|
+
const content = readFileSync(
|
|
319
|
+
join(home, ".config", "user-dirs.dirs"),
|
|
320
|
+
"utf-8",
|
|
321
|
+
);
|
|
322
|
+
const dirs: Record<string, string> = {};
|
|
323
|
+
for (const line of content.split("\n")) {
|
|
324
|
+
const trimmed = line.trim();
|
|
325
|
+
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
326
|
+
const eqIdx = trimmed.indexOf("=");
|
|
327
|
+
const key = trimmed.slice(0, eqIdx);
|
|
328
|
+
let value = trimmed.slice(eqIdx + 1);
|
|
329
|
+
// Strip surrounding quotes
|
|
330
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
331
|
+
value = value.slice(1, -1);
|
|
332
|
+
}
|
|
333
|
+
// Substitute $HOME
|
|
334
|
+
value = value.replace(/\$HOME/g, home);
|
|
335
|
+
dirs[key] = value;
|
|
336
|
+
}
|
|
337
|
+
return dirs;
|
|
338
|
+
} catch {
|
|
339
|
+
return {};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let _xdgUserDirs: Record<string, string> | undefined;
|
|
344
|
+
function xdgUserDir(key: string, fallbackName: string): string {
|
|
345
|
+
if (OS !== "linux") return "";
|
|
346
|
+
if (!_xdgUserDirs) _xdgUserDirs = getLinuxXdgUserDirs();
|
|
347
|
+
return _xdgUserDirs[key] || join(home, fallbackName);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let _versionInfo: { identifier: string; channel: string } | undefined;
|
|
351
|
+
function getVersionInfo(): { identifier: string; channel: string } {
|
|
352
|
+
if (_versionInfo) return _versionInfo;
|
|
353
|
+
try {
|
|
354
|
+
const resourcesDir = "Resources";
|
|
355
|
+
const raw = readFileSync(join("..", resourcesDir, "version.json"), "utf-8");
|
|
356
|
+
const parsed = JSON.parse(raw);
|
|
357
|
+
_versionInfo = { identifier: parsed.identifier, channel: parsed.channel };
|
|
358
|
+
return _versionInfo;
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error("Failed to read version.json", error);
|
|
361
|
+
_versionInfo = { identifier: "", channel: "" };
|
|
362
|
+
return _versionInfo;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function getAppDataDir(): string {
|
|
367
|
+
switch (OS) {
|
|
368
|
+
case "macos":
|
|
369
|
+
return join(home, "Library", "Application Support");
|
|
370
|
+
case "win":
|
|
371
|
+
return process.env["LOCALAPPDATA"] || join(home, "AppData", "Local");
|
|
372
|
+
case "linux":
|
|
373
|
+
return process.env["XDG_DATA_HOME"] || join(home, ".local", "share");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function getCacheDir(): string {
|
|
378
|
+
switch (OS) {
|
|
379
|
+
case "macos":
|
|
380
|
+
return join(home, "Library", "Caches");
|
|
381
|
+
case "win":
|
|
382
|
+
return process.env["LOCALAPPDATA"] || join(home, "AppData", "Local");
|
|
383
|
+
case "linux":
|
|
384
|
+
return process.env["XDG_CACHE_HOME"] || join(home, ".cache");
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function getLogsDir(): string {
|
|
389
|
+
switch (OS) {
|
|
390
|
+
case "macos":
|
|
391
|
+
return join(home, "Library", "Logs");
|
|
392
|
+
case "win":
|
|
393
|
+
return process.env["LOCALAPPDATA"] || join(home, "AppData", "Local");
|
|
394
|
+
case "linux":
|
|
395
|
+
return process.env["XDG_STATE_HOME"] || join(home, ".local", "state");
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function getConfigDir(): string {
|
|
400
|
+
switch (OS) {
|
|
401
|
+
case "macos":
|
|
402
|
+
return join(home, "Library", "Application Support");
|
|
403
|
+
case "win":
|
|
404
|
+
return process.env["APPDATA"] || join(home, "AppData", "Roaming");
|
|
405
|
+
case "linux":
|
|
406
|
+
return process.env["XDG_CONFIG_HOME"] || join(home, ".config");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function getUserDir(
|
|
411
|
+
macName: string,
|
|
412
|
+
winName: string,
|
|
413
|
+
xdgKey: string,
|
|
414
|
+
fallbackName: string,
|
|
415
|
+
): string {
|
|
416
|
+
switch (OS) {
|
|
417
|
+
case "macos":
|
|
418
|
+
return join(home, macName);
|
|
419
|
+
case "win": {
|
|
420
|
+
const userProfile = process.env["USERPROFILE"] || home;
|
|
421
|
+
return join(userProfile, winName);
|
|
422
|
+
}
|
|
423
|
+
case "linux":
|
|
424
|
+
return xdgUserDir(xdgKey, fallbackName);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export const paths = {
|
|
429
|
+
get home(): string {
|
|
430
|
+
return home;
|
|
431
|
+
},
|
|
432
|
+
get appData(): string {
|
|
433
|
+
return getAppDataDir();
|
|
434
|
+
},
|
|
435
|
+
get config(): string {
|
|
436
|
+
return getConfigDir();
|
|
437
|
+
},
|
|
438
|
+
get cache(): string {
|
|
439
|
+
return getCacheDir();
|
|
440
|
+
},
|
|
441
|
+
get temp(): string {
|
|
442
|
+
return tmpdir();
|
|
443
|
+
},
|
|
444
|
+
get logs(): string {
|
|
445
|
+
return getLogsDir();
|
|
446
|
+
},
|
|
447
|
+
get documents(): string {
|
|
448
|
+
return getUserDir(
|
|
449
|
+
"Documents",
|
|
450
|
+
"Documents",
|
|
451
|
+
"XDG_DOCUMENTS_DIR",
|
|
452
|
+
"Documents",
|
|
453
|
+
);
|
|
454
|
+
},
|
|
455
|
+
get downloads(): string {
|
|
456
|
+
return getUserDir(
|
|
457
|
+
"Downloads",
|
|
458
|
+
"Downloads",
|
|
459
|
+
"XDG_DOWNLOAD_DIR",
|
|
460
|
+
"Downloads",
|
|
461
|
+
);
|
|
462
|
+
},
|
|
463
|
+
get desktop(): string {
|
|
464
|
+
return getUserDir("Desktop", "Desktop", "XDG_DESKTOP_DIR", "Desktop");
|
|
465
|
+
},
|
|
466
|
+
get pictures(): string {
|
|
467
|
+
return getUserDir("Pictures", "Pictures", "XDG_PICTURES_DIR", "Pictures");
|
|
468
|
+
},
|
|
469
|
+
get music(): string {
|
|
470
|
+
return getUserDir("Music", "Music", "XDG_MUSIC_DIR", "Music");
|
|
471
|
+
},
|
|
472
|
+
get videos(): string {
|
|
473
|
+
return getUserDir("Movies", "Videos", "XDG_VIDEOS_DIR", "Videos");
|
|
474
|
+
},
|
|
475
|
+
get userData(): string {
|
|
476
|
+
const { identifier, channel } = getVersionInfo();
|
|
477
|
+
return join(getAppDataDir(), identifier, channel);
|
|
478
|
+
},
|
|
479
|
+
get userCache(): string {
|
|
480
|
+
const { identifier, channel } = getVersionInfo();
|
|
481
|
+
return join(getCacheDir(), identifier, channel);
|
|
482
|
+
},
|
|
483
|
+
get userLogs(): string {
|
|
484
|
+
const { identifier, channel } = getVersionInfo();
|
|
485
|
+
return join(getLogsDir(), identifier, channel);
|
|
486
|
+
},
|
|
487
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { ffi } from "../proc/native";
|
|
2
|
+
import sparkBunEventEmitter from "../events/eventEmitter";
|
|
3
|
+
import { type Pointer } from "bun:ffi";
|
|
4
|
+
|
|
5
|
+
const WGPUViewMap: {
|
|
6
|
+
[id: number]: WGPUView;
|
|
7
|
+
} = {};
|
|
8
|
+
|
|
9
|
+
export type WGPUViewOptions = {
|
|
10
|
+
frame: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
autoResize: boolean;
|
|
17
|
+
windowId: number;
|
|
18
|
+
startTransparent: boolean;
|
|
19
|
+
startPassthrough: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const defaultOptions: Partial<WGPUViewOptions> = {
|
|
23
|
+
frame: {
|
|
24
|
+
x: 0,
|
|
25
|
+
y: 0,
|
|
26
|
+
width: 800,
|
|
27
|
+
height: 600,
|
|
28
|
+
},
|
|
29
|
+
autoResize: true,
|
|
30
|
+
startTransparent: false,
|
|
31
|
+
startPassthrough: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class WGPUView {
|
|
35
|
+
id: number = 0;
|
|
36
|
+
windowId!: number;
|
|
37
|
+
autoResize: boolean = true;
|
|
38
|
+
frame: {
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
} = {
|
|
44
|
+
x: 0,
|
|
45
|
+
y: 0,
|
|
46
|
+
width: 800,
|
|
47
|
+
height: 600,
|
|
48
|
+
};
|
|
49
|
+
startTransparent: boolean = false;
|
|
50
|
+
startPassthrough: boolean = false;
|
|
51
|
+
isRemoved: boolean = false;
|
|
52
|
+
|
|
53
|
+
get ptr(): Pointer | null {
|
|
54
|
+
if (this.isRemoved) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return ffi.request.getWGPUViewPointer({ id: this.id }) as Pointer | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
constructor(options: Partial<WGPUViewOptions> = defaultOptions) {
|
|
61
|
+
this.frame = {
|
|
62
|
+
x: options.frame?.x ?? defaultOptions.frame!.x,
|
|
63
|
+
y: options.frame?.y ?? defaultOptions.frame!.y,
|
|
64
|
+
width: options.frame?.width ?? defaultOptions.frame!.width,
|
|
65
|
+
height: options.frame?.height ?? defaultOptions.frame!.height,
|
|
66
|
+
};
|
|
67
|
+
this.windowId = options.windowId ?? 0;
|
|
68
|
+
this.autoResize = options.autoResize === false ? false : true;
|
|
69
|
+
this.startTransparent = options.startTransparent ?? false;
|
|
70
|
+
this.startPassthrough = options.startPassthrough ?? false;
|
|
71
|
+
|
|
72
|
+
this.id = this.init() as number;
|
|
73
|
+
WGPUViewMap[this.id] = this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
init() {
|
|
77
|
+
return ffi.request.createWGPUView({
|
|
78
|
+
windowId: this.windowId,
|
|
79
|
+
frame: {
|
|
80
|
+
width: this.frame.width,
|
|
81
|
+
height: this.frame.height,
|
|
82
|
+
x: this.frame.x,
|
|
83
|
+
y: this.frame.y,
|
|
84
|
+
},
|
|
85
|
+
autoResize: this.autoResize,
|
|
86
|
+
startTransparent: this.startTransparent,
|
|
87
|
+
startPassthrough: this.startPassthrough,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setFrame(x: number, y: number, width: number, height: number) {
|
|
92
|
+
this.frame = { x, y, width, height };
|
|
93
|
+
ffi.request.wgpuViewSetFrame({ id: this.id, x, y, width, height });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setTransparent(transparent: boolean) {
|
|
97
|
+
ffi.request.wgpuViewSetTransparent({ id: this.id, transparent });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setPassthrough(passthrough: boolean) {
|
|
101
|
+
ffi.request.wgpuViewSetPassthrough({ id: this.id, passthrough });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setHidden(hidden: boolean) {
|
|
105
|
+
ffi.request.wgpuViewSetHidden({ id: this.id, hidden });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
on(name: "frame-updated", handler: (event: unknown) => void) {
|
|
109
|
+
const specificName = `${name}-${this.id}`;
|
|
110
|
+
sparkBunEventEmitter.on(specificName, handler);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
remove() {
|
|
114
|
+
if (this.isRemoved) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.isRemoved = true;
|
|
119
|
+
delete WGPUViewMap[this.id];
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
ffi.request.wgpuViewRemove({ id: this.id });
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.error(`Error removing WGPU view ${this.id}:`, e);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getNativeHandle() {
|
|
129
|
+
return ffi.request.wgpuViewGetNativeHandle({ id: this.id });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static getById(id: number) {
|
|
133
|
+
return WGPUViewMap[id];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static adoptExisting(id: number, options: Partial<WGPUViewOptions> = {}) {
|
|
137
|
+
const existing = WGPUViewMap[id];
|
|
138
|
+
if (existing) {
|
|
139
|
+
return existing;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const ptr = ffi.request.getWGPUViewPointer({ id }) as Pointer | null;
|
|
143
|
+
if (!ptr) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const view = Object.create(WGPUView.prototype) as WGPUView;
|
|
148
|
+
view.id = id;
|
|
149
|
+
view.windowId = options.windowId ?? 0;
|
|
150
|
+
view.autoResize = options.autoResize === false ? false : true;
|
|
151
|
+
view.frame = {
|
|
152
|
+
x: options.frame?.x ?? defaultOptions.frame!.x,
|
|
153
|
+
y: options.frame?.y ?? defaultOptions.frame!.y,
|
|
154
|
+
width: options.frame?.width ?? defaultOptions.frame!.width,
|
|
155
|
+
height: options.frame?.height ?? defaultOptions.frame!.height,
|
|
156
|
+
};
|
|
157
|
+
view.startTransparent = options.startTransparent ?? false;
|
|
158
|
+
view.startPassthrough = options.startPassthrough ?? false;
|
|
159
|
+
view.isRemoved = false;
|
|
160
|
+
WGPUViewMap[id] = view;
|
|
161
|
+
return view;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static getAll() {
|
|
165
|
+
return Object.values(WGPUViewMap);
|
|
166
|
+
}
|
|
167
|
+
}
|