Back to Cherry Studio

Window Platform Configuration

docs/references/window-manager/window-manager-platform.md

2.0.010.0 KB
Original Source

Window Platform Configuration

WindowManager splits per-window configuration into three orthogonal layers:

  • windowOptions — Electron BrowserWindow constructor parameters (including platformOverrides for static per-OS differences).
  • behavior — cross-platform declarative WM behavior that Electron's constructor cannot express (blur-auto-hide, setAlwaysOnTop level, setVisibleOnAllWorkspaces options, Dock visibility). See README → Configuration Layers.
  • quirks — OS-specific hacks / workarounds applied via method-slot monkey-patches around hide() / show() / close().

Naming rule: any field effective on only one platform carries a mac / win / linux prefix, irrespective of layer (e.g. behavior.macShowInDock, quirks.macRestoreFocusOnHide).

OS Quirks

Some OS-specific behaviors are tedious to hand-roll at every call site (e.g. the macOS focus dance around hide()). WindowManager ships these as declarative opt-in flags under WindowTypeMetadata.quirks. When set, the manager transparently monkey-patches the corresponding BrowserWindow instance methods so business code continues calling window.hide() / window.show() as usual.

Available Quirks

QuirkPatchesBehavior
macRestoreFocusOnHide: booleanhide(), close()Before invoking the native method, iterate every visible focusable BrowserWindow and setFocusable(false); restore them 50ms later. Prevents other windows from being brought to the front when this one disappears.
macClearHoverOnHide: booleanhide()After invoking the native hide(), send webContents.sendInputEvent({ type: 'mouseMove', x: -1, y: -1 }) to clear any residual hover state.
macReapplyAlwaysOnTop: booleanshow(), showInactive()After invoking the native method, call setAlwaysOnTop(true, level, relativeLevel) with values read from behavior.alwaysOnTop (single source of truth). When behavior.alwaysOnTop.level is unset, falls back to 'floating'. Compensates for macOS level resets between hide/show.

All quirks are macOS-only: on other platforms the methods are left untouched, and window.hide === originalHide (identity preserved).

Example

typescript
[WindowType.SelectionToolbar]: {
  type: WindowType.SelectionToolbar,
  lifecycle: 'singleton',
  showMode: 'manual',
  windowOptions: { /* ... */ },
  behavior: {
    hideOnBlur: true,
    alwaysOnTop: { level: 'screen-saver' },  // level lives here, not in quirks
    visibleOnAllWorkspaces: { enabled: true, visibleOnFullScreen: true },
    macShowInDock: false
  },
  quirks: {
    macRestoreFocusOnHide: true,
    macClearHoverOnHide: true,
    macReapplyAlwaysOnTop: true              // boolean switch; reads level from behavior above
  }
}

With that in place, this.toolbarWindow.hide() from the domain service will:

  1. Snapshot every visible focusable window and call setFocusable(false) on them.
  2. Invoke the native hide().
  3. Send the synthetic mouseMove(-1, -1) to clear hover.
  4. Schedule setFocusable(true) restoration for the snapshot after 50ms.

The domain service carries none of this code.

Implementation Notes

  • w.hide.bind(w) captures the native method with this correctly bound, so Electron's C++ bindings continue to see the real BrowserWindow.
  • EventEmitter behavior (.on('hide', ...), .once('close', ...)) is untouched — the quirks patch only the method slots, not the emitter wiring.
  • Quirks run after onWindowCreated and after applyWindowBehavior fires. This ordering means the behavior layer's initial setter calls (e.g. first setAlwaysOnTop(true, level)) do not trigger the monkey-patched show/showInactive.
  • Quirks are applied per-window at creation time; there is no runtime toggle.

Declarative Behavior Layer

behavior captures configuration that's non-hacky, cross-platform, and needed beyond the Electron constructor. WindowManager applies these on window creation via applyWindowBehavior (in src/main/core/window/behavior.ts).

FieldTypeWhat it does
hideOnBlurbooleanInstalls a blur listener that calls window.hide() (with optional runtime override via wm.behavior.setHideOnBlur(id, enabled)).
alwaysOnTop{ level?: AlwaysOnTopLevel, relativeLevel?: number }Supplies the level / relativeLevel to setAlwaysOnTop calls — the single source of truth, read by: (1) the initial application after create (when windowOptions.alwaysOnTop is true), (2) wm.behavior.setAlwaysOnTop(id, enabled) runtime calls, (3) the macReapplyAlwaysOnTop quirk.
visibleOnAllWorkspaces{ enabled: boolean } & VisibleOnAllWorkspacesOptionsRuns window.setVisibleOnAllWorkspaces(enabled, options) once on create. Windows whose true/false options differ per call should not declare this (e.g. SelectionAction) — drive directly on BrowserWindow instead.
macShowInDockbooleanmacOS-only default for whether a window of this type CONTRIBUTES to Dock visibility (Dock shown iff any alive window contributes). Existence-based, not visibility-based: hiding a contributing window does NOT hide the Dock (Cmd+W semantics). When omitted, defaults to true. false is for helper windows (floating panels, menu-bar style overlays) that should never affect the Dock. Runtime override via wm.behavior.setMacShowInDockByType(type, value) — set it to false before window.hide() to enter tray mode, true before window.show() to leave. No-op on Windows/Linux.

