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,22 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
pub fn build(b: *std.Build) void {
|
|
4
|
+
// zig build -Doptimize=Debug to enable debug mode
|
|
5
|
+
const target = b.standardTargetOptions(.{});
|
|
6
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
7
|
+
|
|
8
|
+
const exe = b.addExecutable(.{
|
|
9
|
+
.name = "extractor",
|
|
10
|
+
.root_source_file = b.path("main.zig"),
|
|
11
|
+
.target = target,
|
|
12
|
+
.optimize = optimize,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Link with libc for chmod and other system calls
|
|
16
|
+
exe.linkLibC();
|
|
17
|
+
|
|
18
|
+
// Use Console subsystem on all platforms so users can see extraction progress
|
|
19
|
+
// The console window will automatically close when extraction completes
|
|
20
|
+
|
|
21
|
+
b.installArtifact(exe);
|
|
22
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import archiveData from "./app-archive.tar.gz" with { type: "file" };
|
|
2
|
+
import metadataJson from "./metadata.json" with { type: "file" };
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { existsSync, mkdirSync, chmodSync, readdirSync } from "fs";
|
|
5
|
+
|
|
6
|
+
interface Metadata {
|
|
7
|
+
identifier: string;
|
|
8
|
+
name: string;
|
|
9
|
+
channel: string;
|
|
10
|
+
hash: string;
|
|
11
|
+
requireAdmin?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const metadata: Metadata = await Bun.file(metadataJson).json();
|
|
15
|
+
const appName = metadata.name;
|
|
16
|
+
const exeName = metadata.name.replace(/ /g, "");
|
|
17
|
+
const installDir = getInstallDir(metadata);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(`Installing ${appName}...`);
|
|
22
|
+
|
|
23
|
+
const archiveBytes = await Bun.file(archiveData).bytes();
|
|
24
|
+
const archive = new Bun.Archive(archiveBytes);
|
|
25
|
+
mkdirSync(installDir, { recursive: true });
|
|
26
|
+
await archive.extract(installDir);
|
|
27
|
+
console.log(`Extracted to ${installDir}`);
|
|
28
|
+
|
|
29
|
+
let appDir = installDir;
|
|
30
|
+
// Look for the app bundle folder inside the extraction dir
|
|
31
|
+
const entries = readdirSync(installDir);
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const entryPath = join(installDir, entry);
|
|
34
|
+
if (existsSync(join(entryPath, "bin"))) {
|
|
35
|
+
appDir = entryPath;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (process.platform === "win32") {
|
|
41
|
+
await createWindowsShortcuts(appDir, metadata);
|
|
42
|
+
} else if (process.platform === "linux") {
|
|
43
|
+
fixPermissions(appDir);
|
|
44
|
+
await createLinuxDesktopEntry(appDir, metadata);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Launch the app
|
|
48
|
+
const launcherName = process.platform === "win32" ? `${exeName}.exe` : exeName;
|
|
49
|
+
const launcherPath = join(appDir, "bin", launcherName);
|
|
50
|
+
|
|
51
|
+
if (existsSync(launcherPath)) {
|
|
52
|
+
console.log(`Launching ${appName}...`);
|
|
53
|
+
if (process.platform === "win32" && metadata.requireAdmin) {
|
|
54
|
+
const { dlopen, ptr } = await import("bun:ffi");
|
|
55
|
+
const shell32 = dlopen("shell32.dll", {
|
|
56
|
+
ShellExecuteW: {
|
|
57
|
+
args: ["ptr", "ptr", "ptr", "ptr", "ptr", "i32"],
|
|
58
|
+
returns: "ptr",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const encode = (s: string) => ptr(new Uint8Array(Buffer.from(s + "\0", "utf-16le")));
|
|
62
|
+
shell32.symbols.ShellExecuteW(
|
|
63
|
+
null,
|
|
64
|
+
encode("runas"),
|
|
65
|
+
encode(launcherPath),
|
|
66
|
+
null,
|
|
67
|
+
encode(join(appDir, "bin")),
|
|
68
|
+
1,
|
|
69
|
+
);
|
|
70
|
+
shell32.close();
|
|
71
|
+
} else {
|
|
72
|
+
Bun.spawn([launcherPath], { cwd: join(appDir, "bin") });
|
|
73
|
+
}
|
|
74
|
+
await Bun.sleep(1000);
|
|
75
|
+
}
|
|
76
|
+
} catch (e: any) {
|
|
77
|
+
console.error(`Installation failed: ${e.message}`);
|
|
78
|
+
if (process.platform === "win32") {
|
|
79
|
+
console.log("Press Enter to exit...");
|
|
80
|
+
for await (const _ of console) { break; }
|
|
81
|
+
}
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getInstallDir(meta: Metadata): string {
|
|
86
|
+
if (process.platform === "win32") {
|
|
87
|
+
const localAppData = process.env.LOCALAPPDATA || join(process.env.USERPROFILE || "", "AppData", "Local");
|
|
88
|
+
return join(localAppData, meta.identifier, meta.channel, "app");
|
|
89
|
+
} else {
|
|
90
|
+
const dataHome = process.env.XDG_DATA_HOME || join(process.env.HOME || "", ".local", "share");
|
|
91
|
+
return join(dataHome, meta.identifier, meta.channel, "app");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function createWindowsShortcuts(appDir: string, meta: Metadata) {
|
|
96
|
+
const binDir = join(appDir, "bin");
|
|
97
|
+
const exePath = join(binDir, `${meta.name.replace(/ /g, "")}.exe`);
|
|
98
|
+
|
|
99
|
+
if (!existsSync(exePath)) return;
|
|
100
|
+
|
|
101
|
+
// Start Menu shortcut
|
|
102
|
+
const startMenu = join(process.env.APPDATA || "", "Microsoft", "Windows", "Start Menu", "Programs");
|
|
103
|
+
try {
|
|
104
|
+
Bun.spawnSync(["powershell", "-NoProfile", "-Command",
|
|
105
|
+
`$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('${join(startMenu, meta.name + ".lnk")}'); $s.TargetPath = '${exePath}'; $s.WorkingDirectory = '${binDir}'; $s.Save()`
|
|
106
|
+
]);
|
|
107
|
+
} catch {}
|
|
108
|
+
|
|
109
|
+
// Desktop shortcut
|
|
110
|
+
try {
|
|
111
|
+
const desktop = join(process.env.USERPROFILE || "", "Desktop");
|
|
112
|
+
Bun.spawnSync(["powershell", "-NoProfile", "-Command",
|
|
113
|
+
`$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('${join(desktop, meta.name + ".lnk")}'); $s.TargetPath = '${exePath}'; $s.WorkingDirectory = '${binDir}'; $s.Save()`
|
|
114
|
+
]);
|
|
115
|
+
} catch {}
|
|
116
|
+
|
|
117
|
+
console.log("Created Start Menu and Desktop shortcuts");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function fixPermissions(appDir: string) {
|
|
121
|
+
const exeBaseName = appDir.split("/").pop()?.replace(/ /g, "") || "app";
|
|
122
|
+
const executables = [`bin/${exeBaseName}`, "bin/bspatch", "bin/bsdiff"];
|
|
123
|
+
for (const exe of executables) {
|
|
124
|
+
const path = join(appDir, exe);
|
|
125
|
+
if (existsSync(path)) {
|
|
126
|
+
try { chmodSync(path, 0o755); } catch {}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function createLinuxDesktopEntry(appDir: string, meta: Metadata) {
|
|
132
|
+
const launcherPath = join(appDir, "bin", meta.name.replace(/ /g, ""));
|
|
133
|
+
if (!existsSync(launcherPath)) return;
|
|
134
|
+
|
|
135
|
+
// Find icon
|
|
136
|
+
let iconPath = "";
|
|
137
|
+
const resourcesDir = join(appDir, "Resources");
|
|
138
|
+
if (existsSync(resourcesDir)) {
|
|
139
|
+
const pngs = readdirSync(resourcesDir).filter(f => f.endsWith(".png"));
|
|
140
|
+
if (pngs.length > 0) iconPath = join(resourcesDir, pngs[0]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// If requireAdmin, install polkit policy and use pkexec in Exec line
|
|
144
|
+
if (meta.requireAdmin) {
|
|
145
|
+
await installPolkitPolicy(launcherPath, meta);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const execLine = meta.requireAdmin
|
|
149
|
+
? `pkexec "${launcherPath}"`
|
|
150
|
+
: `"${launcherPath}"`;
|
|
151
|
+
|
|
152
|
+
const desktopContent = `[Desktop Entry]
|
|
153
|
+
Version=1.0
|
|
154
|
+
Type=Application
|
|
155
|
+
Name=${meta.name}
|
|
156
|
+
Exec=${execLine}
|
|
157
|
+
Icon=${iconPath}
|
|
158
|
+
Terminal=false
|
|
159
|
+
StartupWMClass=${meta.name}
|
|
160
|
+
Categories=Utility;Application;
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
// Write to XDG applications directory
|
|
164
|
+
const dataHome = process.env.XDG_DATA_HOME || join(process.env.HOME || "", ".local", "share");
|
|
165
|
+
const applicationsDir = join(dataHome, "applications");
|
|
166
|
+
mkdirSync(applicationsDir, { recursive: true });
|
|
167
|
+
|
|
168
|
+
const desktopFilePath = join(applicationsDir, `${meta.name}.desktop`);
|
|
169
|
+
await Bun.write(desktopFilePath, desktopContent);
|
|
170
|
+
chmodSync(desktopFilePath, 0o644);
|
|
171
|
+
|
|
172
|
+
// Desktop shortcut
|
|
173
|
+
const desktopDir = join(process.env.HOME || "", "Desktop");
|
|
174
|
+
if (existsSync(desktopDir)) {
|
|
175
|
+
const desktopShortcut = join(desktopDir, `${meta.name}.desktop`);
|
|
176
|
+
await Bun.write(desktopShortcut, desktopContent);
|
|
177
|
+
chmodSync(desktopShortcut, 0o755);
|
|
178
|
+
try {
|
|
179
|
+
Bun.spawnSync(["gio", "set", desktopShortcut, "metadata::trusted", "true"]);
|
|
180
|
+
} catch {}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log("Created desktop entries");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function installPolkitPolicy(launcherPath: string, meta: Metadata) {
|
|
187
|
+
const policyId = meta.identifier.replace(/-/g, ".");
|
|
188
|
+
const policyContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
189
|
+
<!DOCTYPE policyconfig PUBLIC
|
|
190
|
+
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
|
191
|
+
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
|
192
|
+
<policyconfig>
|
|
193
|
+
<action id="${policyId}.run">
|
|
194
|
+
<description>Run ${meta.name}</description>
|
|
195
|
+
<message>Authentication is required to run ${meta.name}</message>
|
|
196
|
+
<defaults>
|
|
197
|
+
<allow_any>auth_admin</allow_any>
|
|
198
|
+
<allow_inactive>auth_admin</allow_inactive>
|
|
199
|
+
<allow_active>auth_admin_keep</allow_active>
|
|
200
|
+
</defaults>
|
|
201
|
+
<annotate key="org.freedesktop.policykit.exec.path">${launcherPath}</annotate>
|
|
202
|
+
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
|
203
|
+
</action>
|
|
204
|
+
</policyconfig>
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
const policyDir = "/usr/share/polkit-1/actions";
|
|
208
|
+
const policyPath = join(policyDir, `${policyId}.policy`);
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
await Bun.write(policyPath, policyContent);
|
|
212
|
+
console.log(`Installed polkit policy: ${policyPath}`);
|
|
213
|
+
} catch {
|
|
214
|
+
console.log("Note: Could not install polkit policy (may need root). The app will still prompt for authentication via pkexec.");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { join, dirname, resolve } from "path";
|
|
2
|
+
import { dlopen, suffix, ptr } from "bun:ffi";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
|
|
5
|
+
// Set CWD to bin/ directory where the binary lives.
|
|
6
|
+
// Without this, pkexec sets CWD to $HOME and all ../Resources/ paths break.
|
|
7
|
+
const pathToMacOS = dirname(process.argv0);
|
|
8
|
+
process.chdir(pathToMacOS);
|
|
9
|
+
const coreLibFileName =
|
|
10
|
+
process.platform === "win32"
|
|
11
|
+
? "ElectrobunCore.dll"
|
|
12
|
+
: `libElectrobunCore.${suffix}`;
|
|
13
|
+
const coreLibPath = join(pathToMacOS, coreLibFileName);
|
|
14
|
+
const absoluteCoreLibPath = resolve(coreLibPath);
|
|
15
|
+
|
|
16
|
+
function isAdmin(): boolean {
|
|
17
|
+
if (process.platform === "win32") {
|
|
18
|
+
const shell32 = dlopen("shell32.dll", {
|
|
19
|
+
IsUserAnAdmin: { args: [], returns: "bool" },
|
|
20
|
+
});
|
|
21
|
+
const admin = shell32.symbols.IsUserAnAdmin();
|
|
22
|
+
shell32.close();
|
|
23
|
+
return admin;
|
|
24
|
+
}
|
|
25
|
+
if (process.platform === "linux") {
|
|
26
|
+
return process.getuid?.() === 0;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function elevateAndExit(): void {
|
|
32
|
+
if (process.platform === "win32") {
|
|
33
|
+
const shell32 = dlopen("shell32.dll", {
|
|
34
|
+
ShellExecuteW: {
|
|
35
|
+
args: ["ptr", "ptr", "ptr", "ptr", "ptr", "i32"],
|
|
36
|
+
returns: "ptr",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const encode = (s: string) => ptr(new Uint8Array(Buffer.from(s + "\0", "utf-16le")));
|
|
40
|
+
shell32.symbols.ShellExecuteW(
|
|
41
|
+
null,
|
|
42
|
+
encode("runas"),
|
|
43
|
+
encode(process.argv0),
|
|
44
|
+
null,
|
|
45
|
+
null,
|
|
46
|
+
1, // SW_SHOWNORMAL
|
|
47
|
+
);
|
|
48
|
+
shell32.close();
|
|
49
|
+
} else if (process.platform === "linux") {
|
|
50
|
+
Bun.spawnSync(["pkexec", process.argv0, ...process.argv.slice(1)], {
|
|
51
|
+
stdout: "inherit",
|
|
52
|
+
stderr: "inherit",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Wrap main logic in a function to avoid top-level return
|
|
59
|
+
function main() {
|
|
60
|
+
// Read version.json early to get identifier, name, and channel for CEF initialization
|
|
61
|
+
let channel = "";
|
|
62
|
+
let identifier = "";
|
|
63
|
+
let name = "";
|
|
64
|
+
try {
|
|
65
|
+
const pathToLauncherBin = process.argv0;
|
|
66
|
+
const pathToBinDir = dirname(pathToLauncherBin);
|
|
67
|
+
const versionJsonPath = join(
|
|
68
|
+
pathToBinDir,
|
|
69
|
+
"..",
|
|
70
|
+
"Resources",
|
|
71
|
+
"version.json",
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (existsSync(versionJsonPath)) {
|
|
75
|
+
const versionInfo = require(versionJsonPath);
|
|
76
|
+
if (versionInfo.identifier) {
|
|
77
|
+
identifier = versionInfo.identifier;
|
|
78
|
+
}
|
|
79
|
+
if (versionInfo.name) {
|
|
80
|
+
name = versionInfo.name;
|
|
81
|
+
}
|
|
82
|
+
if (versionInfo.channel) {
|
|
83
|
+
channel = versionInfo.channel;
|
|
84
|
+
}
|
|
85
|
+
console.log(
|
|
86
|
+
`[LAUNCHER] Loaded identifier: ${identifier}, name: ${name}, channel: ${channel}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(`[LAUNCHER] Warning: Could not read version.json:`, error);
|
|
91
|
+
// Continue anyway - this is not critical for dev builds
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if admin elevation is required
|
|
95
|
+
if (process.platform !== "darwin") {
|
|
96
|
+
try {
|
|
97
|
+
const binDir = dirname(process.argv0);
|
|
98
|
+
const buildJsonPath = join(binDir, "..", "Resources", "build.json");
|
|
99
|
+
if (existsSync(buildJsonPath)) {
|
|
100
|
+
const buildConfig = JSON.parse(
|
|
101
|
+
require("fs").readFileSync(buildJsonPath, "utf-8"),
|
|
102
|
+
);
|
|
103
|
+
if (buildConfig.requireAdmin && !isAdmin()) {
|
|
104
|
+
console.log("[LAUNCHER] Admin required, elevating...");
|
|
105
|
+
elevateAndExit();
|
|
106
|
+
return;
|
|
107
|
+
} else if (buildConfig.requireAdmin) {
|
|
108
|
+
console.log("[LAUNCHER] Already running as admin");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`[LAUNCHER] Warning: Could not check admin requirement:`, error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for CEF libraries and warn if LD_PRELOAD not set (Linux only)
|
|
117
|
+
if (process.platform === "linux") {
|
|
118
|
+
const cefLibs = [
|
|
119
|
+
join(pathToMacOS, "libcef.so"),
|
|
120
|
+
join(pathToMacOS, "libvk_swiftshader.so"),
|
|
121
|
+
];
|
|
122
|
+
const existingCefLibs = cefLibs.filter((lib) => existsSync(lib));
|
|
123
|
+
|
|
124
|
+
if (existingCefLibs.length > 0 && !process.env["LD_PRELOAD"]) {
|
|
125
|
+
console.error(
|
|
126
|
+
`[LAUNCHER] ERROR: CEF libraries found but LD_PRELOAD not set!`,
|
|
127
|
+
);
|
|
128
|
+
console.error(
|
|
129
|
+
`[LAUNCHER] Please run through the wrapper script: ./run.sh`,
|
|
130
|
+
);
|
|
131
|
+
console.error(
|
|
132
|
+
`[LAUNCHER] Or set: LD_PRELOAD="${existingCefLibs.join(":")}" before starting.`,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Try to re-exec ourselves with LD_PRELOAD set
|
|
136
|
+
const { spawn } = require("child_process");
|
|
137
|
+
const env = { ...process.env, LD_PRELOAD: existingCefLibs.join(":") };
|
|
138
|
+
const child = spawn(process.argv[0], process.argv.slice(1), {
|
|
139
|
+
env,
|
|
140
|
+
stdio: "inherit",
|
|
141
|
+
});
|
|
142
|
+
child.on("exit", (code: number | null) => process.exit(code ?? 1));
|
|
143
|
+
return; // Don't continue in this process
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let lib;
|
|
148
|
+
try {
|
|
149
|
+
|
|
150
|
+
lib = dlopen(coreLibPath, {
|
|
151
|
+
electrobun_core_run_main_thread: {
|
|
152
|
+
args: ["cstring", "cstring", "cstring", "i32"],
|
|
153
|
+
returns: "i32",
|
|
154
|
+
},
|
|
155
|
+
electrobun_core_last_error: {
|
|
156
|
+
args: [],
|
|
157
|
+
returns: "cstring",
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error(
|
|
162
|
+
`[LAUNCHER] Failed to load ElectrobunCore: ${(error as Error).message}`,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Try with absolute path as fallback
|
|
166
|
+
try {
|
|
167
|
+
lib = dlopen(absoluteCoreLibPath, {
|
|
168
|
+
electrobun_core_run_main_thread: {
|
|
169
|
+
args: ["cstring", "cstring", "cstring", "i32"],
|
|
170
|
+
returns: "i32",
|
|
171
|
+
},
|
|
172
|
+
electrobun_core_last_error: {
|
|
173
|
+
args: [],
|
|
174
|
+
returns: "cstring",
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
} catch (absError) {
|
|
178
|
+
console.error(
|
|
179
|
+
`[LAUNCHER] Core library loading failed. Try running: ldd ${coreLibPath}`,
|
|
180
|
+
);
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// from different locations
|
|
186
|
+
const pathToLauncherBin = process.argv0;
|
|
187
|
+
const pathToBinDir = dirname(pathToLauncherBin);
|
|
188
|
+
|
|
189
|
+
const resourcesDir = join(pathToBinDir, "..", "Resources");
|
|
190
|
+
const appFolderPath = join(resourcesDir, "app");
|
|
191
|
+
const appEntrypointPath = join(appFolderPath, "bun", "index.js");
|
|
192
|
+
|
|
193
|
+
// Register signal handlers on the main thread to prevent default termination.
|
|
194
|
+
// The worker thread's SIGINT handler will call quit() for graceful shutdown.
|
|
195
|
+
// Without these, SIGINT kills the process before the worker can run beforeQuit.
|
|
196
|
+
process.on("SIGINT", () => {});
|
|
197
|
+
process.on("SIGTERM", () => {});
|
|
198
|
+
|
|
199
|
+
new Worker(appEntrypointPath, {
|
|
200
|
+
// consider adding a preload with error handling
|
|
201
|
+
// preload: [''];
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const runStatus = lib.symbols.electrobun_core_run_main_thread(
|
|
205
|
+
ptr(new Uint8Array(Buffer.from(identifier + "\0", "utf8"))),
|
|
206
|
+
ptr(new Uint8Array(Buffer.from(name + "\0", "utf8"))),
|
|
207
|
+
ptr(new Uint8Array(Buffer.from(channel + "\0", "utf8"))),
|
|
208
|
+
0,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (runStatus !== 0) {
|
|
212
|
+
const coreError = lib.symbols.electrobun_core_last_error();
|
|
213
|
+
console.error(
|
|
214
|
+
`[LAUNCHER] ElectrobunCore failed: ${coreError ? coreError.toString() : "Unknown error"}`,
|
|
215
|
+
);
|
|
216
|
+
process.exit(runStatus);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Call the main function
|
|
221
|
+
main();
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#include "cef_loader.h"
|
|
2
|
+
#include <iostream>
|
|
3
|
+
#include <sys/stat.h>
|
|
4
|
+
#include <unistd.h>
|
|
5
|
+
#include <limits.h>
|
|
6
|
+
#include <cstdlib>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
|
|
9
|
+
// Static member definitions
|
|
10
|
+
void* CefLoader::cef_lib_handle = nullptr;
|
|
11
|
+
std::atomic<bool> CefLoader::initialized(false);
|
|
12
|
+
std::atomic<bool> CefLoader::load_attempted(false);
|
|
13
|
+
|
|
14
|
+
std::string CefLoader::GetCefLibraryPath() {
|
|
15
|
+
// Get the executable directory
|
|
16
|
+
char exe_path[PATH_MAX];
|
|
17
|
+
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
|
18
|
+
if (len == -1) return "";
|
|
19
|
+
|
|
20
|
+
exe_path[len] = '\0';
|
|
21
|
+
std::string execPath(exe_path);
|
|
22
|
+
size_t pos = execPath.find_last_of('/');
|
|
23
|
+
if (pos != std::string::npos) {
|
|
24
|
+
execPath = execPath.substr(0, pos);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check primary location: execDir/cef/libcef.so
|
|
28
|
+
std::string cefLibPath = execPath + "/cef/libcef.so";
|
|
29
|
+
struct stat buffer;
|
|
30
|
+
if (stat(cefLibPath.c_str(), &buffer) == 0) {
|
|
31
|
+
return cefLibPath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check fallback location: execDir/libcef.so
|
|
35
|
+
cefLibPath = execPath + "/libcef.so";
|
|
36
|
+
if (stat(cefLibPath.c_str(), &buffer) == 0) {
|
|
37
|
+
return cefLibPath;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bool CefLoader::Initialize() {
|
|
44
|
+
// Only attempt to load once
|
|
45
|
+
bool expected = false;
|
|
46
|
+
if (!load_attempted.compare_exchange_strong(expected, true)) {
|
|
47
|
+
return initialized.load();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get CEF library path
|
|
51
|
+
std::string cefPath = GetCefLibraryPath();
|
|
52
|
+
if (cefPath.empty()) {
|
|
53
|
+
std::cout << "CEF library not found - CEF features will be disabled" << std::endl;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
std::cout << "Attempting to load CEF from: " << cefPath << std::endl;
|
|
58
|
+
|
|
59
|
+
// Extract directory for LD_LIBRARY_PATH
|
|
60
|
+
std::string cefDir;
|
|
61
|
+
size_t pos = cefPath.find_last_of('/');
|
|
62
|
+
if (pos != std::string::npos) {
|
|
63
|
+
cefDir = cefPath.substr(0, pos);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Set LD_LIBRARY_PATH to include the CEF directory for dependent libraries
|
|
67
|
+
const char* existing_ld_path = getenv("LD_LIBRARY_PATH");
|
|
68
|
+
std::string new_ld_path = cefDir;
|
|
69
|
+
if (existing_ld_path && strlen(existing_ld_path) > 0) {
|
|
70
|
+
new_ld_path += ":";
|
|
71
|
+
new_ld_path += existing_ld_path;
|
|
72
|
+
}
|
|
73
|
+
setenv("LD_LIBRARY_PATH", new_ld_path.c_str(), 1);
|
|
74
|
+
|
|
75
|
+
// Also set LD_LIBRARY_PATH for the current process using dlopen hack
|
|
76
|
+
// This ensures dependent libraries can be found
|
|
77
|
+
void* handle = dlopen(nullptr, RTLD_LAZY | RTLD_GLOBAL);
|
|
78
|
+
if (handle) {
|
|
79
|
+
dlclose(handle);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Load CEF library with RTLD_GLOBAL to make all symbols available globally
|
|
83
|
+
// This allows CEF to be used normally without function pointers
|
|
84
|
+
cef_lib_handle = dlopen(cefPath.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
|
85
|
+
if (!cef_lib_handle) {
|
|
86
|
+
const char* error = dlerror();
|
|
87
|
+
std::cerr << "Failed to load CEF library: " << (error ? error : "unknown error") << std::endl;
|
|
88
|
+
|
|
89
|
+
// Try to provide more helpful error messages
|
|
90
|
+
if (error && strstr(error, "cannot open shared object file")) {
|
|
91
|
+
std::cerr << "Make sure all CEF dependencies are in the same directory as libcef.so" << std::endl;
|
|
92
|
+
std::cerr << "LD_LIBRARY_PATH was set to: " << new_ld_path << std::endl;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Verify critical symbols are available
|
|
98
|
+
dlerror(); // Clear any existing errors
|
|
99
|
+
void* init_sym = dlsym(cef_lib_handle, "cef_initialize");
|
|
100
|
+
if (!init_sym) {
|
|
101
|
+
std::cerr << "CEF library loaded but cef_initialize symbol not found: " << dlerror() << std::endl;
|
|
102
|
+
dlclose(cef_lib_handle);
|
|
103
|
+
cef_lib_handle = nullptr;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
initialized.store(true);
|
|
108
|
+
std::cout << "CEF library loaded successfully" << std::endl;
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#ifndef CEF_LOADER_H
|
|
2
|
+
#define CEF_LOADER_H
|
|
3
|
+
|
|
4
|
+
#include <dlfcn.h>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <atomic>
|
|
7
|
+
|
|
8
|
+
// Dynamic CEF loading wrapper for weak linking on Linux
|
|
9
|
+
// This uses dlopen with RTLD_GLOBAL to make all CEF symbols available
|
|
10
|
+
// globally, which allows us to use CEF normally without function pointers
|
|
11
|
+
class CefLoader {
|
|
12
|
+
private:
|
|
13
|
+
static void* cef_lib_handle;
|
|
14
|
+
static std::atomic<bool> initialized;
|
|
15
|
+
static std::atomic<bool> load_attempted;
|
|
16
|
+
|
|
17
|
+
public:
|
|
18
|
+
// Initialize the loader and attempt to load CEF
|
|
19
|
+
static bool Initialize();
|
|
20
|
+
|
|
21
|
+
// Check if CEF is loaded and available
|
|
22
|
+
static bool IsLoaded() { return initialized.load(); }
|
|
23
|
+
|
|
24
|
+
// Get the path to libcef.so
|
|
25
|
+
static std::string GetCefLibraryPath();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
#endif // CEF_LOADER_H
|