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.
This commit is contained in:
2026-01-01 12:17:35 +13:00
parent 76f5ef188a
commit 1b71a4001d
3 changed files with 101 additions and 81 deletions
+12 -2
View File
@@ -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. 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 ## ⚠️ 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: 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 // Get screen information
const info = await window.bigPictureAPI.getScreenInfo(); 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(); await window.bigPictureAPI.launch();
// Exit Big Picture Mode // Exit Big Picture Mode (navigates back to desktop UI)
await window.bigPictureAPI.exit(); await window.bigPictureAPI.exit();
``` ```
+83 -77
View File
@@ -322,7 +322,8 @@ const STEAM_DECK_WIDTH = 1280;
const STEAM_DECK_HEIGHT = 800; const STEAM_DECK_HEIGHT = 800;
const HANDHELD_THRESHOLD = 1366; // Consider screens smaller than this as "handheld" 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 * 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() { function launchBigPictureMode() {
if (bigPictureWindow && !bigPictureWindow.isDestroyed()) { const windows = BrowserWindow.getAllWindows();
bigPictureWindow.focus(); const mainWindow = windows[0];
return bigPictureWindow;
if (!mainWindow || mainWindow.isDestroyed()) {
console.warn('[BigPicture] No main window available');
return null;
} }
const primaryDisplay = screen.getPrimaryDisplay(); if (isInBigPictureMode) {
const { width, height } = primaryDisplay.workAreaSize; console.log('[BigPicture] Already in Big Picture Mode');
mainWindow.focus();
bigPictureWindow = new BrowserWindow({ return mainWindow;
width: width, }
height: height,
fullscreen: true, isInBigPictureMode = true;
frame: false,
show: false, // Enter fullscreen for Big Picture experience
backgroundColor: '#0a0a0f', mainWindow.setFullScreen(true);
webPreferences: { mainWindow.setTitle('Nebula - Big Picture Mode');
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false, // Navigate to Big Picture UI
contextIsolation: true, mainWindow.loadFile('renderer/bigpicture.html');
webviewTag: true,
spellcheck: false, console.log('[BigPicture] Launched in main window');
webSecurity: true, return mainWindow;
},
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;
} }
/** /**
* Exit Big Picture Mode and return to desktop UI * Exit Big Picture Mode and return to desktop UI in the same window
*/ */
function exitBigPictureMode() { function exitBigPictureMode() {
if (bigPictureWindow && !bigPictureWindow.isDestroyed()) { const windows = BrowserWindow.getAllWindows();
bigPictureWindow.close(); const mainWindow = windows[0];
bigPictureWindow = null;
if (!mainWindow || mainWindow.isDestroyed()) {
console.warn('[BigPicture] No main window to exit from');
return;
} }
// Ensure main window exists and is focused if (!isInBigPictureMode) {
const windows = BrowserWindow.getAllWindows(); console.log('[BigPicture] Not in Big Picture Mode');
const mainWindow = windows.find(w => w !== bigPictureWindow); return;
if (mainWindow) {
mainWindow.focus();
} else if (windows.length === 0) {
createWindow();
} }
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 // IPC handlers for Big Picture Mode
ipcMain.handle('get-screen-info', () => getScreenInfo()); ipcMain.handle('get-screen-info', () => getScreenInfo());
ipcMain.handle('launch-bigpicture', () => { ipcMain.handle('launch-bigpicture', () => {
createBigPictureWindow(); launchBigPictureMode();
return { success: true }; return { success: true };
}); });
@@ -457,6 +447,11 @@ ipcMain.handle('is-bigpicture-suggested', () => {
return isSteamDeckDisplay(); return isSteamDeckDisplay();
}); });
// Check if currently in Big Picture Mode
ipcMain.handle('is-in-bigpicture', () => {
return isInBigPictureMode;
});
ipcMain.on('exit-bigpicture', () => { ipcMain.on('exit-bigpicture', () => {
exitBigPictureMode(); exitBigPictureMode();
}); });
@@ -480,10 +475,15 @@ ipcMain.handle('webview-send-input-event', async (event, { webContentsId, inputE
// ============================================================================= // =============================================================================
function createWindow(startUrl) { function createWindow(startUrl, bigPictureMode = false) {
// Capture highresolution startup timing markers // Capture highresolution startup timing markers
const perfMarks = { createWindow_called: performance.now() }; 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) // 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 { width, height } = screen.getPrimaryDisplay().workAreaSize;
const initialWidth = Math.min(width, Math.round(width * 0.9)); 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 OSlevel pointer jank while Chromium initializes // Window is created hidden; we only show after first meaningful paint to avoid OSlevel pointer jank while Chromium initializes
let windowOptions = { let windowOptions = {
width: initialWidth, width: bigPictureMode ? width : initialWidth,
height: initialHeight, height: bigPictureMode ? height : initialHeight,
show: false, show: false,
useContentSize: true, useContentSize: true,
backgroundColor: '#121212', // avoids white flash & early extra paints backgroundColor: bigPictureMode ? '#0a0a0f' : '#121212', // Big Picture uses darker bg
resizable: true, resizable: true,
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'preload.js'), preload: path.join(__dirname, 'preload.js'),
@@ -517,7 +517,7 @@ function createWindow(startUrl) {
partition: 'persist:main', partition: 'persist:main',
sandbox: false sandbox: false
}, },
fullscreen: false, fullscreen: bigPictureMode, // Start in fullscreen for Big Picture Mode
autoHideMenuBar: true, autoHideMenuBar: true,
icon: process.platform === 'darwin' icon: process.platform === 'darwin'
? path.join(__dirname, 'assets/images/Logos/Nebula-Favicon.icns') ? 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(); perfMarks.loadFile_issued = performance.now();
// if caller passed in a URL, forward it to the renderer after load // if caller passed in a URL, forward it to the renderer after load
@@ -762,11 +768,11 @@ app.whenReady().then(() => {
const t0 = performance.now(); const t0 = performance.now();
// If launched via SteamOS Gaming Mode / gamepad UI, default to Big Picture Mode. // 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(); const startInBigPicture = shouldStartInBigPictureMode();
if (startInBigPicture) { if (startInBigPicture) {
console.log('[Startup] Detected game mode launch; starting in Big Picture Mode'); console.log('[Startup] Detected game mode launch; starting in Big Picture Mode (in main window)');
createBigPictureWindow(); createWindow(null, true); // Pass bigPictureMode flag
} else { } else {
createWindow(); createWindow();
} }
+6 -2
View File
@@ -442,14 +442,18 @@ contextBridge.exposeInMainWorld('aboutAPI', {
}); });
// Big Picture Mode API - Steam Deck / Console UI // 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', { contextBridge.exposeInMainWorld('bigPictureAPI', {
// Get screen info to determine if Big Picture Mode is recommended // Get screen info to determine if Big Picture Mode is recommended
getScreenInfo: () => ipcRenderer.invoke('get-screen-info'), getScreenInfo: () => ipcRenderer.invoke('get-screen-info'),
// Check if device is likely a Steam Deck or handheld // Check if device is likely a Steam Deck or handheld
isSuggested: () => ipcRenderer.invoke('is-bigpicture-suggested'), 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'), 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'), exit: () => ipcRenderer.invoke('exit-bigpicture'),
// Navigate to URL (from Big Picture Mode) // Navigate to URL (from Big Picture Mode)
navigate: (url) => ipcRenderer.send('bigpicture-navigate', url), navigate: (url) => ipcRenderer.send('bigpicture-navigate', url),