Skip to content

fix(desktop): improve Linux desktop experience#1208

Closed
riftzen-bit wants to merge 6 commits into
superset-sh:mainfrom
riftzen-bit:dev
Closed

fix(desktop): improve Linux desktop experience#1208
riftzen-bit wants to merge 6 commits into
superset-sh:mainfrom
riftzen-bit:dev

Conversation

@riftzen-bit
Copy link
Copy Markdown

@riftzen-bit riftzen-bit commented Feb 4, 2026

Summary

  • Enable GPU acceleration flags for Linux (rasterization, zero-copy, Vaapi decode, Ozone platform hint)
  • Enable auto-updater for Linux (AppImage native support via electron-updater)
  • Add PipeWire → PulseAudio → ALSA audio fallback chain for notification sounds
  • Add Linux-compatible terminal fonts (Ubuntu Mono, DejaVu Sans Mono, Liberation Mono)
  • Skip port scanning when no terminal sessions are active (performance)
  • Reduce terminal scrollback from 10000 to 5000 lines for lower memory usage
  • Add prefers-reduced-motion CSS media query for accessibility

Test plan

  • Verify GPU acceleration works on Linux (no lag/flicker)
  • Verify SUPERSET_DISABLE_GPU=1 escape hatch disables GPU flags
  • Test auto-updater on Linux AppImage
  • Test notification sounds with PipeWire, PulseAudio, and ALSA
  • Verify terminal renders correctly with Linux font stack
  • Confirm reduced-motion CSS disables animations when system preference is set
  • TypeScript typecheck passes (verified: zero errors)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added native Linux desktop builds (AppImage and DEB formats)
    • GPU performance optimizations for Linux systems
    • System accessibility: reduced motion support for animations
  • Improvements

    • Smart window restoration with proportional scaling for different display sizes
    • Enhanced audio playback with improved fallback options
    • Fixed auto-update availability for Windows users
    • Expanded terminal font compatibility across platforms

Test User added 6 commits February 4, 2026 09:20
Add Linux build job to CI workflow (ubuntu-latest, x64) with AppImage
and deb targets. Update release workflow to create stable-named Linux
artifacts and fix shell fallback from /bin/zsh to /bin/sh for
cross-platform compatibility.
Move GATED_FEATURES and GatedFeature type from usePaywall.ts to
constants.ts to break the circular import chain that caused
"Cannot access 'GATED_FEATURES' before initialization" at runtime.
Remove blanket disableHardwareAcceleration() that forced CPU-only
rendering on Linux. Add GPU optimization flags (gpu-rasterization,
zero-copy, ignore-gpu-blocklist) and provide SUPERSET_DISABLE_GPU=1
escape hatch for users with problematic drivers.
- Add no-drag class to WindowControls so buttons are clickable
- Add Linux CLI command map for external apps (fixes spawn open ENOENT)
- Make trafficLightPosition macOS-only to avoid warnings on Linux
Default to maximized on first launch. When moving from a smaller
screen to a larger one, auto-scale window to 90% of work area if
saved bounds cover less than 60% of current display.
- Enable GPU acceleration flags (rasterization, zero-copy, Vaapi, Ozone)
- Enable auto-updater for Linux (AppImage native support)
- Add PipeWire → PulseAudio → ALSA audio fallback chain
- Add Linux-compatible terminal fonts (Ubuntu Mono, DejaVu, Liberation)
- Skip port scanning when no terminal sessions are active
- Reduce terminal scrollback to 5000 lines for lower memory usage
- Add prefers-reduced-motion CSS for accessibility
Copilot AI review requested due to automatic review settings February 4, 2026 21:05
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

This PR introduces Linux desktop application support alongside existing macOS and Windows builds, adds CI/CD infrastructure for Linux artifact generation and release workflows, implements platform-specific GPU and audio optimizations, reorganizes Paywall module exports, and applies various UX improvements including proportional window scaling, accessibility features, and updated default settings.

Changes

