Leader Key's window is intentionally designed to NOT become a key window (canBecomeKey = false). This critical design decision ensures:
- Raycast Compatibility: When Leader Key appears, it doesn't steal focus from Raycast's overlay window, allowing both to coexist
- Overlay App Compatibility: Works with Alfred, Spotlight, and other overlay applications without closing them
- No Focus Stealing: The window appears visually but doesn't disrupt the user's current focus context
- Uses
orderFrontinstead ofmakeKeyAndOrderFront - Window becomes visible without becoming key/main
- Preserves the key window state of other applications
Since the window can't receive keyboard events directly (non-key window), Leader Key relies entirely on a global event tap:
- Monitors all keyboard events system-wide
- Processes events before they reach their target application
- Can consume events (return true) or pass them through (return false)
macOS can disable event taps that:
- Take too long to process events (>1 second)
- Experience high CPU load
- Have permission issues
-
Health Check (Every 1 Second)
- Simple, non-adaptive 1-second interval
- Lightweight: just checks
CGEvent.tapIsEnabled()boolean flag - CPU impact is negligible (microseconds per check)
- Fast recovery from system-disabled taps in high CPU scenarios
- NEW: Also monitors for accessibility permission changes when not monitoring
- Automatically starts event tap within 1 second of granting permissions
-
Automatic Recovery
- Re-enables disabled event taps
- Full restart if re-enable fails
- Hides stuck windows during recovery
-
State Recovery Timer (5 seconds)
- Detects inconsistent states (window visible but no active sequence)
- Respects user opacity settings (window can have 0 opacity)
- Automatically hides stuck windows
-
Force Reset (Cmd+Shift+Ctrl+K)
- Registered as global hotkey (works independently of event tap)
- Nuclear option that always works
- Clears all state, hides window, restarts event tap
// Checks multiple conditions to ensure reliable escape
if isWindowVisible || windowAlpha > 0 || hasActiveSequence {
hide()
resetSequenceState()
return true // Consume escape
}isWindowVisible: Normal casewindowAlpha > 0: Window might be transparent but technically visiblehasActiveSequence: State might be active even if window is hidden
- Normal mode opacity: 0.0 to 1.0 (default 0.9)
- Sticky mode opacity: 0.0 to 1.0 (default 0.7)
- Users can set opacity to 0 for invisible operation
- State recovery respects these settings (only checks
isVisible, not opacity)
- Holding modifier keys (Cmd/Ctrl/Option) triggers sticky mode
- Different opacity in sticky mode for visual feedback
- Configurable which modifier does what (group sequences vs sticky mode)
- Tracks modifier state changes
- Handles modifier-only shortcuts (e.g., Cmd+Shift+K)
- Prevents activation during typing (checks for non-modifier keys)
- Window created on first show, not at app launch
- Reduces memory footprint when not in use
- Early exit for non-relevant events
- Minimal processing in event tap callback
- Defers heavy work to main queue when needed
- Running apps still use the seq-style fast path based on
NSRunningApplication.activate()whenever possible - A special-case fallback exists for apps that are already active but have no usable window
- Messages is the canonical example: after
Cmd+W, the app can remain frontmost and keep the menu bar whileactivate()does nothing visible - Leader Key now checks AX window state only for already-active target apps, and reopens with
open -aonly when no regular window exists - This keeps normal background-app switching fast while fixing the frontmost-no-window edge case
- Default config
- App-specific configs
- Overlay-specific configs (Raycast, Alfred)
- Can detect when Raycast/Alfred windows are active
- Loads specific configs for overlay contexts
- Optional feature (disabled by default)
- Event tap may be disabled by system
- Adaptive health check ensures recovery within 1 second
- Force reset always available as fallback
- Some macOS apps can be frontmost without any visible document/chat window
- The app menu staying in the menu bar is not enough evidence that a user-visible window exists
activate()is correct for the normal running-app case, but incorrect for this state- The right fix is not to abandon the fast path globally; it is to detect this state narrowly and reopen only then
- Window positioning accounts for screen with mouse
- Respects user's offset preferences
- Reactivation behavior configurable (reset, hide, nothing)
- Prevents confusing states from rapid key presses
- Can't rely on window being key for tests
- Must test through event tap simulation
- State recovery makes testing timing-sensitive
- Requires accessibility permissions
- May behave differently under test runners
- Force reset provides test recovery mechanism
- Required for global event tap
- Prompts user on first launch
- Graceful degradation if permissions revoked
- Dual Permission Detection: Uses both
CGPreflightListenEventAccess()andAXIsProcessTrustedWithOptions()for reliability - Aggressive Polling After Prompt: When user opens System Settings, polls every 1 second for 30 seconds
- Automatic Detection: Event tap health check monitors for permission changes every second
- No Manual Intervention: Detects when permissions are granted and auto-starts without requiring app restart
- Permission State Tracking: Tracks
lastPermissionCheckto detect changes from false to true
- Doesn't store passwords or sensitive keystrokes
- Only tracks modifier states and activation sequences
When developing Leader Key, macOS treats each rebuild as a new application unless properly code-signed:
-
Automatic Permission Reset (Debug Builds)
- Build script automatically runs
tccutil reset Accessibilitybefore each Debug build - Ensures clean permission state and avoids confusion
- Only runs in Debug configuration to preserve Release build permissions
- Requires re-granting permissions after each rebuild
- Build script automatically runs
-
Why This Happens
- Without paid Apple Developer account, app uses ad-hoc signing (
CODE_SIGN_IDENTITY = "-") - Each build has different signature, so macOS treats it as a new app
- This is intentional security behavior by Apple
- Without paid Apple Developer account, app uses ad-hoc signing (
-
Solutions for Smoother Development
- Free: Use the automated reset script (implemented)
- Paid: Sign with Developer ID certificate ($99/year)
- Alternative: Create self-signed certificate (partial improvement)
- Tests requiring event tap need accessibility permissions
- Permission resets affect test consistency
- Use Force Reset (Cmd+Shift+Ctrl+K) for test recovery
- Per-app event tap health check intervals
- Visual indicator when event tap is recovering
- Telemetry for event tap failure patterns
- Alternative input methods (mouse gestures, touchpad)
- Must remain non-key window for overlay compatibility
- Event tap is the only reliable input method
- Force reset must always work independently