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:
-
ShortcutManager.swift– Located atOpenSuperWhisper/ShortcutManager.swift, this singleton defines default shortcuts in theKeyboardShortcuts.Nameextension (lines 9-12) and manages runtime registration throughsetupKeyboardShortcuts()andsetupRecordingTrigger()(lines 54-82). -
AppPreferences.swift– Found atOpenSuperWhisper/Utils/AppPreferences.swift, thisUserDefaultswrapper stores user selections includingmodifierOnlyHotkeyandmouseButtonHotkey(lines 105-112). -
Monitor Classes –
ModifierKeyMonitor.swiftcreates low-level event taps usingCGEvent.tapCreatefor single-modifier detection, whileMouseButtonMonitor.swiftcapturesotherMouseDownandotherMouseUpevents for mouse-based triggers. -
Settings.swift– The SwiftUI view atOpenSuperWhisper/Settings.swift(lines 290-340) exposes the configuration UI throughSettingsView.
The Three Trigger Modes
OpenSuperWhisper abstracts three input methods through a unified interface:
-
Key Combination – Standard modifier-plus-key shortcuts (e.g., ⌘+
) handled viaKeyboardShortcuts.onKeyDownandonKeyUp` callbacks. -
Single Modifier Key – Standalone modifier detection (e.g., just the Fn key) monitored by
ModifierKeyMonitorwatching OS-level flag-change events. -
Mouse Button – Low-level button capture for peripherals, implemented in
MouseButtonMonitorusingNSEventmonitoring.
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
KeyboardShortcutsSwift package for cross-application global hotkey registration. - ShortcutManager.swift serves as the central coordinator, defining shortcuts in the
KeyboardShortcuts.Nameextension and wiring callbacks insetupKeyboardShortcuts(). - Three trigger modes are supported: traditional key combinations (via
KeyboardShortcuts), single modifiers (viaModifierKeyMonitor), and mouse buttons (viaMouseButtonMonitor). - Persistence is handled through
AppPreferences.swiftfor non-standard inputs, while theKeyboardShortcutslibrary automatically saves key combinations. - UI exposure requires adding a
.editor()view toSettingsView.swiftand binding it to the correspondingKeyboardShortcuts.Nameconstant.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →