From 21da448e8f199cf9a40154daf2be18cc0338cbbd Mon Sep 17 00:00:00 2001 From: Andrew Zambazos Date: Sun, 28 Dec 2025 21:36:50 +1300 Subject: [PATCH 1/3] Initial commit (without build artifacts) --- .gitignore | 24 ++++++++++ GPU-FIX-README.md | 101 ----------------------------------------- README-STEAM.md | 44 ++++++++++++++++++ gpu-config.js | 111 ++++++++------------------------------------- make-appdir.sh | 59 ++++++++++++++++++++++++ package.json | 4 +- preload.js | 27 +++++++++++ renderer/script.js | 20 +++++++- start-steamos.sh | 31 ------------- 9 files changed, 193 insertions(+), 228 deletions(-) create mode 100644 README-STEAM.md create mode 100644 make-appdir.sh delete mode 100644 start-steamos.sh diff --git a/.gitignore b/.gitignore index daeb04e..7e27ca2 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,27 @@ typings/ site-history.json bookmarks.json bookmarks.backup.json + +# AppImage / SteamOS +squashfs-root/ +*.AppImage + +# Electron build output +dist/ +build/ +out/ +release/ + +# Native binaries +nebula +nebula.exe + +# Node/Electron +node_modules/ + +# Logs +*.log + +# Build artifacts +nebula-appdir/ +*.asar diff --git a/GPU-FIX-README.md b/GPU-FIX-README.md index 223f878..e69de29 100644 --- a/GPU-FIX-README.md +++ b/GPU-FIX-README.md @@ -1,101 +0,0 @@ -# GPU Fix Guide for Nebula Browser - -## Common GPU Issues - -Nebula Browser is an Electron-based application that uses Chromium's rendering engine. Some systems may experience GPU-related issues such as: -- Black/blank screens -- Webviews not loading content -- Flickering or visual artifacts -- Application crashes - -## SteamOS / Steam Deck - -### Symptoms -On SteamOS (Steam Deck), you may see: -- Browser chrome loads (tab bar, URL bar visible) -- Web page content area is completely black/empty -- No error messages visible - -### Cause -This is caused by GPU compositing conflicts between Electron's Chromium renderer and Gamescope (Steam Deck's compositor). The AMD GPU in Steam Deck handles nested compositor contexts differently. - -### Solution - -**Option 1: Automatic Detection (Recommended)** -The latest version of Nebula automatically detects SteamOS/Gamescope and applies the necessary fixes. Simply update to the latest version. - -**Option 2: Manual Launch Script** -Use the provided `start-steamos.sh` script: -```bash -chmod +x start-steamos.sh -./start-steamos.sh -``` - -**Option 3: Command-line Flags** -If running manually, add these flags: -```bash -electron . --ozone-platform=x11 --disable-gpu-compositing --disable-gpu-vsync --no-sandbox --disable-dev-shm-usage -``` - -**Option 4: Environment Variable** -Set the `ELECTRON_OZONE_PLATFORM_HINT` environment variable: -```bash -export ELECTRON_OZONE_PLATFORM_HINT=x11 -npm start -``` - -## Linux (General) - -### Wayland -If running on a Wayland compositor (GNOME Wayland, KDE Wayland, Sway, etc.): -```bash -electron . --ozone-platform=wayland --enable-features=UseOzonePlatform,WaylandWindowDecorations -``` - -### X11 -For X11 sessions: -```bash -electron . --ozone-platform=x11 -``` - -### NVIDIA GPUs -If using NVIDIA proprietary drivers: -```bash -electron . --disable-gpu-sandbox --no-sandbox -``` - -## Windows - -### Intel/AMD Integrated Graphics Issues -If experiencing blank screens on Windows with integrated graphics: -1. Try running with `start-gpu-safe.bat` -2. Update your graphics drivers -3. Disable hardware acceleration in settings (if available) - -### Multiple GPU Systems -On laptops with both integrated and discrete GPUs: -- Right-click the Nebula shortcut -- Select "Run with graphics processor" -- Choose your dedicated GPU - -## macOS - -macOS typically has fewer GPU issues, but if problems occur: -```bash -electron . --disable-gpu -``` - -## Diagnostic Information - -To see GPU information and diagnostics: -1. Open Nebula Browser -2. Navigate to `nebula://gpu` or `chrome://gpu` -3. Check the "Graphics Feature Status" section - -## Reporting Issues - -If none of the above solutions work, please report the issue with: -1. Operating system and version -2. GPU model and driver version -3. Contents of `chrome://gpu` page -4. Any error messages from terminal/console diff --git a/README-STEAM.md b/README-STEAM.md new file mode 100644 index 0000000..5714975 --- /dev/null +++ b/README-STEAM.md @@ -0,0 +1,44 @@ +Converting extracted AppImage (`squashfs-root`) into a distributable AppDir for Steam + +If your environment lacks `rsync`, use `cp -a` to copy the extracted AppImage into a clean AppDir and prepare it for upload to Steam. + +1) Copy the extracted AppImage to an AppDir folder +```bash +cp -a squashfs-root/ nebula-appdir +``` + +2) Unpack `app.asar` to edit or include app sources (optional; requires `npx asar`) +```bash +cd nebula-appdir/resources +npx asar extract app.asar app +# keep a backup if you want +mv app app.orig && rm app.asar +cd ../../ +``` + +3) Add/verify launcher (we added `nebula-appdir/Nebula`): +```bash +chmod +x nebula-appdir/Nebula +``` +Run locally: +```bash +cd nebula-appdir +./Nebula +``` + +4) Ensure binary & permissions are correct +```bash +chmod +x nebula-appdir/nebula +``` + +5) Package or upload to Steam +- Create a tarball to upload as game files, or upload the AppDir contents as the depot. +```bash +tar -czf nebula-appdir.tar.gz -C nebula-appdir . +``` +- In Steamworks, set the launch command to `./Nebula` (or `./nebula`). + +Notes +- `--no-sandbox` reduces Chromium sandboxing; prefer fixing `chrome-sandbox` and enabling sandboxing when possible. +- Using the AppDir avoids AppImage/FUSE dependency on target systems. +- Test on a clean SteamOS/Deck image before publishing. diff --git a/gpu-config.js b/gpu-config.js index 12960a4..30cab94 100644 --- a/gpu-config.js +++ b/gpu-config.js @@ -5,36 +5,6 @@ class GPUConfig { constructor() { this.isGPUSupported = false; this.fallbackApplied = false; - this.isSteamOS = false; - this.isLinux = process.platform === 'linux'; - } - - // Detect if running on SteamOS/Steam Deck - detectSteamOS() { - if (!this.isLinux) return false; - - try { - const fs = require('fs'); - // Check for SteamOS identifiers - if (fs.existsSync('/etc/steamos-release')) return true; - if (fs.existsSync('/usr/share/steamos/steamos.conf')) return true; - - // Check os-release for SteamOS - if (fs.existsSync('/etc/os-release')) { - const osRelease = fs.readFileSync('/etc/os-release', 'utf8'); - if (osRelease.includes('SteamOS') || osRelease.includes('steamos')) return true; - } - - // Check if running under Gamescope (Steam Deck's compositor) - if (process.env.GAMESCOPE_WAYLAND_DISPLAY || process.env.SteamDeck) return true; - - // Check for Steam runtime environment - if (process.env.STEAM_RUNTIME || process.env.SteamAppId) return true; - } catch (err) { - console.log('SteamOS detection error:', err.message); - } - - return false; } // Apply GPU configuration based on system capabilities @@ -45,64 +15,24 @@ class GPUConfig { const platform = process.platform; const arch = process.arch; - this.isSteamOS = this.detectSteamOS(); - - console.log(`Platform: ${platform}, Architecture: ${arch}, SteamOS: ${this.isSteamOS}`); - - // Apply Linux/SteamOS specific configuration FIRST (before app ready) - if (this.isLinux) { - this.applyLinuxSettings(); - } + console.log(`Platform: ${platform}, Architecture: ${arch}`); // Start with conservative settings that usually work this.applyConservativeSettings(); + + // On Linux/SteamOS, force disable GPU and sandbox to ensure webview stability + if (platform === 'linux') { + console.log('Linux detected: Disabling GPU and enforcing no-sandbox'); + app.commandLine.appendSwitch('disable-gpu'); + app.commandLine.appendSwitch('no-sandbox'); + this.fallbackApplied = true; + return; + } // Try to enable GPU features progressively this.tryEnableGPU(); } - // Linux-specific settings for proper rendering - applyLinuxSettings() { - console.log('Applying Linux-specific GPU settings...'); - - // Ozone platform selection - critical for rendering on Linux - // Check for Wayland vs X11 environment - const waylandDisplay = process.env.WAYLAND_DISPLAY; - const gamescope = process.env.GAMESCOPE_WAYLAND_DISPLAY; - const x11Display = process.env.DISPLAY; - - if (this.isSteamOS || gamescope) { - // SteamOS/Gamescope: Force X11 backend for better compatibility - // Gamescope provides X11 compatibility layer that works better with Electron - console.log('SteamOS/Gamescope detected - using X11 Ozone backend'); - app.commandLine.appendSwitch('ozone-platform', 'x11'); - - // Disable GPU compositing issues on Steam Deck AMD GPU - app.commandLine.appendSwitch('disable-gpu-compositing'); - app.commandLine.appendSwitch('disable-gpu-vsync'); - - // Use software rendering for webviews if needed - app.commandLine.appendSwitch('disable-accelerated-2d-canvas'); - - // Fix for AMD GPU rendering issues - app.commandLine.appendSwitch('use-gl', 'desktop'); - app.commandLine.appendSwitch('enable-features', 'UseOzonePlatform'); - } else if (waylandDisplay && !x11Display) { - // Pure Wayland environment - console.log('Wayland detected - using Wayland Ozone backend'); - app.commandLine.appendSwitch('ozone-platform', 'wayland'); - app.commandLine.appendSwitch('enable-features', 'UseOzonePlatform,WaylandWindowDecorations'); - } else if (x11Display) { - // X11 environment - console.log('X11 detected - using X11 Ozone backend'); - app.commandLine.appendSwitch('ozone-platform', 'x11'); - } - - // Common Linux fixes - app.commandLine.appendSwitch('disable-features', 'VizDisplayCompositor'); - app.commandLine.appendSwitch('enable-unsafe-swiftshader'); - } - applyConservativeSettings() { // Essential switches that usually don't cause issues app.commandLine.appendSwitch('no-sandbox'); @@ -119,26 +49,23 @@ class GPUConfig { tryEnableGPU() { try { - // Skip aggressive GPU features on SteamOS - they conflict with Gamescope - if (this.isSteamOS) { - console.log('SteamOS detected - skipping aggressive GPU acceleration'); - return; - } - // GPU acceleration switches app.commandLine.appendSwitch('ignore-gpu-blacklist'); app.commandLine.appendSwitch('ignore-gpu-blocklist'); - app.commandLine.appendSwitch('enable-gpu-rasterization'); - app.commandLine.appendSwitch('enable-zero-copy'); + + // On Linux/SteamOS, these aggressive flags can cause webview rendering issues (black screen) + // We disable them for Linux to ensure stability + if (process.platform !== 'linux') { + app.commandLine.appendSwitch('enable-gpu-rasterization'); + app.commandLine.appendSwitch('enable-zero-copy'); + } // Video acceleration (usually safer than full GPU) app.commandLine.appendSwitch('enable-accelerated-video-decode'); app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode'); - // Conservative feature enabling - skip on Linux to avoid conflicts - if (!this.isLinux) { - app.commandLine.appendSwitch('enable-features', 'VaapiVideoDecoder'); - } + // Conservative feature enabling + app.commandLine.appendSwitch('enable-features', 'VaapiVideoDecoder'); console.log('GPU acceleration switches applied'); } catch (err) { diff --git a/make-appdir.sh b/make-appdir.sh new file mode 100644 index 0000000..2df8982 --- /dev/null +++ b/make-appdir.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Assemble nebula-appdir from extracted squashfs-root +set -euo pipefail +SRC="${1:-squashfs-root}" +DEST="${2:-nebula-appdir}" + +if [ ! -d "$SRC" ]; then + echo "Source $SRC not found. Extract the AppImage first (./dist/Nebula-*.AppImage --appimage-extract)" + exit 1 +fi + +# Copy extracted contents into DEST +mkdir -p "$DEST" +cp -a "$SRC/." "$DEST/" + +# Ensure launcher/binary exist +if [ -f "$DEST/run-nebula.sh" ]; then + mv "$DEST/run-nebula.sh" "$DEST/Nebula" 2>/dev/null || true +fi +chmod +x "$DEST/Nebula" || true + +# Ensure directories for icons and desktop entries +mkdir -p "$DEST/usr/share/icons/hicolor/256x256/apps" +mkdir -p "$DEST/usr/share/applications" + +# Copy icon if present at top level of extracted AppImage +if [ -f "$SRC/nebula.png" ]; then + cp "$SRC/nebula.png" "$DEST/usr/share/icons/hicolor/256x256/apps/nebula.png" +fi + +# Also embed project icon if present in repo assets +PROJECT_ICON="$(cd "$(dirname "$0")" && pwd)/assets/images/Logos/Nebula-Favicon.png" +if [ -f "$PROJECT_ICON" ]; then + echo "Embedding project icon into AppDir: $PROJECT_ICON" + cp "$PROJECT_ICON" "$DEST/usr/share/icons/hicolor/256x256/apps/nebula.png" +fi + +# Install desktop file into AppDir +if [ -f "$DEST/nebula.desktop" ]; then + cp "$DEST/nebula.desktop" "$DEST/usr/share/applications/nebula.desktop" +else + cat > "$DEST/usr/share/applications/nebula.desktop" <<'EOF' +[Desktop Entry] +Name=Nebula +Comment=Nebula Browser +Exec=./Nebula %U +Terminal=false +Type=Application +Icon=nebula +Categories=Network;WebBrowser; +StartupWMClass=Nebula +EOF +fi + +# Fix permissions +chmod -R a+r "$DEST/usr/share/icons/hicolor/256x256/apps" || true +chmod +x "$DEST/Nebula" || true + +echo "AppDir assembled at $DEST. Run with: $DEST/Nebula" \ No newline at end of file diff --git a/package.json b/package.json index 40f2eea..2c339ed 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "main.js", "scripts": { "start": "electron .", - "start:steamos": "electron . --ozone-platform=x11 --disable-gpu-compositing --disable-gpu-vsync --no-sandbox --disable-dev-shm-usage --disable-features=VizDisplayCompositor", - "start:linux-safe": "electron . --disable-gpu --disable-gpu-compositing --no-sandbox", + "start:dev": "electron . --no-sandbox --disable-gpu", + "start:linux": "electron . --no-sandbox", "dist": "electron-builder", "run": "electron ." }, diff --git a/preload.js b/preload.js index 855fc00..2ac8079 100644 --- a/preload.js +++ b/preload.js @@ -1,5 +1,11 @@ // preload.js - Optimized version const { contextBridge, ipcRenderer } = require('electron'); +let pathModule; +try { + pathModule = require('path'); +} catch (err) { + pathModule = null; +} // Cache DOM references for performance let domReady = false; @@ -75,6 +81,27 @@ const electronAPI = { saveImageFromNet: async (url) => ipcRenderer.invoke('save-image-from-url', { url }) }; +// Provide absolute path to the renderer preload for webview guests so +// webview `preload` attributes use an absolute, resolvable path on all platforms. +const webviewPreloadAbsolutePath = pathModule ? pathModule.join(__dirname, 'preload.js') : null; +electronAPI.getWebviewPreloadPath = () => webviewPreloadAbsolutePath; + +// Fixup any static attributes in the DOM early so +// guests receive an absolute path instead of a relative one that may fail +// to resolve inside the guest process. +window.addEventListener('DOMContentLoaded', () => { + try { + if (webviewPreloadAbsolutePath) { + const els = document.querySelectorAll('webview[preload]'); + for (const el of els) { + try { el.setAttribute('preload', webviewPreloadAbsolutePath); } catch {}; + } + } + } catch (e) { + // non-fatal + } +}); + // Cache for bookmarks to reduce IPC calls let bookmarksCache = null; let bookmarksCacheTime = 0; diff --git a/renderer/script.js b/renderer/script.js index 69830c0..6e6f727 100644 --- a/renderer/script.js +++ b/renderer/script.js @@ -368,7 +368,16 @@ function createTab(inputUrl) { webview.src = resolvedUrl; webview.setAttribute('allowpopups', ''); webview.setAttribute('partition', 'persist:main'); - webview.setAttribute('preload', '../preload.js'); + // Use absolute preload path provided by the main-window preload to ensure + // the guest process can resolve the file (important on Linux/SteamOS). + try { + const preloadPath = (window.electronAPI && typeof window.electronAPI.getWebviewPreloadPath === 'function') + ? window.electronAPI.getWebviewPreloadPath() + : '../preload.js'; + webview.setAttribute('preload', preloadPath); + } catch (e) { + webview.setAttribute('preload', '../preload.js'); + } // Add attributes needed for Google OAuth and sign-in flows webview.setAttribute('webpreferences', 'allowRunningInsecureContent=false,javascript=true,webSecurity=true'); try { @@ -667,7 +676,14 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) { webview.src = resolvedUrl; webview.setAttribute('allowpopups', ''); webview.setAttribute('partition', 'persist:main'); - webview.setAttribute('preload', '../preload.js'); + try { + const preloadPath = (window.electronAPI && typeof window.electronAPI.getWebviewPreloadPath === 'function') + ? window.electronAPI.getWebviewPreloadPath() + : '../preload.js'; + webview.setAttribute('preload', preloadPath); + } catch (e) { + webview.setAttribute('preload', '../preload.js'); + } // Add attributes needed for Google OAuth and sign-in flows webview.setAttribute('webpreferences', 'allowRunningInsecureContent=false,javascript=true,webSecurity=true'); try { diff --git a/start-steamos.sh b/start-steamos.sh deleted file mode 100644 index b7dd9e9..0000000 --- a/start-steamos.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# SteamOS/Steam Deck launch script for Nebula Browser -# This script applies necessary flags for proper rendering on SteamOS/Gamescope - -# Detect if running on SteamOS -if [ -f /etc/steamos-release ] || [ -f /usr/share/steamos/steamos.conf ]; then - echo "SteamOS detected" -fi - -# Detect if running under Gamescope (Steam Deck's compositor) -if [ -n "$GAMESCOPE_WAYLAND_DISPLAY" ] || [ -n "$SteamDeck" ]; then - echo "Gamescope/Steam Deck environment detected" -fi - -# Get the directory where this script is located -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Launch Nebula with SteamOS-compatible flags -# These flags help with webview rendering issues on AMD GPUs under Gamescope -exec electron "$SCRIPT_DIR" \ - --ozone-platform=x11 \ - --disable-gpu-compositing \ - --disable-gpu-vsync \ - --disable-accelerated-2d-canvas \ - --use-gl=desktop \ - --no-sandbox \ - --disable-dev-shm-usage \ - --disable-gpu-sandbox \ - --disable-features=VizDisplayCompositor \ - --enable-unsafe-swiftshader \ - "$@" From fa04dc4e3222da97de1ff4ce8bc6c9761d4842d6 Mon Sep 17 00:00:00 2001 From: Andrew Zambazos Date: Sun, 28 Dec 2025 21:53:14 +1300 Subject: [PATCH 2/3] Update bigpicture.js Make gamepad work via steam launch --- renderer/bigpicture.js | 97 +++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 19 deletions(-) diff --git a/renderer/bigpicture.js b/renderer/bigpicture.js index 92fa913..71a3bdc 100644 --- a/renderer/bigpicture.js +++ b/renderer/bigpicture.js @@ -594,34 +594,93 @@ function goForward() { // ============================================================================= function initGamepadSupport() { + if (!navigator.getGamepads) { + console.warn('[BigPicture] Gamepad API not available in this environment'); + return; + } + + // Note: On Linux (and some controllers like handheld integrated gamepads), + // the `gamepadconnected` event may not fire until the first button press, + // or at all. We rely on continuous polling for robustness. window.addEventListener('gamepadconnected', (e) => { - console.log('[BigPicture] Gamepad connected:', e.gamepad.id); - state.gamepadConnected = true; - state.gamepadIndex = e.gamepad.index; - showToast('Controller connected'); + console.log('[BigPicture] Gamepad connected:', e.gamepad?.id || 'unknown'); + // Prefer the first connected controller as the active one. + if (state.gamepadIndex === null) { + state.gamepadConnected = true; + state.gamepadIndex = e.gamepad.index; + showToast('Controller connected'); + } }); - + window.addEventListener('gamepaddisconnected', (e) => { - console.log('[BigPicture] Gamepad disconnected'); - state.gamepadConnected = false; - state.gamepadIndex = null; - showToast('Controller disconnected'); + console.log('[BigPicture] Gamepad disconnected:', e.gamepad?.id || 'unknown'); + // If the active controller disconnected, clear it; polling will auto-select another. + if (state.gamepadIndex === e.gamepad.index) { + state.gamepadConnected = false; + state.gamepadIndex = null; + showToast('Controller disconnected'); + } }); - + + // Initial scan (covers controllers that are already connected at load). + refreshActiveGamepad(true); + // Start polling for gamepad input requestAnimationFrame(pollGamepad); } -function pollGamepad() { - if (state.gamepadConnected && state.gamepadIndex !== null) { - const gamepads = navigator.getGamepads(); - const gamepad = gamepads[state.gamepadIndex]; - - if (gamepad) { - handleGamepadInput(gamepad); - } +function getFirstConnectedGamepad(gamepads) { + if (!gamepads) return null; + for (let i = 0; i < gamepads.length; i++) { + const gp = gamepads[i]; + if (gp) return gp; } - + return null; +} + +function refreshActiveGamepad(isInitial = false) { + const gamepads = navigator.getGamepads(); + + // If we have an index, verify it still points to a real gamepad. + let active = null; + if (state.gamepadIndex !== null) { + active = gamepads[state.gamepadIndex] || null; + } + + // Fallback: pick the first connected controller. + if (!active) { + active = getFirstConnectedGamepad(gamepads); + } + + if (active) { + const changed = !state.gamepadConnected || state.gamepadIndex !== active.index; + state.gamepadConnected = true; + state.gamepadIndex = active.index; + if (changed && !isInitial) { + console.log('[BigPicture] Active gamepad selected:', active.id); + showToast('Controller connected'); + } + } else { + if (state.gamepadConnected) { + state.gamepadConnected = false; + state.gamepadIndex = null; + if (!isInitial) { + showToast('Controller disconnected'); + } + } + state.gamepadConnected = false; + state.gamepadIndex = null; + } + + return { gamepads, active }; +} + +function pollGamepad() { + const { active } = refreshActiveGamepad(false); + if (active) { + handleGamepadInput(active); + } + requestAnimationFrame(pollGamepad); } From 60d382a135f7f9aefb5bb7da87d5cd4b669f41d5 Mon Sep 17 00:00:00 2001 From: Andrew Zambazos Date: Sun, 28 Dec 2025 22:13:07 +1300 Subject: [PATCH 3/3] Game Mode compatability - Browser will now launch into big picture mode if device is in game mode - Controllers work --- README-STEAM.md | 4 ++ documentation/BIG_PICTURE_MODE.md | 8 ++++ main.js | 61 ++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/README-STEAM.md b/README-STEAM.md index 5714975..203291b 100644 --- a/README-STEAM.md +++ b/README-STEAM.md @@ -42,3 +42,7 @@ Notes - `--no-sandbox` reduces Chromium sandboxing; prefer fixing `chrome-sandbox` and enabling sandboxing when possible. - Using the AppDir avoids AppImage/FUSE dependency on target systems. - Test on a clean SteamOS/Deck image before publishing. + +Big Picture auto-start (SteamOS Gaming Mode) +- If Nebula is launched from SteamOS Gaming Mode, it will auto-start in Big Picture Mode. +- To force/disable via Steam Launch Options: `--big-picture` or `--no-big-picture`. diff --git a/documentation/BIG_PICTURE_MODE.md b/documentation/BIG_PICTURE_MODE.md index 6367469..f84e4bd 100644 --- a/documentation/BIG_PICTURE_MODE.md +++ b/documentation/BIG_PICTURE_MODE.md @@ -42,6 +42,14 @@ Nebula Browser includes a **Big Picture Mode** - a controller-friendly, console- ### Automatic Detection If Nebula detects a Steam Deck-sized display (1280x800), it will suggest Big Picture Mode in settings. +### Auto-start in SteamOS Gaming Mode +When Nebula is launched from SteamOS **Gaming Mode** (gamescope / Steam gamepad UI), it will automatically start in **Big Picture Mode**. + +You can override this behavior: +- Force Big Picture at launch: launch options `--big-picture` (or `--bigpicture`) +- Disable Big Picture auto-start: launch options `--no-big-picture` (or `--no-bigpicture`) +- Environment overrides: `NEBULA_BIG_PICTURE=1` / `NEBULA_NO_BIG_PICTURE=1` + ## Navigation Sections | Section | Description | diff --git a/main.js b/main.js index 82088eb..7ae02c3 100644 --- a/main.js +++ b/main.js @@ -72,6 +72,53 @@ ipcMain.removeHandler('window-close'); // BIG PICTURE MODE - Steam Deck / Console UI // ============================================================================= +function envTruthy(value) { + if (value === undefined || value === null) return false; + const s = String(value).trim().toLowerCase(); + return s === '1' || s === 'true' || s === 'yes' || s === 'on'; +} + +function argvHasFlag(flag) { + return process.argv.includes(flag); +} + +/** + * Heuristic: detect Steam Deck / SteamOS Gaming Mode (gamescope) launches. + * + * This is intentionally conservative and only used for picking the *default* + * startup UI. Users can override via CLI/env. + */ +function isGameModeEnvironment() { + const env = process.env; + + // Common Steam tenfoot / gamepad UI markers + if (envTruthy(env.STEAM_GAMEPADUI)) return true; + if (envTruthy(env.SteamTenfoot)) return true; + if (envTruthy(env.STEAM_TENFOOT)) return true; + + // SteamOS / gamescope compositor markers + const currentDesktop = String(env.XDG_CURRENT_DESKTOP || '').toLowerCase(); + const sessionDesktop = String(env.XDG_SESSION_DESKTOP || '').toLowerCase(); + if (currentDesktop.includes('gamescope') || sessionDesktop.includes('gamescope')) return true; + + if (env.GAMESCOPE_WSI || env.GAMESCOPE_SESSION || env.GAMESCOPE_FOCUSED_APP) return true; + + return false; +} + +function shouldStartInBigPictureMode() { + // Explicit CLI overrides first + if (argvHasFlag('--no-big-picture') || argvHasFlag('--no-bigpicture')) return false; + if (argvHasFlag('--big-picture') || argvHasFlag('--bigpicture') || argvHasFlag('--tenfoot') || argvHasFlag('--game-mode')) return true; + + // Explicit env overrides + if (envTruthy(process.env.NEBULA_NO_BIG_PICTURE) || envTruthy(process.env.NEBULA_NO_BIGPICTURE)) return false; + if (envTruthy(process.env.NEBULA_BIG_PICTURE) || envTruthy(process.env.NEBULA_BIGPICTURE) || envTruthy(process.env.NEBULA_GAME_MODE)) return true; + + // Auto-detect SteamOS Gaming Mode + return isGameModeEnvironment(); +} + // Steam Deck screen dimensions: 1280x800 const STEAM_DECK_WIDTH = 1280; const STEAM_DECK_HEIGHT = 800; @@ -503,7 +550,17 @@ function configureSessionsAsync() { app.whenReady().then(() => { const t0 = performance.now(); - createWindow(); + + // If launched via SteamOS Gaming Mode / gamepad UI, default to Big Picture Mode. + // Desktop launches remain unchanged. + const startInBigPicture = shouldStartInBigPictureMode(); + if (startInBigPicture) { + console.log('[Startup] Detected game mode launch; starting in Big Picture Mode'); + createBigPictureWindow(); + } else { + createWindow(); + } + // Initialize user plugins after app ready try { pluginManager.ensureUserPluginsDir(); @@ -512,7 +569,7 @@ app.whenReady().then(() => { } catch (e) { console.error('[Plugins] initialization error:', e); } - console.log('[Startup] createWindow invoked in', (performance.now() - t0).toFixed(1), 'ms after app.whenReady'); + console.log('[Startup] initial window created (', startInBigPicture ? 'bigpicture' : 'desktop', ') in', (performance.now() - t0).toFixed(1), 'ms after app.whenReady'); // Handle GPU process crashes (still register early) app.on('gpu-process-crashed', (event, killed) => {