# How the Electron Main Process Manages Windows and IPC in Folia

> Discover how the Electron main process manages windows and IPC in Folia. Learn about secure preload bridges, curated IPC handlers, and context isolation.

- Repository: [冬霧/folia-major](https://github.com/chthollyphile/folia-major)
- Tags: internals
- Published: 2026-07-06

---

**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](https://github.com/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:

```typescript
// 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()`:

```typescript
// 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:

```typescript
// 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()`:

```typescript
// 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`:

```typescript
// 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.