diff --git a/src/main/ipc-channels.ts b/src/main/ipc-channels.ts index ae1e9cc..6547fa1 100644 --- a/src/main/ipc-channels.ts +++ b/src/main/ipc-channels.ts @@ -33,6 +33,12 @@ export const IPC_MAINTENANCE_RESTART = 'maintenance:restart' as const; // powerSaveBlocker here instead. See got-feedback/feedback#686. export const IPC_POWER_SET_SCREEN_AWAKE = 'power:setScreenAwake' as const; +// Main-window fullscreen-at-launch preference. Renderer (Settings → System) +// reads/writes it; main persists it in the desktop config and applies it on +// window creation. +export const IPC_WINDOW_GET_START_FULLSCREEN = 'window:getStartFullscreen' as const; +export const IPC_WINDOW_SET_START_FULLSCREEN = 'window:setStartFullscreen' as const; + // Detachable panes (feedBack core's window.feedBack.panes). // // Deliberately tiny. The renderer OPENS its own pane windows with window.open() — diff --git a/src/main/main.ts b/src/main/main.ts index 7dd5adf..6c8a4cf 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -117,6 +117,8 @@ import { IPC_UPDATE_CHECK_NOW, IPC_UPDATE_APPLY, IPC_POWER_SET_SCREEN_AWAKE, + IPC_WINDOW_GET_START_FULLSCREEN, + IPC_WINDOW_SET_START_FULLSCREEN, } from './ipc-channels'; import { initAudioBridge, shutdownAudio } from './audio-bridge'; import { initDebugLogging, isDebugEnabled } from './debug-log'; @@ -469,6 +471,7 @@ function createWindow(port: number): void { getDesktopConfig().windowBounds, screen.getAllDisplays().map((d) => d.workArea), ); + const startFullscreen = getDesktopConfig().startFullscreen === true; mainWindow = new BrowserWindow({ x: restored.x, y: restored.y, @@ -476,17 +479,20 @@ function createWindow(port: number): void { height: restored.height, minWidth: MIN_WIDTH, minHeight: MIN_HEIGHT, + fullscreen: startFullscreen, title: 'fee[dB]ack', backgroundColor: '#0f172a', // slate-900 to match Slopsmith UI webPreferences: rendererWebPreferences, }); - if (restored.maximized) mainWindow.maximize(); + if (restored.maximized && !startFullscreen) mainWindow.maximize(); // Persist geometry on close. getNormalBounds() so a maximized session // saves the underlying windowed size, restored + re-maximized next launch. - // ponytail: fullscreen is deliberately not persisted (launching straight - // into fullscreen is jarring, especially on macOS) and we save on close - // only — a crash loses the last session's geometry; add debounced + // Fullscreen is NOT auto-persisted from the live window state (launching + // straight into fullscreen is jarring, especially on macOS); it launches + // fullscreen ONLY when the user opts in via Settings → System → "Start in + // fullscreen" (config.startFullscreen, applied above). We save geometry on + // close only — a crash loses the last session's geometry; add debounced // resize/move saving if that ever matters. mainWindow.on('close', () => { if (!mainWindow || mainWindow.isDestroyed()) return; @@ -1272,6 +1278,23 @@ async function startup(): Promise { return { success: true, enabled: on, urls: getLanUrls() }; }); + ipcMain.handle(IPC_WINDOW_GET_START_FULLSCREEN, () => getDesktopConfig().startFullscreen === true); + ipcMain.handle(IPC_WINDOW_SET_START_FULLSCREEN, (_event, on: unknown) => { + const value = on === true; + setDesktopConfig({ startFullscreen: value }); + // Live-apply so the toggle is responsive, not silent-until-relaunch. + // Works on the first toggle on Windows/Linux. On macOS the FIRST enter on + // a window created windowed is dropped by AppKit (its native-fullscreen + // state machine isn't engaged until the window has been fullscreen once — + // creating with `fullscreen: true` engages it), so there it takes effect + // on next launch instead; the core Settings note tells macOS users that. + // Reliable live both ways once the window has entered fullscreen once. + if (mainWindow && !mainWindow.isDestroyed() && mainWindow.isFullScreen() !== value) { + mainWindow.setFullScreen(value); + } + return value; + }); + // Auto-update (Velopack). The renderer Settings panel reads the persisted // channel from localStorage and calls setChannel() on boot — we default // to 'stable' here so the first check runs against the safest feed even diff --git a/src/main/preload.ts b/src/main/preload.ts index 9ba1886..bac485e 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -30,6 +30,8 @@ import { IPC_UPDATE_EVENT_AVAILABLE, IPC_UPDATE_EVENT_DOWNLOADED, IPC_POWER_SET_SCREEN_AWAKE, + IPC_WINDOW_GET_START_FULLSCREEN, + IPC_WINDOW_SET_START_FULLSCREEN, IPC_MAINTENANCE_GET_PATHS, IPC_MAINTENANCE_RESET, IPC_MAINTENANCE_RESTART, @@ -580,6 +582,14 @@ const feedBackDesktopApi = { power: { setScreenAwake: (keep: boolean) => ipcRenderer.invoke(IPC_POWER_SET_SCREEN_AWAKE, keep), }, + // Main-window preferences the renderer can't own because the main process + // must read them at window-creation time. Today: start-in-fullscreen, wired + // by feedBack core's Settings → System "Start in fullscreen" toggle + // (setupWindowOptions()). Both methods return the resulting boolean. + window: { + getStartFullscreen: (): Promise => ipcRenderer.invoke(IPC_WINDOW_GET_START_FULLSCREEN), + setStartFullscreen: (on: boolean): Promise => ipcRenderer.invoke(IPC_WINDOW_SET_START_FULLSCREEN, on), + }, // Detachable panes. The renderer opens its own pane windows with window.open() // — it moves a live DOM node into them and needs a handle on the new document // to do it — and Electron turns that into a real BrowserWindow, which main diff --git a/src/main/soundfont-manager.ts b/src/main/soundfont-manager.ts index c078738..345b67f 100644 --- a/src/main/soundfont-manager.ts +++ b/src/main/soundfont-manager.ts @@ -38,6 +38,13 @@ interface DesktopConfig { // Last main-window geometry, restored (after sanitization against the // current display layout) on next launch — see window-bounds.ts. windowBounds?: SavedWindowBounds; + // When true, the main window launches in fullscreen and the System-settings + // toggle that drives it is shown. Opt-in (default off) — this deliberately + // reverses createWindow()'s historical "never launch fullscreen" default, + // but only for users who ask for it. Persisted here (not renderer + // localStorage) because the main process must read it at window creation, + // before the renderer exists. + startFullscreen?: boolean; // Per-pane pop-out window geometry, keyed by pane id, plus its always-on-top // flag. Sanitized against the display layout on restore, exactly like // windowBounds — see pane-hosts.ts. Lives in the desktop config rather than