Cohort / File(s) Summary
Linux Build and Release Infrastructure
.github/workflows/build-desktop.yml, .github/workflows/release-desktop.yml, apps/desktop/create-release.sh
Adds new Linux build job mirroring macOS workflow structure with Linux-specific packaging (AppImage, deb); extends release script to create stable-named artifact copies for macOS DMG/ZIP and Linux AppImage/deb; updates create-release.sh output to show per-OS download groupings.
Linux Platform Support - App Integration
apps/desktop/src/lib/electron-app/factories/app/setup.ts, apps/desktop/src/lib/trpc/routers/external/helpers.ts
Replaces unconditional Linux GPU acceleration disable with conditional environment-based control and GPU optimizations (rasterization, zero-copy, Canvas OOP, Vaapi decode); refactors external app command routing with Linux-specific CLI command mappings alongside existing macOS and Windows handling.
Session and Port Management
apps/desktop/src/main/lib/terminal/port-manager.ts
Adds adaptive polling guard to skip periodic port scans when no active sessions exist, preventing unnecessary scans during idle periods.
Audio Playback Improvements
apps/desktop/src/main/lib/notification-sound.ts
Expands Linux audio player fallback sequence from single paplay to prioritized chain: pw-play → paplay → aplay with nested error-driven fallbacks.
Auto-updater Platform Logic
apps/desktop/src/main/lib/auto-updater.ts
Flips platform gating from macOS-only to Windows-only; update checks now skip on Windows (similar to dev mode) instead of skipping when not on macOS; updates user-facing messages accordingly.
Window State and Bounds Management
apps/desktop/src/main/lib/window-state/bounds-validation.ts
Changes default initialization to maximize on primary display; introduces proportional scaling of saved bounds when area ratio is below threshold (< 0.6), preserving aspect ratio and targeting up to 90% of work area.
Terminal and Shell Configuration
apps/desktop/src/main/terminal-host/session.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/.../Terminal/config.ts
Changes HeadlessEmulator default scrollback from 10000 to 5000; changes non-Windows default shell from /bin/zsh to /bin/sh; adds cross-platform monospace font fallbacks (Consolas, Ubuntu Mono, DejaVu Sans Mono, Liberation Mono).
UI and Window Adjustments
apps/desktop/src/main/windows/main.ts, apps/desktop/src/renderer/routes/...WindowControls/WindowControls.tsx
Makes trafficLightPosition property macOS-only; adds "no-drag" CSS class to WindowControls outer container.
Paywall Module Reorganization
apps/desktop/src/renderer/components/Paywall/constants.ts, apps/desktop/src/renderer/components/Paywall/index.ts, apps/desktop/src/renderer/components/Paywall/usePaywall.ts
Relocates GATED_FEATURES and GatedFeature type definitions from usePaywall to new constants module; updates re-export paths in usePaywall and index to maintain public API consistency.
Accessibility Enhancements
apps/desktop/src/renderer/globals.css
Adds global CSS media query for prefers-reduced-motion: reduce to minimize animations and transitions when user accessibility preference is enabled.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • Kitenite

🐰 Hops excitedly through desktop patches so fine,
Linux builds now join the macOS line!
GPU glimmers, audio hops with grace,
Windows sidestepped from the updater's race.
Where once was chaos, refactored code takes flight—
A desktop app that runs on every side! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of the PR—improving the Linux desktop experience—and is supported by multiple substantial changes across GPU acceleration, auto-updater, audio, fonts, and accessibility.
Description check ✅ Passed The PR description covers all required template sections with substantive content: clear summary of changes, type of change (implied as fix/feature), comprehensive test plan with specific verification steps, and additional context about objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the Linux desktop experience by enabling GPU acceleration, auto-updates, and improving cross-platform compatibility for audio, fonts, and external applications.

