How the Electron Main Process Manages Windows and IPC in Folia

The Electron main process in Folia centralizes window lifecycle management and inter-process communication through a secure preload bridge, exposing approximately 70 curated IPC handlers while maintaining strict context isolation between the main and renderer processes.

The architecture of the Folia desktop application (chthollyphile/folia-major) relies on a robust Electron main process that orchestrates multiple window types and facilitates secure bidirectional communication. Located primarily in electron/main.cjs, this process implements a hardened security model using context isolation and a whitelist-based preload script, enabling the React-based renderer to access native capabilities without direct Node.js access.

Window Creation and Configuration

Folia’s main process manages three distinct window types: the primary application interface, a lightweight remote-control overlay, and a temporary video-export window. Each creation path enforces consistent security policies while adapting to specific functional requirements.

Main Window Setup

The createWindow() function in electron/main.cjs constructs the primary BrowserWindow with hardened web preferences and dynamic visual configuration:

// electron/main.cjs
const win = new BrowserWindow({
  ...windowBounds,                 // restored bounds or defaults
  minWidth: 350,
  minHeight: 100,
  frame: false,
  transparent: useTransparentWindow,
  backgroundColor: (useTransparentWindow || enableNativeBlur) ? '#00000000' : '#09090b',
  vibrancy: (!useTransparentWindow && enableNativeBlur) && process.platform === 'darwin' ? 'fullscreen-ui' : undefined,
  autoHideMenuBar: true,
  icon: APP_ICON_PATH,
  skipTaskbar: mainWindowSkipTaskbarEnabled,
  alwaysOnTop: mainWindowAlwaysOnTop,
  show: showImmediately,
  webPreferences: {
    preload: path.join(__dirname, 'preload.cjs'),   // secure bridge
    nodeIntegration: false,
    contextIsolation: true,
    webSecurity: true,
    backgroundThrottling: false,
  },
});

This configuration ensures nodeIntegration remains disabled and contextIsolation stays enabled, preventing the renderer from directly accessing Node.js APIs. The preload.cjs script serves as the sole authorized conduit for native functionality.

Specialized Windows

Beyond the main window, the process instantiates:

  • Remote-control window: Created by createRemoteControlWindow() as a fixed-size, frameless utility window that reuses the same preload script for secure IPC.
  • Video-export window: Spawned on-demand via the video-export-prepare-window IPC handler to handle media encoding tasks without blocking the main interface.

All windows connect to a single application instance enforced by app.requestSingleInstanceLock(), preventing duplicate Folia processes.

Window State Persistence

Folia implements automatic state persistence to restore user preferences across sessions. When a window triggers move, resize, maximize, or close events, the main process invokes saveWindowState():

// electron/main.cjs - state persistence logic
function saveWindowState(win, isMaximized) {
  const bounds = win.getBounds();
  store.set('windowState', {
    ...bounds,
    isMaximized,
  });
}

On startup, getStoredWindowState() retrieves stored dimensions, while ensureWindowBoundsVisible() validates that the restored rectangle resides within currently connected displays—critical for multi-monitor setups where displays may have been disconnected.

IPC Architecture and Security

The IPC layer implements a secure proxy pattern where the main process registers asynchronous handlers and the preload script exposes a curated API surface to the renderer.

ipcMain Handlers

The main process registers approximately 70 ipcMain.handle() endpoints in electron/main.cjs, each returning a Promise to the renderer:

// electron/main.cjs - representative handlers
ipcMain.handle('get-settings', () => store.store);

ipcMain.handle('save-settings', (event, key, value) => {
  store.set(key, value);
  return true;
});

ipcMain.handle('window-minimize', () => {
  if (mainWindow) mainWindow.minimize();
});

ipcMain.handle('window-toggle-fullscreen', () => {
  if (mainWindow) {
    mainWindow.setFullScreen(!mainWindow.isFullScreen());
  }
});

This pattern covers settings management, cache operations (get-audio-cache), update checking (updates-check, updates-get-status), OBS Browser Source control, Discord Rich Presence, and Stage API operations.

Preload Script Bridge

electron/preload.cjs defines the secure boundary using contextBridge.exposeInMainWorld():