Runtime Setters

Runtime setters for the behavior layer live on wm.behavior (a BehaviorController instance defined in src/main/core/window/behavior.ts). Grouping them under this sub-namespace mirrors the three-layer windowOptions / behavior / quirks split at the API surface.

SetterPurpose
wm.behavior.setHideOnBlur(id, enabled)Override the declared behavior.hideOnBlur per instance. Cleared on destroy and on pool releaseToPool — pool consumers that need a non-default value must re-apply after open() / reuse. No-op when the window does not declare behavior.hideOnBlur.
wm.behavior.setAlwaysOnTop(id, enabled)Toggle always-on-top using the level / relativeLevel declared in behavior.alwaysOnTop. When neither is declared, calls setAlwaysOnTop(enabled) with no level.
wm.behavior.setMacShowInDockByType(type, value)Override behavior.macShowInDock for an entire window type (not a single instance). Use for app-level tray-mode transitions: (Main, false) then hide() pulls the Dock icon down; (Main, true) then show() brings it back. Keyed by type so it can be set BEFORE the first instance exists (tray-on-launch). Multi-window safe: with Main + SubWindow both contributing, a wm.behavior.setMacShowInDockByType(Main, false) alone does NOT hide the Dock while any SubWindow is alive.

setVisibleOnAllWorkspaces intentionally has no WM-level setter — consumers call it directly on the BrowserWindow when needed. See README → When to Provide a Runtime Setter.

Platform Overrides

Static BrowserWindowConstructorOptions that differ per OS go in windowOptions.platformOverrides. Only the branch matching the current runtime is deep-merged into the final config; unmatched branches are discarded, and the platformOverrides field itself is stripped before reaching new BrowserWindow(...).

typescript
windowOptions: {
  width: 350, height: 43,
  frame: false, transparent: true,
  platformOverrides: {
    mac: { type: 'panel', hiddenInMissionControl: true, acceptFirstMouse: true },
    win: { type: 'toolbar', focusable: false },
    linux: { type: 'toolbar' } // focusable is set at runtime by the domain service
  },
  webPreferences: { /* ... */ }
}

Precedence (later wins) when merging inside mergeWindowOptions:

  1. baseOptions (registry windowOptions)
  2. baseOptions.platformOverrides[currentPlatform]
  3. Caller-provided overrides (via wm.open(type, { options }))
  4. Caller-provided overrides.platformOverrides[currentPlatform]

webPreferences is deep-merged in the same order.

When to Use Which Layer

SituationLayer
BrowserWindow constructor can accept it directlywindowOptions
Only a subset of OSes need a different static valuewindowOptions.platformOverrides[mac/win/linux]
Cross-platform, non-hacky declarative behavior (auto-hide on blur, initial setAlwaysOnTop level, dock visibility, initial setVisibleOnAllWorkspaces)behavior
OS-specific bug workaround requiring a hide/show/close hookquirks

The layers are composable: Selection's toolbar uses all three (windowOptions.platformOverrides for static per-OS differences, behavior.hideOnBlur / behavior.alwaysOnTop / behavior.visibleOnAllWorkspaces / behavior.macShowInDock for declarative behavior, and quirks.* for the macOS hide/show hacks).

Electron Edge Cases

  • setAlwaysOnTop(false, level) — Electron ignores level when enabled is false. The WM wm.behavior.setAlwaysOnTop(id, false) preserves the registry-declared level arg only for signature symmetry; the effect is identical.
  • VisibleOnAllWorkspacesOptions — both visibleOnFullScreen and skipTransformProcessType are documented as @platform darwin in Electron. They are silently ignored on Windows / Linux.
  • Linux Wayland "phantom popup" bugsetVisibleOnAllWorkspaces can put windows into a broken "floating popup" state on KDE Wayland. See MainWindowService.ts:573 for context. WM does not intervene; consumers using behavior.visibleOnAllWorkspaces on Linux should guard via runtime display-protocol checks if they see the regression.
  • Parameters<> type derivationAlwaysOnTopLevel is derived from Parameters<BrowserWindow['setAlwaysOnTop']>[1]. If Electron adds method overloads to setAlwaysOnTop, this derivation resolves against the last overload only and may silently narrow. Re-verify after Electron upgrades.