Changes:

  • Enables GPU acceleration flags for Linux with SUPERSET_DISABLE_GPU=1 escape hatch
  • Enables auto-updater for Linux AppImage distributions via electron-updater
  • Adds Linux audio fallback chain (PipeWire → PulseAudio → ALSA) for notification sounds
  • Adds Linux-compatible terminal fonts and external app CLI commands
  • Optimizes performance by skipping port scanning when no sessions are active
  • Reduces terminal scrollback buffer from 10000 to 5000 lines
  • Adds prefers-reduced-motion CSS for accessibility

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
bun.lock Version bump from 0.0.65 to 0.0.67
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts Adds Linux terminal fonts (Ubuntu Mono, DejaVu Sans Mono, Liberation Mono) and Consolas
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/WindowControls/WindowControls.tsx Adds no-drag class to window controls container
apps/desktop/src/renderer/globals.css Adds prefers-reduced-motion media query for accessibility
apps/desktop/src/renderer/components/Paywall/usePaywall.ts Extracts GATED_FEATURES and GatedFeature to constants.ts
apps/desktop/src/renderer/components/Paywall/index.ts Updates exports to reference constants.ts
apps/desktop/src/renderer/components/Paywall/constants.ts Moves GATED_FEATURES and GatedFeature definitions here
apps/desktop/src/main/windows/main.ts Conditionally applies trafficLightPosition only for macOS
apps/desktop/src/main/terminal-host/session.ts Reduces scrollback from 10000 to 5000 lines, changes default shell from /bin/zsh to /bin/sh
apps/desktop/src/main/lib/window-state/bounds-validation.ts Adds auto-scaling logic for small windows on large screens, maximizes by default on first launch
apps/desktop/src/main/lib/terminal/port-manager.ts Skips port scanning when no sessions are active
apps/desktop/src/main/lib/notification-sound.ts Adds PipeWire → PulseAudio → ALSA fallback chain for Linux audio
apps/desktop/src/main/lib/auto-updater.ts Enables auto-updater for Linux by checking PLATFORM.IS_WINDOWS instead of !PLATFORM.IS_MAC
apps/desktop/src/lib/trpc/routers/external/helpers.ts Adds Linux CLI commands map and Windows support for external app launching
apps/desktop/src/lib/electron-app/factories/app/setup.ts Adds GPU acceleration flags for Linux and SUPERSET_DISABLE_GPU=1 escape hatch
apps/desktop/create-release.sh Updates to display Linux download URLs alongside macOS
.github/workflows/release-desktop.yml Adds Linux artifact handling (AppImage and DEB)
.github/workflows/build-desktop.yml Adds Linux build job for x64 architecture

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +72 to +89
// Allow users with problematic GPU drivers to disable hardware acceleration
if (process.env.SUPERSET_DISABLE_GPU === "1") {
app.disableHardwareAcceleration();
}