// electron/preload.cjs
contextBridge.exposeInMainWorld('electron', {
  // Settings
  getSettings: () => ipcRenderer.invoke('get-settings'),
  saveSettings: (key, value) => ipcRenderer.invoke('save-settings', key, value),
  
  // Window controls
  minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
  toggleFullscreenWindow: () => ipcRenderer.invoke('window-toggle-fullscreen'),
  
  // Video export
  chooseVideoExportPath: (name, ext, title) =>
    ipcRenderer.invoke('video-export-choose-path', name, ext, title),
    
  // Stage API
  getStageStatus: () => ipcRenderer.invoke('stage-get-status'),
  setStageEnabled: (enabled) => ipcRenderer.invoke('stage-set-enabled', enabled),
});

Because the renderer cannot directly require Node modules, it accesses native capabilities exclusively through window.electron.* methods, eliminating arbitrary code execution risks while maintaining full functionality.

Key IPC Handler Categories

Folia organizes its IPC surface into functional domains, keeping the main process as the central authority for system operations.

Settings and Cache

Configuration persistence flows through the Electron Store:

  • get-settings: Retrieves the entire store object
  • save-settings: Updates specific keys atomically
  • get-audio-cache: Fetches cached audio data by key

Window Controls

Direct window manipulation exposed to the renderer includes:

  • window-minimize, window-maximize, window-close: Standard windowing operations
  • window-toggle-fullscreen: Exclusive fullscreen toggling
  • set-main-window-always-on-top: Z-order management for overlay behavior

Media and Export

Specialized workflows for content creation:

  • video-export-choose-path: Opens native dialogs for file selection
  • video-export-prepare-window: Creates the temporary encoding window
  • debug-get-rendered-fonts: Retrieves font diagnostics for troubleshooting

Playback Handoff Mechanism

When Folia recreates the main window (e.g., toggling transparency modes), it preserves playback state through a short-lived handoff store implemented in electron/windowPlaybackHandoff.cjs:

// electron/windowPlaybackHandoff.cjs
function createWindowPlaybackHandoffStore({ ttlMs = 15000 } = {}) {
  let current = null;
  let expires = 0;
  
  return {
    save(handoff) { 
      current = handoff; 
      expires = Date.now() + ttlMs; 
      return true; 
    },
    consume() { 
      if (!current || Date.now() > expires) return null; 
      const h = current; 
      current = null; 
      return h; 
    },
  };
}

The renderer submits handoff data via window.electron.submitWindowPlaybackHandoff(), which the main process stores temporarily. After window recreation, the new renderer instance calls window.electron.consumeWindowPlaybackHandoff() to retrieve the state, ensuring seamless playback continuity.

Summary

  • Single entry point: electron/main.cjs manages all window lifecycle and IPC registration for the Folia application.
  • Security-first architecture: Disabled nodeIntegration and enabled contextIsolation force all native access through the electron/preload.cjs bridge.
  • State persistence: Automatic saving and validation of window bounds prevents off-screen restoration issues.
  • Rich IPC surface: Approximately 70 ipcMain.handle() endpoints cover settings, media, exports, and integrations with OBS and Discord.
  • Handoff pattern: Temporary state stores enable seamless window recreation without playback interruption.

Frequently Asked Questions

How does Folia maintain window state between sessions?

Folia persists window bounds and maximization state using the Electron Store module. When a window moves, resizes, or changes maximize state, saveWindowState() writes the current dimensions to the store. On application launch, getStoredWindowState() retrieves these values, and ensureWindowBoundsVisible() validates that the restored rectangle fits within active display boundaries, preventing windows from opening off-screen when monitors are disconnected.

What security measures protect the renderer process in Folia?

Folia implements a strict security model with nodeIntegration: false and contextIsolation: true in all BrowserWindow configurations. The renderer cannot directly access Node.js APIs or native modules. Instead, electron/preload.cjs uses contextBridge.exposeInMainWorld() to expose only curated, whitelisted functions that invoke ipcRenderer.invoke() for specific ipcMain handlers, eliminating the attack surface of arbitrary code execution.

How does the playback handoff mechanism work when recreating windows?

When Folia must recreate the main window (such as when toggling transparency modes), the renderer submits current playback state via submitWindowPlaybackHandoff(). The main process stores this data in a temporary in-memory store with a 15-second TTL (windowPlaybackHandoff.cjs). After the new window initializes, it calls consumeWindowPlaybackHandoff() to retrieve and restore the previous state, ensuring continuous playback across window recreation events.

What is the purpose of the remote-control window in Folia?

The remote-control window serves as a lightweight, frameless overlay utility created by createRemoteControlWindow(). It provides quick access to playback controls without requiring the full main window interface, utilizing the same secure preload script to communicate with the main process via the established IPC bridge.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →