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.
Files changed (131) hide show
  1. package/bin/sparkbun.cjs +18 -0
  2. package/dist-linux-arm64/bsdiff +0 -0
  3. package/dist-linux-arm64/bspatch +0 -0
  4. package/dist-linux-arm64/libElectrobunCore.so +0 -0
  5. package/dist-linux-arm64/libNativeWrapper.so +0 -0
  6. package/dist-linux-arm64/libasar.so +0 -0
  7. package/dist-linux-x64/bsdiff +0 -0
  8. package/dist-linux-x64/bspatch +0 -0
  9. package/dist-linux-x64/libElectrobunCore.so +0 -0
  10. package/dist-linux-x64/libNativeWrapper.so +0 -0
  11. package/dist-linux-x64/libasar.so +0 -0
  12. package/dist-macos-arm64/bsdiff +0 -0
  13. package/dist-macos-arm64/bspatch +0 -0
  14. package/dist-macos-arm64/libElectrobunCore.dylib +0 -0
  15. package/dist-macos-arm64/libNativeWrapper.dylib +0 -0
  16. package/dist-macos-arm64/libasar.dylib +0 -0
  17. package/dist-macos-arm64/libwebgpu_dawn.dylib +0 -0
  18. package/dist-macos-arm64/preload-full.js +885 -0
  19. package/dist-macos-arm64/preload-sandboxed.js +111 -0
  20. package/dist-macos-arm64/process_helper +0 -0
  21. package/dist-win-x64/ElectrobunCore.dll +0 -0
  22. package/dist-win-x64/WebView2Loader.dll +0 -0
  23. package/dist-win-x64/bsdiff.exe +0 -0
  24. package/dist-win-x64/bspatch.exe +0 -0
  25. package/dist-win-x64/libNativeWrapper.dll +0 -0
  26. package/dist-win-x64/zig-asar/arm64/libasar.dll +0 -0
  27. package/dist-win-x64/zig-asar/x64/libasar.dll +0 -0
  28. package/package.json +47 -0
  29. package/scripts/build-and-upload-artifacts.js +207 -0
  30. package/scripts/gen-webgpu-ffi.mjs +162 -0
  31. package/scripts/install-windows-deps.ps1 +80 -0
  32. package/scripts/package-release.js +237 -0
  33. package/scripts/push-version.js +84 -0
  34. package/scripts/update-bun-version.ts +122 -0
  35. package/scripts/update-cef-version.ts +145 -0
  36. package/src/browser/builtinrpcSchema.ts +19 -0
  37. package/src/browser/global.d.ts +36 -0
  38. package/src/browser/index.ts +234 -0
  39. package/src/browser/webviewtag.ts +88 -0
  40. package/src/browser/wgputag.ts +48 -0
  41. package/src/bun/SparkBunConfig.ts +497 -0
  42. package/src/bun/__tests__/ffi-contract.test.ts +105 -0
  43. package/src/bun/core/ApplicationMenu.ts +70 -0
  44. package/src/bun/core/BrowserView.ts +416 -0
  45. package/src/bun/core/BrowserWindow.ts +396 -0
  46. package/src/bun/core/BuildConfig.ts +71 -0
  47. package/src/bun/core/ContextMenu.ts +75 -0
  48. package/src/bun/core/GpuWindow.ts +289 -0
  49. package/src/bun/core/Paths.ts +5 -0
  50. package/src/bun/core/Socket.ts +22 -0
  51. package/src/bun/core/Tray.ts +197 -0
  52. package/src/bun/core/Updater.ts +1131 -0
  53. package/src/bun/core/Utils.ts +487 -0
  54. package/src/bun/core/WGPUView.ts +167 -0
  55. package/src/bun/core/menuRoles.ts +181 -0
  56. package/src/bun/events/ApplicationEvents.ts +22 -0
  57. package/src/bun/events/event.ts +27 -0
  58. package/src/bun/events/eventEmitter.ts +45 -0
  59. package/src/bun/events/trayEvents.ts +11 -0
  60. package/src/bun/events/webviewEvents.ts +39 -0
  61. package/src/bun/events/windowEvents.ts +23 -0
  62. package/src/bun/index.ts +120 -0
  63. package/src/bun/preload/.generated/compiled.ts +2 -0
  64. package/src/bun/preload/build.ts +65 -0
  65. package/src/bun/preload/dragRegions.ts +41 -0
  66. package/src/bun/preload/encryption.ts +86 -0
  67. package/src/bun/preload/events.ts +171 -0
  68. package/src/bun/preload/globals.d.ts +45 -0
  69. package/src/bun/preload/index-sandboxed.ts +28 -0
  70. package/src/bun/preload/index.ts +77 -0
  71. package/src/bun/preload/internalRpc.ts +80 -0
  72. package/src/bun/preload/overlaySync.ts +107 -0
  73. package/src/bun/preload/webviewTag.ts +451 -0
  74. package/src/bun/preload/wgpuTag.ts +246 -0
  75. package/src/bun/proc/linux.md +43 -0
  76. package/src/bun/proc/native.ts +3253 -0
  77. package/src/bun/webGPU.ts +346 -0
  78. package/src/bun/webgpuAdapter.ts +3011 -0
  79. package/src/cli/bun.lockb +0 -0
  80. package/src/cli/index.ts +4653 -0
  81. package/src/cli/package-lock.json +81 -0
  82. package/src/cli/package.json +11 -0
  83. package/src/cli/templates/embedded.ts +2 -0
  84. package/src/core/build.zig +16 -0
  85. package/src/core/main.zig +3378 -0
  86. package/src/extractor/build.zig +22 -0
  87. package/src/installer/installer-template.ts +216 -0
  88. package/src/launcher/main.ts +221 -0
  89. package/src/native/build/libNativeWrapper.so +0 -0
  90. package/src/native/linux/build/nativeWrapper.o +0 -0
  91. package/src/native/linux/cef_loader.cpp +110 -0
  92. package/src/native/linux/cef_loader.h +28 -0
  93. package/src/native/linux/cef_process_helper_linux.cpp +160 -0
  94. package/src/native/linux/nativeWrapper.cpp +11768 -0
  95. package/src/native/macos/cef_process_helper_mac.cc +160 -0
  96. package/src/native/macos/nativeWrapper.mm +9172 -0
  97. package/src/native/shared/accelerator_parser.h +72 -0
  98. package/src/native/shared/app_paths.h +110 -0
  99. package/src/native/shared/asar.h +35 -0
  100. package/src/native/shared/cache_migration.h +244 -0
  101. package/src/native/shared/callbacks.h +57 -0
  102. package/src/native/shared/cef_response_filter.h +189 -0
  103. package/src/native/shared/chromium_flags.h +181 -0
  104. package/src/native/shared/config.h +66 -0
  105. package/src/native/shared/download_event.h +197 -0
  106. package/src/native/shared/ffi_helpers.h +139 -0
  107. package/src/native/shared/glob_match.h +59 -0
  108. package/src/native/shared/json_menu_parser.h +223 -0
  109. package/src/native/shared/mime_types.h +101 -0
  110. package/src/native/shared/navigation_rules.h +98 -0
  111. package/src/native/shared/partition_context.h +137 -0
  112. package/src/native/shared/pending_resize_queue.h +45 -0
  113. package/src/native/shared/permissions.h +118 -0
  114. package/src/native/shared/permissions_cef.h +74 -0
  115. package/src/native/shared/preload_script.h +71 -0
  116. package/src/native/shared/shutdown_guard.h +134 -0
  117. package/src/native/shared/thread_safe_map.h +138 -0
  118. package/src/native/shared/webview_storage.h +91 -0
  119. package/src/native/win/cef_process_helper_win.cpp +143 -0
  120. package/src/native/win/dcomp_compositor.h +352 -0
  121. package/src/native/win/nativeWrapper.cpp +12434 -0
  122. package/src/npmbin/index.js +34 -0
  123. package/src/shared/bun-version.ts +3 -0
  124. package/src/shared/cef-version.ts +5 -0
  125. package/src/shared/naming.test.ts +327 -0
  126. package/src/shared/naming.ts +188 -0
  127. package/src/shared/platform.ts +48 -0
  128. package/src/shared/rpc.ts +541 -0
  129. package/src/shared/sparkbun-version.ts +2 -0
  130. package/src/types/three.d.ts +1 -0
  131. 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
+ }