// Enable GPU optimizations on Linux
if (PLATFORM.IS_LINUX) {
app.commandLine.appendSwitch("enable-gpu-rasterization");
app.commandLine.appendSwitch("enable-zero-copy");
app.commandLine.appendSwitch("ignore-gpu-blocklist");
// Canvas OOP rasterization offloads canvas rendering to GPU process
app.commandLine.appendSwitch(
"enable-features",
"CanvasOopRasterization,VaapiVideoDecodeLinuxGL",
);
// Auto-detect Wayland vs X11 for native compositor integration
app.commandLine.appendSwitch("ozone-platform-hint", "auto");
}
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GPU acceleration escape hatch (SUPERSET_DISABLE_GPU=1) doesn't fully disable GPU features on Linux. When SUPERSET_DISABLE_GPU=1 is set, disableHardwareAcceleration() is called, but then the Linux-specific GPU switches (enable-gpu-rasterization, enable-zero-copy, etc.) are still applied unconditionally. This creates inconsistent behavior where the user attempts to disable GPU acceleration, but GPU-specific features are still enabled via command-line switches. The Linux GPU optimization block should be wrapped in a condition that checks if GPU acceleration wasn't disabled.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +82
// Windows: use start command
const appName = MAC_APP_NAMES[app];
if (!appName) return null;
return { command: "open", args: ["-a", appName, targetPath] };
return { command: "cmd", args: ["/c", "start", "", appName, targetPath] };
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Windows implementation in getAppCommand uses MAC_APP_NAMES which contains application display names like "Visual Studio Code", "IntelliJ IDEA", etc. These are not valid executable names for Windows. The cmd /c start command expects either executable names (like "code.exe") or properly registered application names. This implementation will fail to open applications on Windows. A WINDOWS_APP_COMMANDS map similar to LINUX_APP_COMMANDS should be created with appropriate Windows executable names or registered application names.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@apps/desktop/src/lib/electron-app/factories/app/setup.ts`:
- Around line 77-89: In the PLATFORM.IS_LINUX block update the GPU flags: remove
the unsupported switches passed via
app.commandLine.appendSwitch("enable-gpu-rasterization"), ("enable-zero-copy")
and ("ignore-gpu-blocklist") and instead use a supported alternative for forcing
discrete GPU when needed (e.g., the Electron-supported
force_high_performance_gpu switch) via app.commandLine.appendSwitch; also update
the enable-features value passed to
app.commandLine.appendSwitch("enable-features", ...) to replace the deprecated
"VaapiVideoDecodeLinuxGL" with the modern names
"AcceleratedVideoDecodeLinuxGL,AcceleratedVideoDecodeLinuxZeroCopyGL" while
keeping CanvasOopRasterization as appropriate. Ensure all changes are made
inside the same PLATFORM.IS_LINUX conditional around
app.commandLine.appendSwitch.

In `@apps/desktop/src/main/lib/notification-sound.ts`:
- Around line 64-74: The nested execFile callbacks currently swallow errors and
shadow the same `error` name; update the Linux audio fallback block (where
execFile is called with "pw-play", "paplay", "aplay" and uses `soundPath`) to
use distinct callback parameter names (e.g., errPw, errPap, errAplay) and, if
the final execFile call fails, log a helpful error message including the
soundPath and which players were attempted (using console.error or the project
logger). Ensure each intermediate failure is either logged or propagated as
needed and avoid reusing the same `error` variable in nested callbacks.

In `@apps/desktop/src/main/lib/window-state/bounds-validation.ts`:
- Around line 73-93: Change scaling to use the work area of the display that
contains the saved bounds rather than the primary display: locate where
workAreaSize is used and replace it with a display-specific work area obtained
from a helper (e.g., getDisplayForBounds or similar) using
savedState.width/height and savedState.x/y; extract the magic numbers into
module-level constants (e.g., MIN_AREA_RATIO = 0.6, WORKAREA_SCALE = 0.9) and
use them instead of 0.6/0.9; and update clampToWorkArea to accept an object
parameter (e.g., clampToWorkArea({ width, height })) and update its call site
here accordingly. Ensure the scaling math (savedAspect,
targetWidth/targetHeight) uses the display-specific workAreaSize and keep the
same aspect-preserving logic.
🧹 Nitpick comments (7)
apps/desktop/src/renderer/components/Paywall/usePaywall.ts (1)

2-7: Consider removing redundant re-exports from this file.

The GATED_FEATURES and GatedFeature are now re-exported from both usePaywall.ts (line 7) and index.ts (lines 2-3). Since index.ts serves as the barrel file for the Paywall module, the re-export here in usePaywall.ts may be unnecessary and creates multiple import paths to the same symbols.

If existing consumers import from usePaywall.ts directly, keeping the re-export maintains backwards compatibility. Otherwise, consider removing line 7 to enforce a single import path through index.ts.

♻️ Optional: Remove redundant re-export
 import { authClient } from "renderer/lib/auth-client";
 import { type GatedFeature, GATED_FEATURES } from "./constants";
 import { paywall } from "./Paywall";
 
 type UserPlan = "free" | "pro";
 
-export { type GatedFeature, GATED_FEATURES };
-
 export function usePaywall() {
apps/desktop/src/main/lib/auto-updater.ts (1)

200-216: Comment references macOS-specific artifact but code now runs on Linux too.

Line 212's comment mentions latest-mac.yml, but with the platform gating change this code now also runs on Linux, where electron-updater will look for latest-linux.yml. The comment should be updated to be platform-agnostic.

📝 Suggested comment update
 	// Use generic provider with explicit feed URL
-	// This ensures we always fetch latest-mac.yml from the correct GitHub release
+	// This ensures we always fetch the platform-specific yml (latest-mac.yml or latest-linux.yml) from the correct GitHub release
 	autoUpdater.setFeedURL({
apps/desktop/src/main/terminal-host/session.ts (2)

135-140: Extract the scrollback default to a named constant.

Avoid inline magic numbers so the default can be tuned in one place.
As per coding guidelines, extract hardcoded magic numbers, strings, and enums to named constants at module top instead of leaving them inline in logic.

♻️ Suggested change
+const DEFAULT_SCROLLBACK_LINES = 5_000;
 ...
 		this.emulator = new HeadlessEmulator({
 			cols: options.cols,
 			rows: options.rows,
-			scrollback: options.scrollbackLines ?? 5000,
+			scrollback: options.scrollbackLines ?? DEFAULT_SCROLLBACK_LINES,
 		});

939-943: Promote the /bin/sh fallback to a module constant.

This avoids a hardcoded path literal inside logic and makes future platform overrides safer.
As per coding guidelines, extract hardcoded magic numbers, strings, and enums to named constants at module top instead of leaving them inline in logic.

♻️ Suggested change
+const DEFAULT_FALLBACK_SHELL = "/bin/sh";
 ...
 	private getDefaultShell(): string {
 		if (process.platform === "win32") {
 			return process.env.COMSPEC || "cmd.exe";
 		}
-		return process.env.SHELL || "/bin/sh";
+		return process.env.SHELL || DEFAULT_FALLBACK_SHELL;
 	}
apps/desktop/src/main/windows/main.ts (1)

85-87: Consider using PLATFORM.IS_MAC for consistency.

The platform check works correctly, but the codebase has a PLATFORM.IS_MAC constant in shared/constants.ts that could be used here for consistency with other platform checks (e.g., line 91 in setup.ts uses PLATFORM.IS_WINDOWS).

♻️ Suggested refactor
+import { PLATFORM } from "shared/constants";
+
 // In createWindow options:
-		...(process.platform === "darwin"
+		...(PLATFORM.IS_MAC
 			? { trafficLightPosition: { x: 16, y: 16 } }
 			: {}),
apps/desktop/src/lib/trpc/routers/external/helpers.ts (2)

32-57: Linux app commands mapping is well-structured.

Good approach marking macOS-only apps (xcode, iterm, warp, appcode) as null and using standard CLI commands for cross-platform apps.

One note: x-terminal-emulator (line 42) is Debian/Ubuntu specific (alternatives system). On Fedora, Arch, etc., this command won't exist. Consider adding a fallback chain or detecting the available terminal emulator.

💡 Optional: Add terminal fallback detection
// Could add a helper to detect available terminal:
// gnome-terminal, konsole, xfce4-terminal, xterm as fallbacks

67-82: Platform branching logic is clear and maintainable.

The explicit platform checks with early returns are readable. However, note that the Windows fallback (line 80) uses MAC_APP_NAMES which contains macOS-specific application names like "Visual Studio Code". While the Windows start command may resolve these through shell path lookup, this is fragile and may not work for all applications.

Consider adding a WINDOWS_APP_COMMANDS mapping similar to LINUX_APP_COMMANDS for more reliable Windows support:

💡 Suggested improvement for Windows
+/** Map of app IDs to their Windows executable names */
+const WINDOWS_APP_COMMANDS: Record<ExternalApp, string | null> = {
+	finder: null,
+	vscode: "code",
+	"vscode-insiders": "code-insiders",
+	cursor: "cursor",
+	// ... etc
+};

 // Windows: use start command
-const appName = MAC_APP_NAMES[app];
-if (!appName) return null;
-return { command: "cmd", args: ["/c", "start", "", appName, targetPath] };
+const command = WINDOWS_APP_COMMANDS[app];
+if (!command) return null;
+return { command: "cmd", args: ["/c", command, targetPath] };

Comment on lines +77 to +89
// Enable GPU optimizations on Linux
if (PLATFORM.IS_LINUX) {
app.commandLine.appendSwitch("enable-gpu-rasterization");
app.commandLine.appendSwitch("enable-zero-copy");
app.commandLine.appendSwitch("ignore-gpu-blocklist");
// Canvas OOP rasterization offloads canvas rendering to GPU process
app.commandLine.appendSwitch(
"enable-features",
"CanvasOopRasterization,VaapiVideoDecodeLinuxGL",
);
// Auto-detect Wayland vs X11 for native compositor integration
app.commandLine.appendSwitch("ozone-platform-hint", "auto");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find package.json in the desktop app to check Electron version
find . -name "package.json" -type f | head -20

Repository: superset-sh/superset

Length of output: 586


🏁 Script executed:

# Search for electron in package files
rg "electron" --type json -l | head -10

Repository: superset-sh/superset

Length of output: 163


🏁 Script executed:

cat ./apps/desktop/package.json | rg "electron" -A 1 -B 1

Repository: superset-sh/superset

Length of output: 1388


🌐 Web query:

Electron 39 Chromium version compatibility

💡 Result:

Electron 39.x is built on Chromium 142 (Chrome/Chromium “M142”). (endoflife.date)

Exact Chromium patch versions depend on the Electron 39 minor/patch release, for example:

To verify for any specific 39.x version, check its entry on Electron Releases (it lists the bundled Chromium/Node/V8). (releases.electronjs.org)

Citations:


🌐 Web query:

VaapiVideoDecodeLinuxGL Chromium flag electron 39

💡 Result:

Electron 39 ships with Chromium 142.0.7444.52 (releases.electronjs.org), so you’re dealing with “modern” Linux VA-API behavior/flags.

What VaapiVideoDecodeLinuxGL is

--enable-features=VaapiVideoDecodeLinuxGL is the Chromium feature-flag that enables VA-API hardware video decoding on Linux via the GL path (best-effort / not officially supported). Chromium’s own VA-API doc lists it as the decode switch and also calls out the commonly-paired GL/ANGLE switches: --use-gl=angle and --use-angle=gl. (chromium.googlesource.com)

How to pass it in Electron 39

Add the switches before app.whenReady():

const { app } = require('electron');

app.commandLine.appendSwitch('enable-features', 'VaapiVideoDecodeLinuxGL');
app.commandLine.appendSwitch('use-gl', 'angle');
app.commandLine.appendSwitch('use-angle', 'gl');
// optional, sometimes needed:
// app.commandLine.appendSwitch('ignore-gpu-blocklist');

app.whenReady().then(() => { /* create windows */ });

Important: flag rename in newer Chromium

Multiple Linux guides report that starting around Chromium 131, VA-API decode flags were effectively renamed from Vaapi* to Accelerated* (and --use-gl=angle --use-angle=gl may no longer be required on some setups). The commonly suggested replacements are:

  • --enable-features=AcceleratedVideoDecodeLinuxGL,AcceleratedVideoDecodeLinuxZeroCopyGL (plus AcceleratedVideoEncoder if you care about encode)
  • Sometimes also VaapiIgnoreDriverChecks (esp. non-Intel / finicky configs) (dev.to) (wiki.debian.org)

So on Electron 39, if VaapiVideoDecodeLinuxGL doesn’t do anything, try:

app.commandLine.appendSwitch(
  'enable-features',
  'AcceleratedVideoDecodeLinuxGL,AcceleratedVideoDecodeLinuxZeroCopyGL'
);

Verify it worked

Use Chromium’s recommended check:

  • chrome://media-internals → for the playing video, look for video_decoder showing a GPU/VAAPI decoder. (chromium.googlesource.com)

Citations:


🌐 Web query:

enable-gpu-rasterization enable-zero-copy ignore-gpu-blocklist electron 39 deprecated

💡 Result:

Those three flags are Chromium “about:flags”/GPU tuning switches, not Electron APIs. In Electron v39, they’re commonly reported as “deprecated/ignored” because Electron only guarantees a limited set of supported command-line switches, and unsupported switches “will have no effect” (even if Chromium used to accept them). [1]

What to do instead:

  • Don’t rely on --enable-gpu-rasterization, --enable-zero-copy, --ignore-gpu-blocklist for Electron v39—they’re not in Electron’s supported-switch list, so behavior can change or become a no-op. [1]
  • If your goal is “use the discrete GPU,” use Electron-supported switches like --force_high_performance_gpu (or the low-power counterpart). [1]
  • If you’re trying to tweak Chromium rendering behavior, Electron recommends discovering the current Chromium-in-Electron switches via Chromium’s flags/version pages and noting many modern toggles are now --enable-features=... entries rather than standalone switches. [1]
  • For background on GPU rasterization being a Chromium feature primarily controlled via chrome://flags/#enable-gpu-rasterization / related mechanisms, see Chromium’s graphics docs. [2]

Sources: Electron “Supported Command Line Switches” (unsupported switches have no effect; guidance on Chromium flags) [1]; Chromium graphics doc on enabling GPU rasterization [2].


Remove or replace unsupported GPU flags for Electron 39.

The flags enable-gpu-rasterization, enable-zero-copy, and ignore-gpu-blocklist are not in Electron's supported command-line switches list and will have no effect in Electron 39. Remove these or replace with Electron-supported alternatives like --force_high_performance_gpu if targeting discrete GPU usage.

Additionally, VaapiVideoDecodeLinuxGL is deprecated in Chromium 142 (bundled with Electron 39.x) in favor of AcceleratedVideoDecodeLinuxGL and AcceleratedVideoDecodeLinuxZeroCopyGL. Update the enable-features flag to use the modern accelerated video decode names for better compatibility.

Current code
app.commandLine.appendSwitch("enable-gpu-rasterization");
app.commandLine.appendSwitch("enable-zero-copy");
app.commandLine.appendSwitch("ignore-gpu-blocklist");
app.commandLine.appendSwitch(
	"enable-features",
	"CanvasOopRasterization,VaapiVideoDecodeLinuxGL",
);
🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/electron-app/factories/app/setup.ts` around lines 77 -
89, In the PLATFORM.IS_LINUX block update the GPU flags: remove the unsupported
switches passed via app.commandLine.appendSwitch("enable-gpu-rasterization"),
("enable-zero-copy") and ("ignore-gpu-blocklist") and instead use a supported
alternative for forcing discrete GPU when needed (e.g., the Electron-supported
force_high_performance_gpu switch) via app.commandLine.appendSwitch; also update
the enable-features value passed to
app.commandLine.appendSwitch("enable-features", ...) to replace the deprecated
"VaapiVideoDecodeLinuxGL" with the modern names
"AcceleratedVideoDecodeLinuxGL,AcceleratedVideoDecodeLinuxZeroCopyGL" while
keeping CanvasOopRasterization as appropriate. Ensure all changes are made
inside the same PLATFORM.IS_LINUX conditional around
app.commandLine.appendSwitch.

