Skip to content

Electron & IPC ​

Main Process Initialization ​

The Electron main process (electron/main.ts) initializes in this order:

  1. Install log interceptor (500-line ring buffer for diagnostics)
  2. Set platform-specific flags (macOS ScreenCaptureKit, Linux PulseAudio loopback)
  3. Create caption window controller
  4. Register trusted windows (main + caption)
  5. Create desktop source and local runtime controllers
  6. Request single-instance lock (quit if another instance is running)
  7. On app.whenReady():
    • Start HTTP server on port 23456 (Volcengine proxy)
    • Attach API server to the HTTP server
    • Create main window
    • Create system tray
    • Register global shortcuts
    • Set up auto-updater (if supported)
  8. Register all IPC handlers

IPC Modules ​

ModulePurposeTrust Required
appIpc.tsWindow controls, auto-launch, file pickerPartial (sensitive ops only)
captionIpc.tsCaption window text, style, draggable, positionNo
safeStorageIpc.tsEncrypt/decrypt secrets via OS keychainYes
updaterIpc.tsCheck, download, install updatesYes (download/install)
diagnosticsIpc.tsExport redacted diagnostics JSONYes
apiIpc.tsBridge: Main ↔ Renderer for API dataNo
localRuntimeIpc.tswhisper.cpp binary/model managementYes

Trusted Window Verification ​

ipcSecurity.ts maintains a list of trusted BrowserWindow providers. assertTrustedSender(event, channel) checks that event.sender.id matches one of the registered windows' webContents.id.

Only the main window and caption window are registered as trusted.

IPC Patterns ​

Main → Renderer (Request/Response) ​

Used by the API bridge (apiIpc.ts):

Main process (apiServer receives HTTP request)
  → webContents.send('api-get-sessions', { limit, offset })
  → Renderer (useApiIpcResponder) processes request
  → ipcRenderer.send('api-respond-sessions', data)
  → Main process resolves pending Promise
  → HTTP response sent to client

The pattern uses a 5-second timeout with fallback empty data to avoid hanging.

Renderer → Main (One-Way) ​

Session lifecycle notifications:

Renderer (sessionStore detects session start/end)
  → ipcRenderer.send('api-notify-session-start', { sessionId })
  → Main process (apiIpc.ts) receives notification
  → apiBroadcast broadcasts to WebSocket clients

Renderer → Main (Invoke) ​

Standard Electron invoke pattern for most operations:

Renderer: await window.electronAPI.getAppVersion()
  → ipcMain.handle('get-app-version', () => app.getVersion())

Preload Bridge ​

electron/preload.ts exposes a single electronAPI object via contextBridge.exposeInMainWorld. This is the only way the Renderer communicates with the Main process.

The full API surface is defined in shared/electronApi.ts as the ElectronAPI TypeScript interface, ensuring type safety across the process boundary.

Window Configuration ​

Main Window ​

  • 1200×800 default, 800×600 minimum
  • frame: false (custom title bar)
  • contextIsolation: true, nodeIntegration: false
  • backgroundThrottling: false (keeps recording active when minimized)
  • CSP injected via session.webRequest.onHeadersReceived
  • Navigation restricted to localhost:5173, file:, and devtools: URLs

Caption Window ​

  • Separate BrowserWindow for floating subtitle overlay
  • transparent: true (Linux uses semi-transparent black)
  • alwaysOnTop: true, skipTaskbar: true
  • Click-through when not interacting (setIgnoreMouseEvents with forward: true)
  • Mouse position polling (100ms) to detect hover for temporary interaction
  • focusable toggled based on interaction state

Released under the Apache 2.0 License.