# FluidVoice Global Hotkey Event Tap System Architecture: A Deep Dive

> Explore the FluidVoice global hotkey event tap system architecture. Discover its three-layer design, retry-aware pipeline, and Core Graphics API implementation on macOS. Learn how it ensures robust event dispatch.

- Repository: [ALTIC/FluidVoice](https://github.com/altic-dev/FluidVoice)
- Tags: architecture
- Published: 2026-06-29

---

**FluidVoice implements a self-contained, retry-aware event-tap pipeline using macOS Core Graphics APIs, organized into three logical layers: tap creation with run-loop integration, robustness and health-check mechanisms, and event dispatch with a thread-safe state machine.**

The **FluidVoice** project (altic-dev/FluidVoice) is an open-source macOS dictation application that requires system-wide keyboard monitoring to function. Its global hotkey event tap system captures keyboard and mouse events across the entire macOS environment, enabling features like push-to-talk transcription, command mode activation, and prompt-based rewriting. Understanding this architecture reveals how modern macOS applications can implement reliable, always-on input monitoring while handling system-imposed constraints and race conditions.

## Three-Layer Architecture Overview

The event tap system in [`GlobalHotkeyManager.swift`](https://github.com/altic-dev/FluidVoice/blob/main/GlobalHotkeyManager.swift) separates concerns into distinct architectural layers that handle creation, resilience, and business logic.

**Layer 1: Event-Tap Creation and Run-Loop Integration**

This layer establishes the low-level communication channel with macOS. The `setupGlobalHotkey()` method creates a system-wide event tap using `CGEvent.tapCreate`, configuring it to listen for keyboard, mouse, and modifier flag changes. The tap wraps in a `CFMachPortCreateRunLoopSource` and registers with the main run loop via `CFRunLoopGetMain()`, ensuring events flow into the application’s event stream【/cache/repos/github.com/altic-dev/FluidVoice/main/Sources/Fluid/Services/GlobalHotkeyManager.swift#L62-L88】.

**Layer 2: Robustness and Health-Checking**

macOS automatically disables event taps under specific conditions—timeouts or user input protection mechanisms. The `handleTapDisableEvent(_:type:event:)` callback detects `.tapDisabledByTimeout` and `.tapDisabledByUserInput` states, immediately re-enabling the tap【/cache/repos/github.com/altic-dev/FluidVoice/main/Sources/Fluid/Services/GlobalHotkeyManager.swift#L94-L112】. For catastrophic failures, `setupGlobalHotkeyWithRetry()` implements an exponential back-off strategy, attempting recreation up to `maxRetryAttempts` (default 5) before failing gracefully【/cache/repos/github.com/altic-dev/FluidVoice/main/Sources/Fluid/Services/GlobalHotkeyManager.swift#L42-L50】.

**Layer 3: Event Dispatch and State Machine**

Raw `CGEvent` objects transform into semantic actions through `handleKeyEvent(proxy:type:event:)`. This layer parses `keyDown`, `keyUp`, `flagsChanged`, and mouse events, routing them to specialized handlers like `primaryModifierOnlyBehavior` and `handlePromptModeKeyDown`. Mutable state lives in a dedicated `HotkeyState` container protected by `NSLock`, ensuring thread safety when the background event-tap thread modifies flags consumed by the main thread【/cache/repos/github.com/altic-dev/FluidVoice/main/Sources/Fluid/Services/GlobalHotkeyManager.swift#L200-L260】.

## Core Components and Data Structures

Three primary data structures orchestrate the global hotkey event tap system:

- **GlobalHotkeyManager**: The central coordinator owning the `eventTap` reference, `runLoopSource`, and callback closures. It manages the lifecycle from initialization through teardown and exposes the public API for configuring shortcuts.

- **HotkeyState**: A lock-protected struct marked as `@unchecked Sendable` that holds volatile flags including `isKeyPressed`, `modifierOnlyKeyDown`, and `pendingHoldModeStart`. This structure eliminates race conditions between the Core Graphics callback thread and the UI thread【/cache/repos/github.com/altic-dev/FluidVoice/main/Sources/Fluid/Services/GlobalHotkeyManager.swift#L45-L62】.

- **HotkeyShortcut**: Encapsulates concrete keyboard combinations and mouse gestures with modifier masks. This model provides matching algorithms, conflict detection, and user-facing display strings, residing in [`Sources/Fluid/Models/HotkeyShortcut.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Models/HotkeyShortcut.swift)【/cache/repos/github.com/altic-dev/FluidVoice/main/Sources/Fluid/Models/HotkeyShortcut.swift#L5-L95】.

## Event Handling Modes

The architecture supports three distinct interaction patterns for transcription and command modes:

**Hold Mode**  
Press-and-hold behavior initiates on `keyDown` and terminates on `keyUp`. The system tracks the duration through `HotkeyState.isKeyPressed`, ensuring the action stops immediately upon release.

**Automatic Mode**  
This hybrid approach uses `automaticTapThresholdSeconds` (approximately 0.4 seconds) to distinguish between brief taps and extended holds. The `beginAutomaticPress` method starts a timer; if release occurs before the threshold, it triggers a tap action via `finishAutomaticPress`, otherwise it transitions to hold logic via `handleAutomaticKeyRelease`.

**Toggle Mode**  
A single keystroke alternates between active and inactive states. The implementation checks `asrService.isRunning` to determine whether to invoke `stopAndProcessCallback` or `startRecordingCallback`, requiring no additional state tracking beyond the service’s running flag.

**Modifier-Only Shortcuts**  
When users press only modifier keys (Command, Option, Control), the `handleModifierOnlyShortcutFlagsChanged` handler creates a pending task stored in `HotkeyState.pendingHoldModeStart`. If no additional keys arrive within the hold delay, the system executes the `onHoldStart` closure; otherwise, `cancelPendingModifierOnlyHoldStart` aborts the operation.

## Implementation Details

### Creating the Event Tap

The initialization sequence delays tap creation by 1.5 seconds to allow the system to grant Accessibility permissions. The `setupGlobalHotkey()` function configures the tap to listen for mouse, key, and flags changed events:

```swift
// From GlobalHotkeyManager.swift
func setupGlobalHotkey() {
    // Create the event tap for keyboard, mouse, and flags changed events
    let eventMask = (1 << CGEventType.keyDown.rawValue) | 
                    (1 << CGEventType.keyUp.rawValue) | 
                    (1 << CGEventType.flagsChanged.rawValue) |
                    (1 << CGEventType.mouseMoved.rawValue)
    
    guard let tap = CGEvent.tapCreate(
        tap: .cgSessionEventTap,
        place: .headInsertEventTap,
        options: .defaultTap,
        eventsOfInterest: CGEventMask(eventMask),
        callback: { proxy, type, event, refcon in
            // Handle event
            return Unmanaged.passRetained(event)
        },
        userInfo: nil
    ) else {
        return
    }
    
    self.eventTap = tap
    self.runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
    CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, .defaultMode)
    CGEvent.tapEnable(tap: tap, enable: true)
}

```

### Retry Logic and Robustness

The retry mechanism wraps the setup logic to handle transient failures during permissions requests or system resource constraints:

```swift
// Exponential backoff with maxRetryAttempts (default 5)
func setupGlobalHotkeyWithRetry(attempt: Int = 0) {
    setupGlobalHotkey()
    
    if eventTap == nil && attempt < maxRetryAttempts {
        let delay = pow(2.0, Double(attempt)) // 1, 2, 4, 8, 16 seconds
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
            self.setupGlobalHotkeyWithRetry(attempt: attempt + 1)
        }
    }
}

```

### Thread Safety and State Management

The event tap callback executes on a background thread, requiring synchronization for shared state. The `HotkeyState` struct uses `NSLock` to protect mutable properties:

```swift
struct HotkeyState {
    private let lock = NSLock()
    
    private var _isKeyPressed: Bool = false
    var isKeyPressed: Bool {
        get { lock.withLock { _isKeyPressed } }
        set { lock.withLock { _isKeyPressed = newValue } }
    }
    
    // Additional state: pendingHoldModeStart, modifierOnlyKeyDown, etc.
}

```

## Code Examples

### Initializing the Global Hotkey Manager

Applications configure the manager by providing shortcut definitions and callbacks for transcription lifecycle events:

```swift
let hotkeyMgr = GlobalHotkeyManager(
    asrService: asrService,
    primaryShortcuts: SettingsStore.shared.transcribeHotkeys,
    promptModeShortcut: SettingsStore.shared.promptHotkey,
    commandModeShortcut: SettingsStore.shared.commandHotkey,
    rewriteModeShortcut: SettingsStore.shared.rewriteHotkey,
    promptShortcutAssignments: SettingsStore.shared.promptAssignments,
    promptModeShortcutEnabled: true,
    commandModeShortcutEnabled: true,
    rewriteModeShortcutEnabled: true,
    startRecordingCallback: { await asrService.start() },
    stopAndProcessCallback: { await asrService.stopAndProcess() }
)

```

### Handling Modifier-Only Shortcuts

The modifier-only detection logic tracks flags-changed events separately from standard keystrokes:

```swift
func handleModifierOnlyShortcutFlagsChanged(_ flags: CGEventFlags) {
    let isModifierActive = flags.contains(.maskCommand) || 
                           flags.contains(.maskOption)
    
    if isModifierActive && !hotkeyState.modifierOnlyKeyDown {
        hotkeyState.modifierOnlyKeyDown = true
        hotkeyState.pendingHoldModeStart = DispatchWorkItem {
            self.activateHoldMode()
        }
        DispatchQueue.main.asyncAfter(
            deadline: .now() + holdThreshold, 
            execute: hotkeyState.pendingHoldModeStart!
        )
    } else if !isModifierActive {
        cancelPendingModifierOnlyHoldStart()
        hotkeyState.modifierOnlyKeyDown = false
    }
}

```

### Simulating Events for Testing

The test suite exercises the event pipeline by injecting synthetic `CGEvent` objects:

```swift
let testShortcut = HotkeyShortcut(keyCode: 0, modifierFlags: .maskCommand)
let event = CGEvent(
    keyboardEventSource: nil, 
    virtualKey: CGKeyCode(testShortcut.keyCode), 
    keyDown: true
)!

hotkeyMgr.handleKeyEvent(
    proxy: .default, 
    type: .keyDown, 
    event: event
)

```

## Key Source Files

The global hotkey event tap system spans the following files in the altic-dev/FluidVoice repository:

| File | Purpose |
|------|---------|
| [`Sources/Fluid/Services/GlobalHotkeyManager.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/GlobalHotkeyManager.swift) | Core event-tap creation, retry logic, and state machine implementation |
| [`Sources/Fluid/Models/HotkeyShortcut.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Models/HotkeyShortcut.swift) | Data models for keyboard/mouse shortcuts with matching algorithms |
| [`Tests/FluidDictationIntegrationTests/HotkeyShortcutTests.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Tests/FluidDictationIntegrationTests/HotkeyShortcutTests.swift) | Unit tests covering hold, automatic, and toggle modes |
| [`Sources/Fluid/Services/ASRService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift) | Audio service providing `isRunning` state and start/stop APIs consumed by the hotkey manager |

## Summary

- FluidVoice’s global hotkey event tap system uses **macOS Core Graphics APIs** (`CGEvent.tapCreate`) to monitor system-wide input events.
- The **three-layer architecture** separates tap creation, robustness/retry logic, and event dispatch into distinct concerns.
- **Automatic recovery mechanisms** detect when macOS disables the tap and rebuild it using exponential back-off with up to 5 retry attempts.
- **Thread-safe state management** via `HotkeyState` and `NSLock` ensures race-condition-free operation between the background event-tap thread and the main application thread.
- **Multiple interaction modes** (Hold, Automatic/Tap, Toggle) accommodate different user workflows for dictation and command input.

## Frequently Asked Questions

### How does FluidVoice handle macOS disabling the event tap?

The `handleTapDisableEvent(_:type:event:)` callback monitors for `.tapDisabledByTimeout` and `.tapDisabledByUserInput` event types. When detected, it immediately re-enables the tap using `CGEvent.tapEnable(tap:enable:)`【/cache/repos/github.com/altic-dev/FluidVoice/main/Sources/Fluid/Services/GlobalHotkeyManager.swift#L94-L112】. If the tap fails to recreate, the `setupGlobalHotkeyWithRetry()` method implements an exponential back-off strategy, attempting reconstruction up to five times before abandoning the effort.

### What makes the hotkey state thread-safe in FluidVoice?

The `HotkeyState` struct isolates mutable flags using an `NSLock` instance. All properties like `isKeyPressed` and `pendingHoldModeStart` wrap their getters and setters with `lock.withLock()`, ensuring atomic access from the background thread running the Core Graphics callback and the main UI thread【/cache/repos/github.com/altic-dev/FluidVoice/main/Sources/Fluid/Services/GlobalHotkeyManager.swift#L45-L62】. This prevents race conditions when the user releases a key simultaneously with the processing of a hotkey action.

### How does FluidVoice distinguish between a tap and a hold action?

The automatic mode uses a threshold timer set to approximately 0.4 seconds (`automaticTapThresholdSeconds`). When the user presses a key, `beginAutomaticPress` starts a timer. If `handleAutomaticKeyRelease` fires before the threshold elapses, the system treats it as a tap; otherwise, it transitions to hold mode. This logic resides in the event dispatch layer of `GlobalHotkeyManager`.

### Where is the event tap callback registered in the run loop?

The callback registers with the main run loop through `CFMachPortCreateRunLoopSource` and `CFRunLoopAddSource`. Specifically, `setupGlobalHotkey()` creates a `CFRunLoopSource` from the mach port returned by `CGEvent.tapCreate`, then adds it to `CFRunLoopGetMain()` with the default mode, ensuring event processing occurs on the main thread despite the tap existing at the system level【/cache/repos/github.com/altic-dev/FluidVoice/main/Sources/Fluid/Services/GlobalHotkeyManager.swift#L62-L88】.