# How to Debug Issues with ShortcutManager.swift in OpenSuperWhisper: A Complete Guide

> Debug ShortcutManager.swift issues in OpenSuperWhisper. Learn to verify trigger modes, add logging, and confirm shortcut registration for faster troubleshooting. Get the complete guide.

- Repository: [Starmel/OpenSuperWhisper](https://github.com/Starmel/OpenSuperWhisper)
- Tags: how-to-guide
- Published: 2026-07-05

---

**To debug ShortcutManager.swift effectively, verify which trigger mode is active (regular shortcut, modifier-only, or mouse button), add strategic logging to `handleKeyDown()` and `handleKeyUp()`, and confirm that the `KeyboardShortcuts` library properly registers the `.toggleRecord` name in `setupKeyboardShortcuts()`.**

ShortcutManager.swift serves as the central command hub for hot-key handling in the OpenSuperWhisper macOS application, bridging user preferences defined in `AppPreferences` with low-level system monitors. When shortcuts fail to fire, hold-to-record behaves unexpectedly, or the indicator window never appears, developers need targeted debugging strategies to isolate the root cause. This guide provides specific techniques for troubleshooting [`OpenSuperWhisper/ShortcutManager.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/ShortcutManager.swift) based on the actual source implementation and its dependencies.

## Understanding the ShortcutManager.swift Architecture

### Core Components and Their Interactions

The `ShortcutManager` class operates as a singleton that coordinates between multiple specialized monitors and the UI layer. Understanding these relationships is critical for effective debugging:

- **ShortcutManager**: The singleton coordinator that registers hot-keys and manages three distinct trigger modes. It initializes with a log statement at line 25 and directly instantiates `KeyboardShortcuts`, `ModifierKeyMonitor`, `MouseButtonMonitor`, and `IndicatorWindowManager`.

- **KeyboardShortcuts**: A third-party library (declared in [`Package.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/Package.swift)) that watches for the named shortcut `.toggleRecord` and delivers callbacks on key down and key up events. This is configured in `setupKeyboardShortcuts()` at lines 54-71.

- **ModifierKeyMonitor**: A low-level Carbon monitor that watches for standalone modifier key presses (e.g., ⌥ Option). This activates when users select modifier-only hot-key mode, with callbacks wired in `setupRecordingTrigger()` at lines 99-112.

- **MouseButtonMonitor**: Similar to the modifier monitor but tracks mouse button events. Enabled when mouse button hot-key is selected, with implementation at lines 84-98 in `setupRecordingTrigger()`.

- **IndicatorWindowManager**: Creates the floating recording window and controls audio capture lifecycle. Called from `handleKeyDown()` (lines 28-44) and `handleKeyUp()` to show and hide the recording interface.

- **AppPreferences**: Central storage for user settings including `holdToRecord`, `modifierOnlyHotkey`, and `mouseButtonHotkey`. Read in `setupRecordingTrigger()` at lines 75-78 and within `handleKeyDown/Up` at line 26.

### Execution Flow from User Input to Recording

The typical execution path through ShortcutManager.swift follows six distinct stages:

1. **Singleton Initialization**: `ShortcutManager.shared` is lazily instantiated once, logging "ShortcutManager init" to confirm activation.

2. **Keyboard Registration**: `setupKeyboardShortcuts()` configures `KeyboardShortcuts.onKeyDown` and `onKeyUp` for the `.toggleRecord` identifier.

3. **Trigger Mode Selection**: `setupRecordingTrigger()` reads `AppPreferences.shared` and enables the appropriate monitor while disabling others. The system prioritizes mouse button > modifier key > regular shortcut, with monitors being mutually exclusive as noted in the comment at line 78.

4. **Key Down Handling**: `handleKeyDown()` creates an `IndicatorWindowManager` view model if none exists, captures the current caret/cursor position, and initiates recording. If *hold-to-record* is enabled, it queues a `DispatchWorkItem` to set `holdMode` after the `holdThreshold` (0.3 seconds).

5. **Key Up Handling**: `handleKeyUp()` cancels any pending `holdWorkItem`. If operating in hold mode, it stops the recording and cleans up the view model.

## Common Debugging Scenarios and Solutions

### Hot-Key Not Firing at All

When no shortcuts respond, the issue typically stems from registration failures in the `KeyboardShortcuts` library or disabled monitors.

First, verify the singleton initialized by checking for the "ShortcutManager init" log. If absent, the manager never instantiated. Next, confirm registration succeeded by adding a print statement immediately after `KeyboardShortcuts.enable(.toggleRecord)` in `setupKeyboardShortcuts()`.

If using modifier-only or mouse button modes, ensure `setupRecordingTrigger()` actually enabled the correct monitor. The three modes are mutually exclusive, so enabling one disables others.

### Hold-to-Record Mode Malfunctioning

Hold-to-record logic depends on the `holdWorkItem` DispatchWorkItem and the `holdMode` boolean flag. Common bugs include the work item not cancelling properly, leaving `holdMode` stuck in the wrong state.

Add logging to track state transitions:

```swift
print("holdMode before: \(holdMode), workItem exists: \(holdWorkItem != nil)")

```

If `holdToRecordEnabled` reads false from `AppPreferences`, verify the preference key exists and `holdThreshold` hasn't been set to an excessively high value.

