diff --git a/README.md b/README.md index 5dec7e3..e0930bd 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,8 @@ bun run release -- --target linux-x64 # on a Linux x64 machine Artifacts land in `release//miniSCP-Build-.*` — a `.dmg` per mac arch; on Windows a `-Setup.zip` installer **and** a `-portable.zip` (unzip anywhere and run); on Linux a portable `.tar.gz` (unpack and run -`bin/launcher`). The mac-x64 build fetches electrobun's x64 CLI once +`miniSCP.sh`, which sets the NVIDIA workaround then execs `bin/launcher`). +The mac-x64 build fetches electrobun's x64 CLI once into `~/.cache/miniscp/` and runs it under Rosetta so the whole pipeline believes it is on an Intel Mac. CI: `.gitea/workflows/release.yml` runs the same script on a macos + windows runner matrix once act_runners are @@ -259,10 +260,11 @@ its tray icon. - **Tray visibility** depends on the desktop: KDE, XFCE, MATE and Cinnamon show it; GNOME 3.26+ needs the AppIndicator extension. The app works either way. - **Unsigned and unpackaged**: no AppImage, `.deb`, desktop entry or icon - installation — unpack the tarball and run `bin/launcher`. -- Harmless startup warnings (`GLXBadWindow`, `Failed to create GBM buffer`, an - appindicator deprecation notice) are documented in - [gotchas](docs/gotchas.md#linux). + installation — unpack the tarball and run `miniSCP.sh`. +- **NVIDIA GPUs** need `WEBKIT_DISABLE_DMABUF_RENDERER=1` or the window renders + black. Every launch path (dev scripts and the packaged `miniSCP.sh` wrapper) + sets it automatically when an NVIDIA driver is detected — see + [gotchas](docs/gotchas.md#linux) if you add a new way to launch the app. Full dependency table, diagnostics and the first-boot smoke test: [platform notes](docs/platform-notes.md). diff --git a/docs/build-and-release.md b/docs/build-and-release.md index fb54fd3..25f8e0a 100644 --- a/docs/build-and-release.md +++ b/docs/build-and-release.md @@ -62,7 +62,9 @@ Facts the script encodes: `stable-linux-x64-miniSCP.tar.zst` + `update.json`, i.e. the updater feed and nothing installable, so the script's **`.tar.gz` of the assembled app folder** under `build/stable-linux-x64/` IS the release artifact (same best-effort - warn). No AppImage/.deb/.desktop — unpack and run `bin/launcher`. + warn). No AppImage/.deb/.desktop — unpack and run the `miniSCP.sh` wrapper the + script writes beside `bin/launcher` (it applies the NVIDIA/DMABUF workaround, + which the app cannot set for itself — see gotchas.md). - **Linux builds need the AppIndicator dev libraries present to RUN** (not to build) — see platform-notes.md; a packaged tarball started on a machine without them dies at `dlopen`. diff --git a/docs/gotchas.md b/docs/gotchas.md index 2e649c3..ce1d4b8 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -67,9 +67,25 @@ touching the adjacent code. `libasar.so => not found` line in that output is a red herring (it ships beside the wrapper and resolves at load). Install list per distro is in [platform-notes.md](platform-notes.md). -- Benign startup noise on Linux — all non-fatal, the app runs normally: - `X11 Error: GLXBadWindow (code 170)`, `Failed to create GBM buffer of size - WxH: Invalid argument` (compositor/GLX chatter from the GTK webview), and +- **Black window on NVIDIA + `Failed to create GBM buffer of size WxH: Invalid + argument`** (usually with `X11 Error: GLXBadWindow (code 170)`). The app boots, + logs "miniSCP started", reports no error — and paints nothing. WebKitGTK's + DMABUF renderer can't allocate buffers on the NVIDIA proprietary driver; + `WEBKIT_DISABLE_DMABUF_RENDERER=1` is the upstream workaround. + - **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). Setting it in + `src/bun/index.ts` — even in a module imported before `electrobun/bun` — + does nothing. It must be in the environment before the app process starts. + - Hence every launch path injects it: `scripts/linux-gpu.ts` (`launchEnv()`) + for `bun run dev` / `bun run start`, and the `miniSCP.sh` wrapper that + `release.ts` writes beside `bin/launcher` in the tarball. **A new launch + path must call `launchEnv()` or it will render black on NVIDIA.** + - Gated on `/proc/driver/nvidia` so Intel/AMD keep hardware compositing, and + an explicit `WEBKIT_DISABLE_DMABUF_RENDERER` from the user always wins. + - A "does it boot?" log check does NOT catch this — grep for the GBM line, or + look at the window. +- Benign startup noise on Linux — non-fatal, the app runs normally: `libayatana-appindicator is deprecated. Please use libayatana-appindicator-glib` (upstream electrobun's choice, not ours). - **Window-open flash prevention is three-layer** — remove any one and the diff --git a/docs/platform-notes.md b/docs/platform-notes.md index 20a191b..8c2e6cb 100644 --- a/docs/platform-notes.md +++ b/docs/platform-notes.md @@ -96,18 +96,26 @@ either way — the tray is a convenience, and every action it offers exists in-a already macOS-only, and the panel context menus are now HTML on every platform, so nothing is missing at the UI level — but do not add a feature that depends on a native menu. -- **Benign startup noise**: `X11 Error: GLXBadWindow (code 170)`, `Failed to - create GBM buffer of size WxH: Invalid argument`, and a deprecation warning - pointing at `libayatana-appindicator-glib`. None are fatal. +- **NVIDIA renders black without the DMABUF workaround.** WebKitGTK can't + allocate GBM buffers on the proprietary driver, so the window paints nothing + while the log looks perfectly healthy. Every launch path sets + `WEBKIT_DISABLE_DMABUF_RENDERER=1` when `/proc/driver/nvidia` exists — + `scripts/linux-gpu.ts` for dev, the `miniSCP.sh` wrapper in the tarball for + releases. The app can't do it itself (Bun's `process.env` writes don't reach + child processes); see [gotchas.md](gotchas.md#linux). +- **Benign startup noise**: a deprecation warning pointing at + `libayatana-appindicator-glib`. Not fatal. - **Unsigned, unpackaged**: the release artifact is a portable tarball — no AppImage, `.deb`, `.desktop` entry or icon installation. Unpack and run - `bin/launcher`. + `miniSCP.sh` (which execs `bin/launcher`). ## First-Linux-boot smoke test 1. Install the AppIndicator package for your distro (table above), then - `bun run start`. The window opens and the tray icon appears (or doesn't, on - bare GNOME — that's the extension caveat, not a failure). + `bun run start`. The window opens **and actually renders the UI** (a black + window on NVIDIA means the DMABUF workaround didn't apply — check for + `Failed to create GBM buffer` in the log), and the tray icon appears (or + doesn't, on bare GNOME — that's the extension caveat, not a failure). 2. Right-click a file row: menu appears at the pointer, Send to Other Panel is first, Permissions is present, Escape / click-outside / arrow keys + Enter all work, and a right-click near the bottom-right flips the menu instead of @@ -122,7 +130,7 @@ either way — the tray is a convenience, and every action it offers exists in-a 6. Move to Trash lands the item in the desktop's trash; connections.json is 0600. 7. `bun scripts/release.ts --target linux-x64` produces `release/linux-x64/miniSCP-Build-linux-x64.tar.gz`; unpack it elsewhere and - run `bin/launcher`. + run `miniSCP.sh`. ## Windows known issues (upstream electrobun, documented not fixed) diff --git a/package.json b/package.json index 496656c..e583997 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dev:ui": "bun run --cwd ui start", "build:ui": "bun run --cwd ui build", "build": "bun scripts/gen-version.ts && bun run build:ui && bunx electrobun build", - "start": "bun scripts/gen-version.ts && bun run build:ui && bunx electrobun dev", + "start": "bun scripts/gen-version.ts && bun run build:ui && bun scripts/start.ts", "postinstall": "bun scripts/gen-version.ts", "release": "bun scripts/release.ts" }, diff --git a/scripts/dev.ts b/scripts/dev.ts index 11a292a..8991106 100644 --- a/scripts/dev.ts +++ b/scripts/dev.ts @@ -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() { diff --git a/scripts/linux-gpu.ts b/scripts/linux-gpu.ts new file mode 100644 index 0000000..032dcb6 --- /dev/null +++ b/scripts/linux-gpu.ts @@ -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 = {}): Record { + return { + ...process.env, + ...(needsDmabufWorkaround() ? { WEBKIT_DISABLE_DMABUF_RENDERER: "1" } : {}), + ...extra, + }; +} diff --git a/scripts/release.ts b/scripts/release.ts index 34f8c5e..7b35736 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -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//miniSCP-Build-.. -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)]); diff --git a/scripts/start.ts b/scripts/start.ts new file mode 100644 index 0000000..ebd41c9 --- /dev/null +++ b/scripts/start.ts @@ -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);