Fix black window on Linux + NVIDIA (WebKitGTK DMABUF renderer)
On the NVIDIA proprietary driver the app booted, logged "miniSCP started", reported no error — and painted a completely black window, with stderr showing "Failed to create GBM buffer of size WxH: Invalid argument" and an X11 GLXBadWindow. WebKitGTK's DMABUF renderer cannot allocate buffers there; WEBKIT_DISABLE_DMABUF_RENDERER=1 is the upstream workaround. Pre-existing on Linux, unrelated to the context-menu change — it just had never been looked at because nothing on Linux had been run before. The app CANNOT apply this to itself. WebKit reads the variable when it forks its web process, and Bun's `process.env` writes do not propagate to child processes (verified: a spawned child sees it unset), so setting it in src/bun/index.ts is inert even from a module imported before `electrobun/bun`. It has to be in the environment before the app process starts, which means every launch path must inject it: - scripts/linux-gpu.ts — shared `launchEnv()` helper, gated on /proc/driver/nvidia so Intel/AMD keep hardware compositing, and never overriding an explicit value from the user's environment. - scripts/dev.ts — uses it for `bun run dev`. - scripts/start.ts (new) — `bun run start` previously called `bunx electrobun dev` straight from package.json, where no env could be set; it now goes through a script like dev does. - scripts/release.ts — writes a `miniSCP.sh` wrapper beside bin/launcher in the Linux tarball, since a packaged app goes through none of the above. Documented in gotchas.md (including that a "does it boot?" log check does NOT catch this — the log looks healthy) and platform-notes.md, with the smoke test updated to require that the UI actually renders. Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean, and `bun run start` now boots with zero GBM/GLX errors. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -1,5 +1,7 @@
|
||||
// Dev orchestrator: starts ng serve (UI hot reload), waits for it, then runs
|
||||
// `electrobun dev --watch` (relaunches the app when bun-side code changes).
|
||||
import { launchEnv } from "./linux-gpu";
|
||||
|
||||
const DEV_SERVER_URL = "http://localhost:4200";
|
||||
|
||||
const ui = Bun.spawn(["bun", "run", "--cwd", "ui", "start"], {
|
||||
@@ -27,7 +29,7 @@ console.log("Angular dev server is up, launching Electrobun ...");
|
||||
const app = Bun.spawn(["bunx", "electrobun", "dev", "--watch"], {
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
env: { ...process.env, DEV_SERVER_URL },
|
||||
env: launchEnv({ DEV_SERVER_URL }),
|
||||
});
|
||||
|
||||
function shutdown() {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// PORTABILITY: WebKitGTK's DMABUF renderer cannot allocate GBM buffers on the
|
||||
// NVIDIA proprietary driver — the app window paints BLACK and stderr fills with
|
||||
// "Failed to create GBM buffer of size WxH: Invalid argument" (usually with an
|
||||
// "X11 Error: GLXBadWindow" alongside). Disabling that renderer path is the
|
||||
// standard upstream workaround.
|
||||
//
|
||||
// It has to be in the environment BEFORE the app process starts: WebKit reads
|
||||
// it when it forks its web process, and Bun's `process.env` writes do NOT
|
||||
// propagate to children (verified — a child sees the variable unset), so the
|
||||
// app cannot set this for itself from inside src/bun/. Every launch path has to
|
||||
// inject it: the dev scripts here, and the wrapper script that release.ts ships
|
||||
// beside the packaged launcher.
|
||||
//
|
||||
// Scoped to NVIDIA so Intel/AMD keep hardware compositing, and an explicit
|
||||
// value from the user's environment always wins.
|
||||
import { existsSync } from "node:fs";
|
||||
|
||||
export function needsDmabufWorkaround(): boolean {
|
||||
return (
|
||||
process.platform === "linux" &&
|
||||
process.env.WEBKIT_DISABLE_DMABUF_RENDERER === undefined &&
|
||||
existsSync("/proc/driver/nvidia")
|
||||
);
|
||||
}
|
||||
|
||||
/** `process.env` plus the workaround, ready to hand to Bun.spawn. */
|
||||
export function launchEnv(extra: Record<string, string> = {}): Record<string, string | undefined> {
|
||||
return {
|
||||
...process.env,
|
||||
...(needsDmabufWorkaround() ? { WEBKIT_DISABLE_DMABUF_RENDERER: "1" } : {}),
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
+19
-1
@@ -6,7 +6,7 @@
|
||||
// an arm64 Mac: we fetch electrobun's x64 CLI binary and run it under
|
||||
// Rosetta 2 — it sees an x64 host, pulls the x64 core, and packages mac-x64.
|
||||
// Artifacts land in release/<target>/miniSCP-Build<N>-<target>.<ext>.
|
||||
import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
||||
import { cpSync, existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -131,6 +131,24 @@ if (requested === "win-x64" || requested === "linux-x64") {
|
||||
run(["powershell", "-Command", `Compress-Archive -Force -Path '${appDir}\\*' -DestinationPath '${portable}'`]);
|
||||
out.push(path.basename(portable));
|
||||
} else {
|
||||
// Ship a wrapper beside the launcher: the packaged app never goes
|
||||
// through scripts/start.ts, and the NVIDIA/DMABUF workaround has to be
|
||||
// in the environment BEFORE the process starts (see scripts/linux-gpu.ts).
|
||||
const wrapper = path.join(appDir, `${BRANDING.appName}.sh`);
|
||||
writeFileSync(
|
||||
wrapper,
|
||||
[
|
||||
"#!/bin/sh",
|
||||
"# WebKitGTK's DMABUF renderer cannot allocate GBM buffers on the NVIDIA",
|
||||
"# proprietary driver — the window renders black. Disable it there only.",
|
||||
'if [ -z "$WEBKIT_DISABLE_DMABUF_RENDERER" ] && [ -e /proc/driver/nvidia ]; then',
|
||||
" export WEBKIT_DISABLE_DMABUF_RENDERER=1",
|
||||
"fi",
|
||||
'exec "$(dirname "$(readlink -f "$0")")/bin/launcher" "$@"',
|
||||
"",
|
||||
].join("\n"),
|
||||
{ mode: 0o755 },
|
||||
);
|
||||
const tarball = path.join(outDir, `${BRANDING.appName}-${buildName}-linux-x64.tar.gz`);
|
||||
// -C the parent so the archive unpacks into one named folder
|
||||
run(["tar", "-czf", tarball, "-C", path.dirname(appDir), path.basename(appDir)]);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// `bun run start`: launch the app against the prebuilt Angular bundle (no ng
|
||||
// serve / hot reload — that's `bun run dev`). Exists as a script rather than a
|
||||
// bare `bunx electrobun dev` in package.json so the Linux GPU workaround can be
|
||||
// injected into the environment before the app process starts.
|
||||
import { launchEnv } from "./linux-gpu";
|
||||
|
||||
const app = Bun.spawn(["bunx", "electrobun", "dev"], {
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
env: launchEnv(),
|
||||
});
|
||||
|
||||
function shutdown(): void {
|
||||
app.kill();
|
||||
process.exit(0);
|
||||
}
|
||||
process.on("SIGINT", shutdown);
|
||||
process.on("SIGTERM", shutdown);
|
||||
|
||||
process.exit(await app.exited);
|
||||
Reference in New Issue
Block a user