### Wrong Trigger Mode Active

Because ShortcutManager.swift only allows one active monitor at a time, configuration issues in `AppPreferences` can cause unexpected behavior.

Determine the active mode by logging the preference flags at the start of `setupRecordingTrigger()`:

```swift
print("Active mode: mouse=\(useMouseButtonHotkey) modifier=\(useModifierOnlyHotkey) regular=\(!useMouseButtonHotkey && !useModifierOnlyHotkey)")

```

If these flags don't match your expectation, the `hotkeySettingsChanged` notification likely failed to trigger after preference changes, preventing `setupRecordingTrigger()` from re-evaluating the configuration.

### Indicator Window Not Appearing or Positioned Incorrectly

When the indicator window fails to display, verify that `IndicatorWindowManager.shared.show(nearPoint:)` returns a valid view model. Add this check after line 38 in `handleKeyDown()`:

```swift
guard let vm = IndicatorWindowManager.shared.show(nearPoint: cursorPosition ?? .zero) else {
    print("Failed to create indicator window")
    return
}
print("Created view model: \(vm)")

```

For positioning issues, inspect the values returned by `FocusUtils.getCaretRect()` before passing them to `show(nearPoint:)`. Nil or stale coordinates indicate the focus utility failed to detect the current text field.

## Practical Debugging Code Examples

### Logging the Full Hot-Key Lifecycle

Add temporary logging without altering core logic by extending ShortcutManager:

```swift
extension ShortcutManager {
    private func logEvent(_ name: String) {
        print("[ShortcutManager] \(name) – vm: \(String(describing: activeVm)) holdMode: \(holdMode)")
    }
    
    private func handleKeyDown() {
        logEvent("keyDown start")
        // original implementation...
        logEvent("keyDown end")
    }
    
    private func handleKeyUp() {
        logEvent("keyUp start")
        // original implementation...
        logEvent("keyUp end")
    }
}

```

This trace helps identify whether key events are being swallowed or if the view model lifecycle is failing.

### Forcing a Specific Trigger Mode

To isolate monitor-specific bugs, temporarily override preferences in a debug build:

```swift
// Force modifier-only mode
AppPreferences.shared.modifierOnlyHotkey = ModifierKey.control.rawValue
AppPreferences.shared.mouseButtonHotkey = MouseButton.none.rawValue
NotificationCenter.default.post(name: .hotkeySettingsChanged, object: nil)

```

This triggers the code path at lines 99-114, allowing you to verify `ModifierKeyMonitor` behavior independently of the regular shortcut system.

### Simulating Key Presses for Unit Testing

For automated testing or manual debugging without hardware input:

```swift
func simulateToggleRecord() {
    ShortcutManager.shared.handleKeyDown()
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        ShortcutManager.shared.handleKeyUp()
    }
}

```

Note: This bypasses the actual `KeyboardShortcuts` layer and should only be used for debugging the downstream logic in `handleKeyDown()` and `handleKeyUp()`.

## Summary

- **ShortcutManager.swift** acts as a singleton coordinator between user preferences and three mutually exclusive trigger modes (regular shortcut, modifier-only, or mouse button).
- **Debug registration issues** by verifying the `KeyboardShortcuts.enable(.toggleRecord)` call succeeds and checking for the "ShortcutManager init" log.
- **Trace hold-to-record problems** by monitoring the `holdWorkItem` DispatchWorkItem and `holdMode` state transitions in `handleKeyDown()` and `handleKeyUp()`.
- **Identify active trigger modes** by logging `useMouseButtonHotkey` and `useModifierOnlyHotkey` flags at the start of `setupRecordingTrigger()`.
- **Fix indicator window issues** by validating that `IndicatorWindowManager.shared.show()` returns a non-nil view model and that `FocusUtils.getCaretRect()` provides valid coordinates.

## Frequently Asked Questions

### Why does my shortcut only work once and then stop responding?

This typically occurs when the `holdWorkItem` DispatchWorkItem fails to cancel in `handleKeyUp()`, leaving `holdMode` stuck in an inconsistent state. Add logging to verify `holdWorkItem?.cancel()` executes and that `holdMode` resets properly after each key cycle. Also ensure `AppPreferences.shared.holdToRecord` hasn't been inadvertently disabled.

### How do I know which trigger mode is currently active?

Insert a debug print statement at the beginning of `setupRecordingTrigger()` to log the boolean flags: `useMouseButtonHotkey`, `useModifierOnlyHotkey`, and the negated combination for regular shortcuts. Since the monitors are mutually exclusive as implemented at line 78, only one flag should be true at any given time.

### Can I debug ShortcutManager.swift without rebuilding the entire app?

Yes. Since ShortcutManager is a singleton with public methods, you can create a temporary debug extension or add logging statements directly to `handleKeyDown()` and `handleKeyUp()`. You can also programmatically trigger these handlers using the simulation code shown above to test the recording logic without physically pressing keys.

### Where does ShortcutManager.swift store its configuration?

The class reads all settings from `AppPreferences.shared`, which persists user choices for modifier keys, mouse buttons, and hold-to-record preferences. When debugging, verify that `AppPreferences` returns expected values at lines 75-78, and ensure the `hotkeySettingsChanged` notification posts correctly after preference changes to trigger `setupRecordingTrigger()` reconfiguration.