# 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](./README.md#configuration-layers-windowoptions--behavior--quirks). - **`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 | Quirk | Patches | Behavior | |---|---|---| | `macRestoreFocusOnHide: boolean` | `hide()`, `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: boolean` | `hide()` | After invoking the native `hide()`, send `webContents.sendInputEvent({ type: 'mouseMove', x: -1, y: -1 })` to clear any residual hover state. | | `macReapplyAlwaysOnTop: boolean` | `show()`, `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`). | Field | Type | What it does | |---|---|---| | `hideOnBlur` | `boolean` | Installs 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 } & VisibleOnAllWorkspacesOptions` | Runs `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. | | `macShowInDock` | `boolean` | macOS-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. | Setter | Purpose | |---|---| | `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](./README.md#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 | Situation | Layer | |---|---| | `BrowserWindow` constructor can accept it directly | `windowOptions` | | Only a subset of OSes need a different static value | `windowOptions.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 hook | `quirks` | 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" bug** — `setVisibleOnAllWorkspaces` 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 derivation** — `AlwaysOnTopLevel` is derived from `Parameters[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.