From 1b71a4001d6a4e2d81c27f0d5896d67bc6ae2f3c Mon Sep 17 00:00:00 2001 From: Andrew Zambazos Date: Thu, 1 Jan 2026 12:17:35 +1300 Subject: [PATCH] Refactor Big Picture Mode to use main window Big Picture Mode now launches in the main application window instead of a separate Electron window. This reduces resource usage, prevents SteamOS desktop mode conflicts, and enables seamless switching between Desktop and Big Picture modes. Updated documentation, main process logic, and preload API to reflect the new architecture. --- documentation/BIG_PICTURE_MODE.md | 14 ++- main.js | 160 ++++++++++++++++-------------- preload.js | 8 +- 3 files changed, 101 insertions(+), 81 deletions(-) diff --git a/documentation/BIG_PICTURE_MODE.md b/documentation/BIG_PICTURE_MODE.md index 6791cc0..a585940 100644 --- a/documentation/BIG_PICTURE_MODE.md +++ b/documentation/BIG_PICTURE_MODE.md @@ -2,6 +2,13 @@ Nebula Browser includes a **Big Picture Mode** - a controller-friendly, console-style interface designed for Steam Deck, handheld devices, and living room setups. +## 🚀 Single Window Architecture + +Big Picture Mode now opens **in the main window** instead of a separate window. This design: +- **Keeps resources low** - No extra Electron window process +- **Prevents SteamOS conflicts** - When auto-launching in Gaming Mode, Steam won't create a desktop mode alongside, preventing Steam from overriding controls to emulate keyboard/mouse +- **Seamless switching** - Navigate between Desktop and Big Picture modes smoothly + ## ⚠️ Steam Deck: Disabling Mouse Emulation If Steam is emulating mouse/keyboard input with the joysticks (overriding native controller support), you need to configure Steam Input: @@ -129,10 +136,13 @@ const suggested = await window.bigPictureAPI.isSuggested(); // Get screen information const info = await window.bigPictureAPI.getScreenInfo(); -// Launch Big Picture Mode +// Check if currently in Big Picture Mode +const isActive = await window.bigPictureAPI.isActive(); + +// Launch Big Picture Mode (navigates main window) await window.bigPictureAPI.launch(); -// Exit Big Picture Mode +// Exit Big Picture Mode (navigates back to desktop UI) await window.bigPictureAPI.exit(); ``` diff --git a/main.js b/main.js index 0047011..759b1ac 100644 --- a/main.js +++ b/main.js @@ -322,7 +322,8 @@ const STEAM_DECK_WIDTH = 1280; const STEAM_DECK_HEIGHT = 800; const HANDHELD_THRESHOLD = 1366; // Consider screens smaller than this as "handheld" -let bigPictureWindow = null; +// Track if main window is currently in Big Picture Mode (no separate window anymore) +let isInBigPictureMode = false; /** * Check if the current display is likely a Steam Deck or similar handheld @@ -362,89 +363,78 @@ function getScreenInfo() { } /** - * Create Big Picture Mode window + * Launch Big Picture Mode in the main window (no separate window) + * This keeps resources low and prevents SteamOS from creating desktop mode alongside. */ -function createBigPictureWindow() { - if (bigPictureWindow && !bigPictureWindow.isDestroyed()) { - bigPictureWindow.focus(); - return bigPictureWindow; +function launchBigPictureMode() { + const windows = BrowserWindow.getAllWindows(); + const mainWindow = windows[0]; + + if (!mainWindow || mainWindow.isDestroyed()) { + console.warn('[BigPicture] No main window available'); + return null; } - - const primaryDisplay = screen.getPrimaryDisplay(); - const { width, height } = primaryDisplay.workAreaSize; - - bigPictureWindow = new BrowserWindow({ - width: width, - height: height, - fullscreen: true, - frame: false, - show: false, - backgroundColor: '#0a0a0f', - webPreferences: { - preload: path.join(__dirname, 'preload.js'), - nodeIntegration: false, - contextIsolation: true, - webviewTag: true, - spellcheck: false, - webSecurity: true, - }, - icon: process.platform === 'darwin' - ? path.join(__dirname, 'assets/images/Logos/Nebula-Favicon.icns') - : path.join(__dirname, 'assets/images/Logos/Nebula-favicon.png'), - title: 'Nebula - Big Picture Mode' - }); - - bigPictureWindow.loadFile('renderer/bigpicture.html'); - - bigPictureWindow.once('ready-to-show', () => { - bigPictureWindow.show(); - - // Apply saved display scale to big picture window - try { - const configPath = path.join(app.getPath('userData'), 'localStorage'); - const dbPath = path.join(configPath, 'leveldb'); - // Note: localStorage will be applied via CSS in bigpicture.js since it reads from localStorage directly - // The IPC handler will apply setZoomFactor when adjustments are made - console.log('[BigPicture] Window ready - display scale will be applied from localStorage'); - } catch (err) { - console.warn('[BigPicture] Failed to apply saved display scale on ready:', err); - } - - console.log('[BigPicture] Window ready'); - }); - - bigPictureWindow.on('closed', () => { - bigPictureWindow = null; - console.log('[BigPicture] Window closed'); - }); - - return bigPictureWindow; + + if (isInBigPictureMode) { + console.log('[BigPicture] Already in Big Picture Mode'); + mainWindow.focus(); + return mainWindow; + } + + isInBigPictureMode = true; + + // Enter fullscreen for Big Picture experience + mainWindow.setFullScreen(true); + mainWindow.setTitle('Nebula - Big Picture Mode'); + + // Navigate to Big Picture UI + mainWindow.loadFile('renderer/bigpicture.html'); + + console.log('[BigPicture] Launched in main window'); + return mainWindow; } /** - * Exit Big Picture Mode and return to desktop UI + * Exit Big Picture Mode and return to desktop UI in the same window */ function exitBigPictureMode() { - if (bigPictureWindow && !bigPictureWindow.isDestroyed()) { - bigPictureWindow.close(); - bigPictureWindow = null; + const windows = BrowserWindow.getAllWindows(); + const mainWindow = windows[0]; + + if (!mainWindow || mainWindow.isDestroyed()) { + console.warn('[BigPicture] No main window to exit from'); + return; } - // Ensure main window exists and is focused - const windows = BrowserWindow.getAllWindows(); - const mainWindow = windows.find(w => w !== bigPictureWindow); - if (mainWindow) { - mainWindow.focus(); - } else if (windows.length === 0) { - createWindow(); + if (!isInBigPictureMode) { + console.log('[BigPicture] Not in Big Picture Mode'); + return; } + + isInBigPictureMode = false; + + // Exit fullscreen and restore normal window + mainWindow.setFullScreen(false); + mainWindow.setTitle('Nebula'); + + // Navigate back to desktop UI + mainWindow.loadFile('renderer/index.html'); + + // Maximize on Windows after exiting fullscreen + if (process.platform === 'win32') { + setTimeout(() => { + try { mainWindow.maximize(); } catch {} + }, 100); + } + + console.log('[BigPicture] Exited to desktop mode'); } // IPC handlers for Big Picture Mode ipcMain.handle('get-screen-info', () => getScreenInfo()); ipcMain.handle('launch-bigpicture', () => { - createBigPictureWindow(); + launchBigPictureMode(); return { success: true }; }); @@ -457,6 +447,11 @@ ipcMain.handle('is-bigpicture-suggested', () => { return isSteamDeckDisplay(); }); +// Check if currently in Big Picture Mode +ipcMain.handle('is-in-bigpicture', () => { + return isInBigPictureMode; +}); + ipcMain.on('exit-bigpicture', () => { exitBigPictureMode(); }); @@ -480,10 +475,15 @@ ipcMain.handle('webview-send-input-event', async (event, { webContentsId, inputE // ============================================================================= -function createWindow(startUrl) { +function createWindow(startUrl, bigPictureMode = false) { // Capture high‑resolution startup timing markers const perfMarks = { createWindow_called: performance.now() }; + // Track Big Picture Mode state if starting in that mode + if (bigPictureMode) { + isInBigPictureMode = true; + } + // Get the available screen size (avoid full workArea allocation jank by starting slightly smaller then maximizing later if desired) const { width, height } = screen.getPrimaryDisplay().workAreaSize; const initialWidth = Math.min(width, Math.round(width * 0.9)); @@ -491,11 +491,11 @@ function createWindow(startUrl) { // Window is created hidden; we only show after first meaningful paint to avoid OS‑level pointer jank while Chromium initializes let windowOptions = { - width: initialWidth, - height: initialHeight, + width: bigPictureMode ? width : initialWidth, + height: bigPictureMode ? height : initialHeight, show: false, useContentSize: true, - backgroundColor: '#121212', // avoids white flash & early extra paints + backgroundColor: bigPictureMode ? '#0a0a0f' : '#121212', // Big Picture uses darker bg resizable: true, webPreferences: { preload: path.join(__dirname, 'preload.js'), @@ -517,7 +517,7 @@ function createWindow(startUrl) { partition: 'persist:main', sandbox: false }, - fullscreen: false, + fullscreen: bigPictureMode, // Start in fullscreen for Big Picture Mode autoHideMenuBar: true, icon: process.platform === 'darwin' ? path.join(__dirname, 'assets/images/Logos/Nebula-Favicon.icns') @@ -621,7 +621,13 @@ function createWindow(startUrl) { }); }); - win.loadFile('renderer/index.html'); + // Load appropriate UI based on mode (Big Picture or Desktop) + if (bigPictureMode) { + win.loadFile('renderer/bigpicture.html'); + win.setTitle('Nebula - Big Picture Mode'); + } else { + win.loadFile('renderer/index.html'); + } perfMarks.loadFile_issued = performance.now(); // if caller passed in a URL, forward it to the renderer after load @@ -762,11 +768,11 @@ app.whenReady().then(() => { const t0 = performance.now(); // If launched via SteamOS Gaming Mode / gamepad UI, default to Big Picture Mode. - // Desktop launches remain unchanged. + // Desktop launches remain unchanged. Big Picture now opens in main window to keep resources low. const startInBigPicture = shouldStartInBigPictureMode(); if (startInBigPicture) { - console.log('[Startup] Detected game mode launch; starting in Big Picture Mode'); - createBigPictureWindow(); + console.log('[Startup] Detected game mode launch; starting in Big Picture Mode (in main window)'); + createWindow(null, true); // Pass bigPictureMode flag } else { createWindow(); } diff --git a/preload.js b/preload.js index b072151..9d5d17d 100644 --- a/preload.js +++ b/preload.js @@ -442,14 +442,18 @@ contextBridge.exposeInMainWorld('aboutAPI', { }); // Big Picture Mode API - Steam Deck / Console UI +// Note: Big Picture Mode now opens in the main window (not a separate window) to keep resources low +// and prevent SteamOS from creating desktop mode alongside when auto-launching. contextBridge.exposeInMainWorld('bigPictureAPI', { // Get screen info to determine if Big Picture Mode is recommended getScreenInfo: () => ipcRenderer.invoke('get-screen-info'), // Check if device is likely a Steam Deck or handheld isSuggested: () => ipcRenderer.invoke('is-bigpicture-suggested'), - // Launch Big Picture Mode + // Check if currently in Big Picture Mode + isActive: () => ipcRenderer.invoke('is-in-bigpicture'), + // Launch Big Picture Mode (navigates main window to Big Picture UI) launch: () => ipcRenderer.invoke('launch-bigpicture'), - // Exit Big Picture Mode + // Exit Big Picture Mode (navigates main window back to desktop UI) exit: () => ipcRenderer.invoke('exit-bigpicture'), // Navigate to URL (from Big Picture Mode) navigate: (url) => ipcRenderer.send('bigpicture-navigate', url),