Skip to content
C.W.K.
Lesson 03 of 05 · published

The Chord — Shared Hotkeys, Visible Collisions

~14 min · global-hotkeys, carbon, cwkkitmac, collisions, observation

Level 0Idle Hands
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Successful bindings survive other bindings' conflicts. No silent failure." — CwkKitMac's global hotkey registrar

Borrowed, Not Rebuilt

Tinker does not contain its own hotkey code. Flint, the family's text-transformation app, had already paid for system-wide chords — the Carbon RegisterEventHotKey door, the Cocoa-to-Carbon modifier translation, a recorder, a collision report — and that work was lifted into the shared Swift kit, CwkKitMac, as HotkeyChord and GlobalHotkeys. Tinker uses the same types and passes its own four-character signature, 'TINK', so its registrations can never be confused with another app's. When the kit's chord type gets a fix, every app that shares it gets the same fix.

A Chord Always Has a Modifier

HotkeyChord is a key plus a Carbon modifier mask, and its initialiser returns nil when the mask holds none of ⌘ ⇧ ⌥ ⌃. A "chord" with no modifier is just a keystroke, and registering one would steal that key from every app on the Mac. The type makes the mistake unrepresentable instead of relying on each caller to remember. It also decodes strictly: a stored key code the current build cannot name throws rather than decoding to something nameless.

Registration Reports Per Command

GlobalHotkeys.start takes every command that has a chord and tries each in turn. Two commands with the same chord: the second fails with "Duplicates first command's name". A chord macOS or another app already owns: "macOS or another app owns ⇧⌘E (…)." Every failure lands in a dictionary keyed by command ID, and every success stays registered regardless. Tinker shows each failure beside that command's recorder, and the menu adds a single "Hotkey conflicts — open Settings" line. A shortcut that silently does nothing is the one outcome the design will not accept. Internal hotkey IDs are never reused, so an event already queued for a removed binding cannot fire a newly registered one.

When Hotkeys Are Off

Tinker ships with no chord assigned to any command; Dad records his own. Registration is torn down and rebuilt whenever commands change, and it is skipped entirely in three states: while the recorder is listening (so pressing the chord you are trying to record does not run the command), while the engine is stopped, and while the engine could not be created. Emergency release stops hotkeys first.

Keeping Carbon Out of Observation

Tinker's app model is a Swift @Observable class, so SwiftUI re-renders when its properties change. The hotkey registrar holds Carbon references and is not a value the UI should track. It is declared @ObservationIgnored and created lazily; one early commit's whole purpose was to keep Carbon registration outside observation tracking. Only the resulting failure dictionary is observed, because that is what the screen shows.

Code

Registering every command that has a chord (from the app model)·swift
@ObservationIgnored private lazy var hotkeys = GlobalHotkeys(signature: 0x54494E4B) { // 'TINK'
  [weak self] id in Task { await self?.performCommand(id, trigger: .hotkey) }
}

func registerHotkeys() {
  hotkeys.stop()
  guard !recording, !stopped, engine != nil else { return }
  let result = hotkeys.start(configuration.commands.compactMap { command in
    command.hotkey.map { GlobalHotkeyBinding(id: command.id, name: command.name, chord: $0) }
  })
  hotkeyErrors = result.failures      // shown beside each recorder; the menu adds one conflicts line
}
The kit's chord type refuses a chord with no modifier·swift
public struct HotkeyChord: Hashable, Sendable, Codable {
  public let key: HotkeyKey
  public let carbonModifiers: UInt32
  public static let modifierBits = UInt32(cmdKey | shiftKey | optionKey | controlKey)

  public init?(key: HotkeyKey, carbonModifiers: UInt32) {
    let relevant = carbonModifiers & Self.modifierBits
    guard relevant != 0 else { return nil }   // a bare key would steal typing
    self.key = key
    self.carbonModifiers = relevant
  }

  /// Written the way macOS writes it: control, option, shift, command, then the key.
  public var displayLabel: String {
    var glyphs = ""
    if carbonModifiers & UInt32(controlKey) != 0 { glyphs += "⌃" }
    if carbonModifiers & UInt32(optionKey) != 0 { glyphs += "⌥" }
    if carbonModifiers & UInt32(shiftKey) != 0 { glyphs += "⇧" }
    if carbonModifiers & UInt32(cmdKey) != 0 { glyphs += "⌘" }
    return glyphs + key.name
  }
}

External links

Exercise

Dad assigns ⌘⇧E to edit.toggle and, by mistake, the same chord to draw.toggle; ⌥⌘B to broadcast.toggle is free. Write the failure dictionary GlobalHotkeys returns, say which commands now respond to a key press, and describe what Settings shows. Then explain why registration should not simply refuse the whole set.
Hint
Commands are tried in order, so edit.toggle registers and draw.toggle fails with 'Duplicates Toggle Final Cut editing.'; broadcast.toggle registers. Two of three chords work. Refusing the whole set would punish two correct bindings for one mistake and leave Dad with no shortcuts at all — exactly the silent-failure feeling the per-command report exists to prevent.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.