# How cmux Handles Window Focus and Prevents Focus Stealing: A Deep Dive into AppKit and Bonsplit

> Discover how cmux prevents focus stealing with layered window focus management, AppKit integration, and socket command gating. Learn about its robust safeguards.

- Repository: [manaflow-ai/cmux](https://github.com/manaflow-ai/cmux)
- Tags: deep-dive
- Published: 2026-03-29

---

**cmux implements a layered focus-management strategy that synchronizes window-level activation, AppKit first-responder state, and model-level panel selection while explicitly blocking accidental focus-stealing through socket command gating, omnibar suppression, and programmatic split safeguards.**

The `manaflow-ai/cmux` terminal multiplexer built on AppKit, SwiftUI, and a custom split-pane engine (Bonsplit) faces unique focus-management challenges. Because UI actions like splits, tab moves, and socket commands constantly mutate the visual hierarchy, cmux maintains strict coordination between the **window-level focus**, **AppKit first-responder**, and **model-level "focused panel"** to prevent unwanted focus theft.

## Window-Level Focus Management

All window activation operations in cmux route through a single chokepoint in [`Sources/AppDelegate.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/AppDelegate.swift). The `focusMainWindow(windowId:)` method handles bringing windows to the foreground and claiming application activation.

### Activating Windows via AppDelegate

When a command requests window focus, cmux calls `AppDelegate.focusMainWindow(windowId:)` to ensure the target window becomes key and the app activates:

```swift
// Sources/AppDelegate.swift
func focusMainWindow(windowId: UUID) -> Bool {
    guard let window = mainWindow(for: windowId) else { return false }
    window.makeKeyAndOrderFront(nil)          // bring to front
    NSApp.activate(ignoringOtherApps: true)   // become the active app
    return true
}

```

Higher-level focus actions, including `TerminalController.focusWindow(_:)` and V2 socket commands (`focus_window`), eventually delegate to this method. For example, `TerminalController` wraps the call in a synchronous main-thread block:

```swift
// Sources/TerminalController.swift – focusWindow()
let ok = v2MainSync { AppDelegate.shared?.focusMainWindow(windowId: windowId) ?? false }

```

## Per-Panel Focus Synchronization

Each surface (terminal, browser, or other content) lives inside a **panel** managed by the `Workspace` class. The `Workspace.focusPanel(_:previousHostedView:trigger:)` method in [`Sources/Workspace.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift) serves as the single source of truth for aligning model state with UI state.

### The Workspace.focusPanel Method

This centralized method updates four critical elements simultaneously:

- **Model state**: Updates `focusedPanelId` via `markExplicitFocusIntent(on:)`
- **Bonsplit pane**: Selects the pane containing the panel using `focusPane` and `selectTab`
- **AppKit first-responder**: Makes the panel's native view (`GhosttySurfaceScrollView` for terminals, `WKWebView` for browsers) the first responder
- **Focus override**: Respects `AppFocusState.overrideIsFocused` for automation scenarios

```swift
// Sources/Workspace.swift – focusPanel()
guard let tabId = surfaceIdFromPanelId(panelId) else { return }
let targetPaneId = bonsplitController.allPaneIds.first { ... }
if let targetPaneId, !selectionAlreadyConverged {
    bonsplitController.focusPane(targetPaneId)        // pane → focused
}
if !selectionAlreadyConverged {
    bonsplitController.selectTab(tabId)              // tab → selected
}
applyTabSelection(tabId: tabId, inPane: targetPaneId,
                  reassertAppKitFocus: !shouldSuppressReentrantRefocus,
                  focusIntent: activationIntent,
                  previousTerminalHostedView: previousTerminalHostedView)

```

### Re-asserting Focus After Non-Focus Splits

When splits occur without explicit focus intent, Bonsplit's asynchronous layout could temporarily diverge from the desired focus state. The `reassertFocusAfterNonFocusSplit()` method guarantees that newly-created panels receive focus even when layout races occur:

```swift
// Sources/Workspace.swift – reassertFocusAfterNonFocusSplit()
if focusedPanelId == splitPanelId {
    focusPanel(preferredPanelId, previousHostedView: allowPreviousHostedView ? previousHostedView : nil)
}

```

## Preventing Focus Stealing in cmux

cmux employs multiple defensive layers to ensure focus only changes when explicitly requested, neutralizing transient UI race conditions that could pull focus away from user-intended targets.

### Socket Command Gating

Only authorized socket commands may mutate focus. The `TerminalController.socketCommandAllowsInAppFocusMutations()` static method checks the socket's **focus-override** flag and command key to determine if the operation should proceed:

```swift
// Sources/TerminalController.swift – socketCommandAllowsInAppFocusMutations()
static func socketCommandAllowsInAppFocusMutations() -> Bool {
    // Returns true for commands that are documented to modify focus
}

```

All UI-mutating calls—including `focusWindow`, `focusSurface`, and `focus_pane`—wrap their logic with this guard. This prevents arbitrary remote clients from stealing focus unless the policy explicitly permits it.

### Browser Omnibar Suppression

When a browser panel receives focus, the omnibar (address bar) would otherwise auto-focus immediately, pulling first-responder status away from terminals. `BrowserPanel.suppressOmnibarAutofocus(for:)` sets a timestamp that the panel checks before auto-focusing:

```swift
// Sources/Panels/BrowserPanel.swift – suppressOmnibarAutofocus()
func suppressOmniboxAutofocus(for seconds: TimeInterval) {
    suppressOmnibarAutofocusUntil = Date().addingTimeInterval(seconds)
}
func shouldSuppressOmnibarAutofocus() -> Bool {
    if let until = suppressOmnibarAutofocusUntil { return Date() < until }
    return false
}

```

The focus path that moves a web view into focus calls this suppression for approximately one second before invoking `window.makeFirstResponder(webView)`:

```swift
// Sources/TerminalController.swift – v2BrowserFocus()
browserPanel.suppressOmnibarAutofocus(for: 1.0)
window.makeFirstResponder(webView)

```

### Safeguards for Programmatic Splits

When creating splits programmatically via `newTerminalSplit` or `newBrowserSplit`, the old panel's `becomeFirstResponder` side-effects could race with Bonsplit's layout. The code **suppresses the old view's re-parent focus**, explicitly calls `focusPanel` on the new panel, and runs delayed re-assertions to guarantee convergence:

```swift
// Sources/Workspace.swift – split creation (excerpt)
previousHostedView?.suppressReparentFocus()
focusPanel(newPanel.id, previousHostedView: previousHostedView)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
    previousHostedView?.clearSuppressReparentFocus()
}

```

Three nested `asyncAfter` blocks progressively clear suppression and re-assert focus, ensuring any transient divergence resolves within approximately 150 milliseconds.

### Modal UI Protection

Before any focus change, `Workspace.isCommandPaletteVisibleForWorkspaceWindow()` checks whether the global command-palette UI is active. If visible, focus changes are ignored to prevent stealing the palette's first-responder status:

```swift
guard !isCommandPaletteVisibleForWorkspaceWindow() else { return }

```

Similar guards exist for alerts and other modal dialogs, ensuring that `focus_main_window` cannot trigger from within close-confirmation dialogs or similar modal contexts.

## Summary

- **Centralized window activation**: All window-level focus routes through `AppDelegate.focusMainWindow(windowId:)` in [`Sources/AppDelegate.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/AppDelegate.swift), ensuring consistent `NSApp.activate` behavior.
- **Unified panel focus**: `Workspace.focusPanel(_:previousHostedView:trigger:)` in [`Sources/Workspace.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift) synchronizes model state, Bonsplit pane selection, and AppKit first-responder in a single transaction.
- **Socket-level security**: `TerminalController.socketCommandAllowsInAppFocusMutations()` gates focus-mutating commands, preventing unauthorized remote focus changes.
- **Browser focus protection**: `BrowserPanel.suppressOmnibarAutofocus(for:)` prevents the address bar from stealing focus when switching to browser panels.
- **Split creation safety**: Suppression of re-parent focus and delayed re-assertion logic eliminates race conditions during programmatic split operations.
- **Modal safeguards**: Visibility checks for command palettes and dialogs block external focus changes while modal UI owns first-responder.

## Frequently Asked Questions

### How does cmux prevent a terminal command from stealing focus when opening a browser split?

cmux uses **socket command gating** via `TerminalController.socketCommandAllowsInAppFocusMutations()` to verify that only explicitly authorized commands may request focus changes. When a browser split opens, the system also invokes `BrowserPanel.suppressOmnibarAutofocus(for: 1.0)` to prevent the address bar from immediately capturing first-responder status, ensuring the terminal maintains focus unless the user explicitly requests otherwise.

### What happens if Bonsplit's asynchronous layout conflicts with a focus change request?

When programmatic splits occur, cmux calls `previousHostedView?.suppressReparentFocus()` to disable the old view's focus side-effects, then explicitly focuses the new panel via `Workspace.focusPanel()`. The system runs three nested `DispatchQueue.main.asyncAfter` delays (at 0.05s, 0.1s, and 0.15s) to progressively clear suppression and re-assert focus, guaranteeing convergence even when Bonsplit's layout animation temporarily diverges from the model state.

### Can automation scripts force cmux to appear focused without triggering focus-stealing side effects?

Yes. cmux provides `AppFocusState.overrideIsFocused`, which allows automation to force a specific focus state without triggering the standard focus mutation guards. This override bypasses the normal socket command gating while still respecting modal UI protections, enabling scripted interactions to maintain consistent window appearance without disrupting the user's current focus context.

### Why does cmux check for command palette visibility before changing focus?

The `isCommandPaletteVisibleForWorkspaceWindow()` check prevents external focus requests from interrupting user interaction with the global command palette. If the palette is visible, focus changes return early, preserving the palette's first-responder status. This safeguard ensures that background processes or socket commands cannot pull focus away from active text input in the command palette or modal dialogs.