How to Implement Custom Keyboard Shortcuts in OpenSuperWhisper

OpenSuperWhisper leverages the Swift KeyboardShortcuts package to register global hotkeys, with ShortcutManager.swift handling runtime wiring, AppPreferences.swift persisting user choices, and Settings.swift providing the configuration interface.

OpenSuperWhisper is a macOS transcription application built in Swift that requires reliable global keyboard shortcuts for hands-free recording control. Implementing custom shortcuts in this codebase involves extending the existing abstraction layer that supports three distinct trigger modes: traditional key combinations, single-modifier hotkeys, and mouse button events. This guide walks through the exact implementation pattern used in the repository to add new global shortcuts without modifying low-level event-tap code.

Understanding the Keyboard Shortcut Architecture

The shortcut system follows a pipeline architecture that separates definition, configuration, and execution concerns across specific source files.

Core Components

The implementation relies on four primary components working in concert:

The Three Trigger Modes

OpenSuperWhisper abstracts three input methods through a unified interface:

  1. Key Combination – Standard modifier-plus-key shortcuts (e.g., ⌘+) handled via KeyboardShortcuts.onKeyDownandonKeyUp` callbacks.

  2. Single Modifier Key – Standalone modifier detection (e.g., just the Fn key) monitored by ModifierKeyMonitor watching OS-level flag-change events.

  3. Mouse Button – Low-level button capture for peripherals, implemented in MouseButtonMonitor using NSEvent monitoring.

Step-by-Step Implementation Guide

To add a new global shortcut—such as "Toggle Transcription"—follow this exact pattern derived from the source code.

Step 1: Define the Shortcut Identifier

Extend KeyboardShortcuts.Name in OpenSuperWhisper/ShortcutManager.swift with a static constant and sensible default:

// OpenSuperWhisper/ShortcutManager.swift
extension KeyboardShortcuts.Name {
    static let toggleTranscription = Self(
        "toggleTranscription",
        default: .init(.t, modifiers: [.command, .option])   // Cmd-Option-T
    )
}

This declaration associates a string identifier with a default key combination that users can later override through the Settings UI.

Step 2: Register Callbacks in ShortcutManager

Inside the setupKeyboardShortcuts() method, register event listeners for your new shortcut alongside existing registrations:

private func setupKeyboardShortcuts() {
    // Existing shortcuts
    KeyboardShortcuts.onKeyDown(for: .toggleRecord) { [weak self] in
        self?.handleKeyDown()
    }
    KeyboardShortcuts.onKeyUp(for: .toggleRecord) { [weak self] in
        self?.handleKeyUp()
    }

    // New shortcut registration
    KeyboardShortcuts.onKeyDown(for: .toggleTranscription) { [weak self] in
        self?.toggleTranscription()
    }
}

The KeyboardShortcuts library automatically manages the global event monitor when these callbacks are registered.

Step 3: Create the Action Handler

Implement the handler method that executes when the shortcut fires. Place this in ShortcutManager.swift or delegate to a service class:

private func toggleTranscription() {
    if TranscriptionService.shared.isRunning {
        TranscriptionService.shared.stop()
    } else {
        TranscriptionService.shared.start()
    }
}

This pattern mirrors the existing handleKeyDown() and handleKeyUp() methods (lines 22-68 in ShortcutManager.swift) that control the recording indicator via IndicatorWindowManager.

Step 4: Add UI Controls in SettingsView

Expose the configuration option in the Settings interface. In OpenSuperWhisper/Settings.swift, add a control within the shortcut settings section:

// Inside SettingsView.shortcutSettings
Form {
    // Existing UI elements...
    
    VStack(alignment: .leading, spacing: 8) {
        Text("Toggle Transcription")
            .font(.subheadline)
        
        KeyboardShortcuts.Name.toggleTranscription
            .editor()  // Provided by KeyboardShortcuts library
            .frame(maxWidth: 250)
    }
    .padding(.vertical, 4)
}

The .editor() view modifier automatically renders the current key combination and provides a capture interface for user customization.

Step 5: Persist User Preferences

While the KeyboardShortcuts package automatically persists key combinations, custom modifier-only or mouse button triggers require explicit storage. Add a @Published property to SettingsViewModel:

@Published var customModifier: ModifierKey = .function

Bind this property to a picker in SettingsView, and ShortcutManager will read the value via AppPreferences.shared.modifierOnlyHotkey during setupRecordingTrigger() initialization.

Key Source Files Reference

The following files constitute the complete shortcut infrastructure in OpenSuperWhisper:

File Role
OpenSuperWhisper/ShortcutManager.swift Central hotkey manager; defines default shortcuts, registers callbacks, and selects active trigger mode.
OpenSuperWhisper/Utils/AppPreferences.swift UserDefaults wrapper storing selected hotkey options.
OpenSuperWhisper/ModifierKeyMonitor.swift Low-level monitor for single-modifier hotkeys using CGEvent.tapCreate.
OpenSuperWhisper/MouseButtonMonitor.swift Low-level monitor for mouse-button triggers capturing otherMouseDown/Up events.
OpenSuperWhisper/Settings.swift SwiftUI view presenting the Shortcuts tab and binding selections to SettingsViewModel.
OpenSuperWhisper/Utils/KeyboardLayoutProvider.swift Layout-aware key-code conversion utility.

Advanced Customization Options

Implementing Modifier-Only Shortcuts

For shortcuts that trigger on a single modifier key (like Fn or Control), leverage the existing ModifierKeyMonitor infrastructure. Add your modifier preference to AppPreferences.swift, then extend setupRecordingTrigger() in ShortcutManager.swift to initialize a ModifierKeyMonitor instance that calls your custom handler when the specific modifier flags change.

Adding Mouse Button Triggers

Mouse button support follows the same pattern using MouseButtonMonitor. Define the button constant in AppPreferences, then instantiate MouseButtonMonitor with a callback closure in setupRecordingTrigger(). The monitor automatically handles the low-level NSEvent tap creation and cleanup.

Summary

  • OpenSuperWhisper uses the KeyboardShortcuts Swift package for cross-application global hotkey registration.
  • ShortcutManager.swift serves as the central coordinator, defining shortcuts in the KeyboardShortcuts.Name extension and wiring callbacks in setupKeyboardShortcuts().
  • Three trigger modes are supported: traditional key combinations (via KeyboardShortcuts), single modifiers (via ModifierKeyMonitor), and mouse buttons (via MouseButtonMonitor).
  • Persistence is handled through AppPreferences.swift for non-standard inputs, while the KeyboardShortcuts library automatically saves key combinations.
  • UI exposure requires adding a .editor() view to SettingsView.swift and binding it to the corresponding KeyboardShortcuts.Name constant.

Frequently Asked Questions

How do I change the default key combination for existing shortcuts?

Modify the default parameter in the KeyboardShortcuts.Name extension within OpenSuperWhisper/ShortcutManager.swift. For example, change .init(.graveAccent, modifiers: [.command]) to .init(.r, modifiers: [.command, .shift]) to switch from Command-` to Command-Shift-R. Users can still override this through the Settings UI, but your default will apply on fresh installations.

Why does my custom shortcut work in development but not in release builds?

Release builds require specific entitlements for global event monitoring. Ensure your app has the Input Monitoring permission granted in System Settings > Security & Privacy > Privacy > Input Monitoring. Additionally, verify that ModifierKeyMonitor.swift and MouseButtonMonitor.swift are not stripped by dead-code elimination if using aggressive compiler optimizations, as they may be instantiated dynamically via string reflection.

Can I implement a double-tap modifier key shortcut?

The current architecture in ShortcutManager.swift does not support double-tap detection natively. You would need to extend ModifierKeyMonitor.swift to implement a timing threshold mechanism that tracks timestamp values from CGEvent objects. Store the last modifier-down timestamp in an instance variable, and if the next event occurs within 300 milliseconds, trigger your custom action instead of the standard behavior.

Where are the keyboard shortcut preferences stored on disk?

Standard KeyboardShortcuts preferences are stored in ~/Library/Preferences/com.starmel.OpenSuperWhisper.plist under keys prefixed with KeyboardShortcuts_. Custom modifier and mouse button selections are stored in the same plist file but managed through AppPreferences.swift using @AppStorage property wrappers. You can inspect these values using the defaults read com.starmel.OpenSuperWhisper command in Terminal.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →