global-hotkeys
Global Hotkeys — System-Wide Keyboard Shortcuts
Critical Constraints
- ❌ DO NOT use only
addGlobalMonitorForEvents→ ✅ Also addaddLocalMonitorForEvents(global doesn't fire when YOUR app is focused) - ❌ DO NOT forget Accessibility permission → ✅ Global event monitoring requires
AXIsProcessTrusted() - ❌ DO NOT compare raw
modifierFlags→ ✅ Mask with.deviceIndependentFlagsMaskfirst - ❌ DO NOT use NSEvent for system-wide hotkeys in sandboxed apps → ✅ Use Carbon
RegisterEventHotKeyfor reliability
Decision Tree
In-app shortcut only?
└── SwiftUI .onKeyPress or .keyboardShortcut
System-wide, non-sandboxed?
├── Simple → NSEvent global + local monitors
└── Reliable → Carbon RegisterEventHotKey
System-wide, sandboxed (App Store)?
└── Carbon RegisterEventHotKey + Accessibility entitlement
Method 1: NSEvent Monitors (Simple)
import AppKit
class HotkeyManager {
private var globalMonitor: Any?
private var localMonitor: Any?
var onHotkey: (() -> Void)?
var modifiers: NSEvent.ModifierFlags = [.command, .shift]
var keyCode: UInt16 = 49 // Space
func start() {
globalMonitor = NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { [weak self] event in
self?.handleEvent(event)
}
localMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
self?.handleEvent(event)
return event
}
}
func stop() {
if let m = globalMonitor { NSEvent.removeMonitor(m) }
if let m = localMonitor { NSEvent.removeMonitor(m) }
}
private func handleEvent(_ event: NSEvent) {
let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
if flags == modifiers && event.keyCode == keyCode {
DispatchQueue.main.async { self.onHotkey?() }
}
}
}
Method 2: Carbon API (Reliable, System-Wide)
import Carbon
class CarbonHotkeyManager {
private var hotkeyRef: EventHotKeyRef?
private var eventHandler: EventHandlerRef?
var onHotkey: (() -> Void)?
func register(keyCode: UInt32, modifiers: UInt32) {
unregister()
var hotkeyID = EventHotKeyID()
hotkeyID.signature = OSType("HTKY".fourCharCodeValue)
hotkeyID.id = 1
var eventType = EventTypeSpec(
eventClass: OSType(kEventClassKeyboard),
eventKind: UInt32(kEventHotKeyPressed)
)
let handler: EventHandlerUPP = { _, event, userData in
let mgr = Unmanaged<CarbonHotkeyManager>.fromOpaque(userData!).takeUnretainedValue()
mgr.onHotkey?()
return noErr
}
InstallEventHandler(GetApplicationEventTarget(), handler, 1, &eventType,
Unmanaged.passUnretained(self).toOpaque(), &eventHandler)
RegisterEventHotKey(keyCode, modifiers, hotkeyID,
GetApplicationEventTarget(), 0, &hotkeyRef)
}
func unregister() {
if let ref = hotkeyRef { UnregisterEventHotKey(ref) }
if let h = eventHandler { RemoveEventHandler(h) }
}
}
extension String {
var fourCharCodeValue: FourCharCode {
utf8.reduce(0) { ($0 << 8) + FourCharCode($1) }
}
}
Carbon Modifier Constants
// Carbon modifier flags for RegisterEventHotKey
let cmdKey: UInt32 = UInt32(cmdKey) // 256
let shiftKey: UInt32 = UInt32(shiftKey) // 512
let optionKey: UInt32 = UInt32(optionKey) // 2048
let controlKey: UInt32 = UInt32(controlKey) // 4096
// Example: Cmd+Shift+Space
register(keyCode: 49, modifiers: UInt32(cmdKey) | UInt32(shiftKey))
Common Key Codes
| Key | Code | Key | Code | Key | Code |
|---|---|---|---|---|---|
| Space | 49 | Return | 36 | Escape | 53 |
| Tab | 48 | Delete | 51 | A | 0 |
| S | 1 | D | 2 | F | 3 |
| J | 38 | K | 40 | L | 37 |
SwiftUI In-App Shortcuts
// Keyboard shortcut on button
Button("Save") { save() }
.keyboardShortcut("s", modifiers: .command)
// Raw key press handler
.onKeyPress(.escape) { dismiss(); return .handled }
.onKeyPress(.upArrow) { moveUp(); return .handled }
.onKeyPress(.downArrow) { moveDown(); return .handled }
Accessibility Permission Check
func checkAccessibilityPermissions() -> Bool {
let options = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as String: true]
return AXIsProcessTrustedWithOptions(options as CFDictionary)
}
// Non-prompting check
func isAccessibilityGranted() -> Bool {
AXIsProcessTrusted()
}
Common Mistakes & Fixes
| Mistake | Fix |
|---|---|
| Hotkey works everywhere except own app | Add local monitor alongside global monitor |
| Modifier comparison fails | Mask with .deviceIndependentFlagsMask |
| Hotkey fires twice | Debounce with timestamp check (0.3s threshold) |
| Doesn't work on first launch | Check/request Accessibility permission |
References
More from makgunay/claude-swift-skills
appkit-bridge
Bridging AppKit components into SwiftUI macOS apps. Covers NSViewRepresentable and NSViewControllerRepresentable protocols for hosting AppKit views in SwiftUI, NSHostingView/NSHostingController for hosting SwiftUI in AppKit, NSPanel for floating windows, NSWindow configuration (styleMask, level, collectionBehavior), responder chain integration, NSEvent monitoring (global and local), NSAnimationContext for AppKit animations, NSPopover, NSStatusItem for menu bar, and NSGlassEffectView for AppKit Liquid Glass. Use when SwiftUI lacks a native equivalent, building floating panels, custom window chrome, or integrating legacy AppKit components.
15swiftui-core
Core SwiftUI patterns for macOS and iOS development including navigation (NavigationSplitView, NavigationStack), state management (@State, @Binding, @Environment, @Bindable with @Observable), the new customizable toolbar system (toolbar IDs, ToolbarSpacer, DefaultToolbarItem, searchToolbarBehavior, matchedTransitionSource, sharedBackgroundVisibility), styled text editing (TextEditor with AttributedString, AttributedTextSelection, transformAttributes, textFormattingDefinition), and layout patterns. Use when building any SwiftUI view, implementing navigation, managing state, creating toolbars, or building rich text editors. Corrects common LLM errors like using deprecated NavigationView, wrong state wrappers, and outdated toolbar APIs.
14swiftui-webkit
Native SwiftUI WebKit integration with the new WebView struct and WebPage observable class. Covers WebView creation from URLs, WebPage for navigation control and state management, JavaScript execution (callJavaScript with arguments and content worlds), custom URL scheme handlers, navigation management (load, reload, back/forward), navigation decisions, text search (findNavigator), content capture (snapshots, PDF generation, web archives), and configuration (data stores, user agents, JS permissions). Use when embedding web content in SwiftUI apps instead of the old WKWebView + UIViewRepresentable/NSViewRepresentable bridge pattern. This is a brand new API — do NOT use the old WKWebView wrapping approach.
10liquid-glass
Comprehensive guide to Apple's Liquid Glass design system introduced in macOS 26, iOS 26, and across all Apple platforms. Covers SwiftUI (.glassEffect(), GlassEffectContainer, .interactive(), .tint(), glassEffectID morphing, .buttonStyle(.glass), .buttonStyle(.glassProminent), glassEffectUnion), AppKit (NSGlassEffectView, NSGlassEffectContainerView), UIKit (UIGlassEffect, UIGlassContainerEffect, UIScrollEdgeEffect), and WidgetKit (accented rendering mode, widgetAccentable). Use whenever building UI with the new Apple design language, adopting glass effects, styling buttons or toolbars, or creating modern macOS/iOS interfaces. Always consult this skill when asked about new Apple design.
10tech-stack-validator
Validates and recommends technology stacks for native macOS/iOS app projects against PRD and architecture requirements. Reads the PRD and architecture documents (or gathers requirements interactively), then systematically checks every technology choice for: OS version availability, framework capability gaps, performance feasibility, distribution compatibility (sandbox vs direct), API deprecation risks, dependency conflicts, and timeline realism for the team size. Produces a structured validation report with go/no-go verdicts, risk flags, and alternative recommendations. Use when user says things like 'validate my tech stack', 'check if this architecture works', 'what should I build this with', 'is SwiftData the right choice', 'can I ship this on the App Store', 'review my architecture', or before starting implementation of any PRD. Also use when migrating between tech stacks or evaluating whether to adopt a new framework.
9swift-lang
Swift 6.2 language patterns and critical corrections for LLM code generation. Covers the concurrency paradigm shift (default MainActor isolation, @concurrent for background work, isolated conformances, nonisolated opt-out), @Observable vs ObservableObject, structured concurrency, modern error handling, property wrappers, InlineArray, Span, and value generics. Use whenever generating Swift code, especially async/concurrent code, class/struct design, or performance-critical paths. This skill prevents the most common LLM mistakes in Swift code generation.
8