Comment on lines +64 to 74
// Linux - try common audio players in order of preference:
// pw-play (PipeWire), paplay (PulseAudio), aplay (ALSA)
execFile("pw-play", [soundPath], (error) => {
if (error) {
execFile("aplay", [soundPath]);
execFile("paplay", [soundPath], (error) => {
if (error) {
execFile("aplay", [soundPath]);
}
});
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add error logging when all audio players fail.

If all three audio players fail, the error is silently swallowed. This makes debugging audio issues on Linux unnecessarily difficult. Additionally, consider using distinct parameter names to avoid shadowing error in the nested callbacks.

As per coding guidelines: "Never swallow errors silently; at minimum log errors with context before rethrowing or handling them explicitly."

🔊 Proposed fix to add error logging
 		// Linux - try common audio players in order of preference:
 		// pw-play (PipeWire), paplay (PulseAudio), aplay (ALSA)
-		execFile("pw-play", [soundPath], (error) => {
-			if (error) {
-				execFile("paplay", [soundPath], (error) => {
-					if (error) {
-						execFile("aplay", [soundPath]);
+		execFile("pw-play", [soundPath], (pwError) => {
+			if (pwError) {
+				execFile("paplay", [soundPath], (paError) => {
+					if (paError) {
+						execFile("aplay", [soundPath], (alsaError) => {
+							if (alsaError) {
+								console.warn(
+									`[notification-sound] All audio players failed for: ${soundPath}`
+								);
+							}
+						});
 					}
 				});
 			}
 		});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Linux - try common audio players in order of preference:
// pw-play (PipeWire), paplay (PulseAudio), aplay (ALSA)
execFile("pw-play", [soundPath], (error) => {
if (error) {
execFile("aplay", [soundPath]);
execFile("paplay", [soundPath], (error) => {
if (error) {
execFile("aplay", [soundPath]);
}
});
}
});
// Linux - try common audio players in order of preference:
// pw-play (PipeWire), paplay (PulseAudio), aplay (ALSA)
execFile("pw-play", [soundPath], (pwError) => {
if (pwError) {
execFile("paplay", [soundPath], (paError) => {
if (paError) {
execFile("aplay", [soundPath], (alsaError) => {
if (alsaError) {
console.warn(
`[notification-sound] All audio players failed for: ${soundPath}`
);
}
});
}
});
}
});
🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/notification-sound.ts` around lines 64 - 74, The
nested execFile callbacks currently swallow errors and shadow the same `error`
name; update the Linux audio fallback block (where execFile is called with
"pw-play", "paplay", "aplay" and uses `soundPath`) to use distinct callback
parameter names (e.g., errPw, errPap, errAplay) and, if the final execFile call
fails, log a helpful error message including the soundPath and which players
were attempted (using console.error or the project logger). Ensure each
intermediate failure is either logged or propagated as needed and avoid reusing
the same `error` variable in nested callbacks.

Comment on lines +73 to +93
let { width, height } = clampToWorkArea(savedState.width, savedState.height);

// Scale up if saved bounds are much smaller than current work area
// This handles moving from a small screen to a large screen
const areaRatio =
(width * height) / (workAreaSize.width * workAreaSize.height);
if (areaRatio < 0.6) {
// Saved window covers less than 60% of current screen → scale up
// Use 90% of work area while maintaining aspect ratio
const savedAspect = width / height;
const targetWidth = Math.round(workAreaSize.width * 0.9);
const targetHeight = Math.round(targetWidth / savedAspect);

if (targetHeight <= workAreaSize.height) {
width = targetWidth;
height = targetHeight;
} else {
height = Math.round(workAreaSize.height * 0.9);
width = Math.round(height * savedAspect);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Scale against the display that contains the saved bounds.

Right now scaling is based on the primary display’s work area. If the window was saved on a smaller secondary display, the scaled bounds can exceed that display and be treated as off-screen, causing an unexpected recenter onto the primary display. Consider deriving the work area from the display matching the saved bounds before applying the scale. Also, please extract the scaling thresholds to constants and switch clampToWorkArea to object params for consistency.

🔧 Suggested update (display-aware scaling + constants + object params)
 const MIN_VISIBLE_OVERLAP = 50;
 const MIN_WINDOW_SIZE = 400;
+const SCALE_UP_AREA_RATIO_THRESHOLD = 0.6;
+const SCALE_UP_WORKAREA_RATIO = 0.9;

 function clampToWorkArea(
-	width: number,
-	height: number,
+	{ width, height }: { width: number; height: number },
 ): { width: number; height: number } {
 	const { workAreaSize } = screen.getPrimaryDisplay();
 	return {
 		width: Math.min(Math.max(width, MIN_WINDOW_SIZE), workAreaSize.width),
 		height: Math.min(Math.max(height, MIN_WINDOW_SIZE), workAreaSize.height),
 	};
 }

 export function getInitialWindowBounds(
 	savedState: WindowState | null,
 ): InitialWindowBounds {
-	const { workAreaSize } = screen.getPrimaryDisplay();
+	const primaryWorkArea = screen.getPrimaryDisplay().workAreaSize;

 	// No saved state → maximize by default for best first-launch experience
 	if (!savedState) {
 		return {
-			width: workAreaSize.width,
-			height: workAreaSize.height,
+			width: primaryWorkArea.width,
+			height: primaryWorkArea.height,
 			center: true,
 			isMaximized: true,
 		};
 	}

-	let { width, height } = clampToWorkArea(savedState.width, savedState.height);
+	let { width, height } = clampToWorkArea({
+		width: savedState.width,
+		height: savedState.height,
+	});
+	const baseBounds: Rectangle = {
+		x: savedState.x,
+		y: savedState.y,
+		width,
+		height,
+	};
+	const { workAreaSize } = screen.getDisplayMatching(baseBounds);

 	// Scale up if saved bounds are much smaller than current work area
 	// This handles moving from a small screen to a large screen
 	const areaRatio =
 		(width * height) / (workAreaSize.width * workAreaSize.height);
-	if (areaRatio < 0.6) {
+	if (areaRatio < SCALE_UP_AREA_RATIO_THRESHOLD) {
 		// Saved window covers less than 60% of current screen → scale up
 		// Use 90% of work area while maintaining aspect ratio
 		const savedAspect = width / height;
-		const targetWidth = Math.round(workAreaSize.width * 0.9);
+		const targetWidth = Math.round(
+			workAreaSize.width * SCALE_UP_WORKAREA_RATIO,
+		);
 		const targetHeight = Math.round(targetWidth / savedAspect);

 		if (targetHeight <= workAreaSize.height) {
 			width = targetWidth;
 			height = targetHeight;
 		} else {
-			height = Math.round(workAreaSize.height * 0.9);
+			height = Math.round(
+				workAreaSize.height * SCALE_UP_WORKAREA_RATIO,
+			);
 			width = Math.round(height * savedAspect);
 		}
 	}

As per coding guidelines, Extract hardcoded magic numbers, strings, and enums to named constants at module top instead of leaving them inline in logic; Use object parameters for functions with 2 or more parameters instead of positional arguments.

🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/window-state/bounds-validation.ts` around lines 73
- 93, Change scaling to use the work area of the display that contains the saved
bounds rather than the primary display: locate where workAreaSize is used and
replace it with a display-specific work area obtained from a helper (e.g.,
getDisplayForBounds or similar) using savedState.width/height and
savedState.x/y; extract the magic numbers into module-level constants (e.g.,
MIN_AREA_RATIO = 0.6, WORKAREA_SCALE = 0.9) and use them instead of 0.6/0.9; and
update clampToWorkArea to accept an object parameter (e.g., clampToWorkArea({
width, height })) and update its call site here accordingly. Ensure the scaling
math (savedAspect, targetWidth/targetHeight) uses the display-specific
workAreaSize and keep the same aspect-preserving logic.

@riftzen-bit riftzen-bit closed this by deleting the head repository Feb 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants