refactor: centralize shortcut recording lifetime

This commit is contained in:
Valdemar Lorenzen
2026-10-16 10:23:20 +02:00
parent 04e505b846
commit 6725be8ad5
7 changed files with 199 additions and 30 deletions
+25 -5
View File
@@ -10,6 +10,18 @@ public final class GlobalShortcut {
private let defaults: UserDefaults
private let action: @MainActor () -> Void
private let native = NativeHotKey()
private var registration: Registration?
struct Registration {
var activate: (KeyboardShortcut) throws -> Void
var unregister: () throws -> Void
var stop: () -> Void
}
convenience init(defaults: UserDefaults, storageKey: String, registration: Registration) {
self.init(defaults: defaults, storageKey: storageKey, action: {})
self.registration = registration
}
public init(
defaults: UserDefaults = .standard,
@@ -44,7 +56,7 @@ public final class GlobalShortcut {
try activate(candidate)
defaults.set(candidate.storedValue, forKey: storageKey)
} else {
try native.unregister()
try unregister()
isRegistered = false
defaults.removeObject(forKey: storageKey)
}
@@ -53,7 +65,7 @@ public final class GlobalShortcut {
public func suspend() {
do {
try native.unregister()
try unregister()
isRegistered = false
} catch {
NSLog("Conjure could not suspend its shortcut: %@", error.localizedDescription)
@@ -61,7 +73,7 @@ public final class GlobalShortcut {
}
public func stop() {
native.dispose()
if let registration { registration.stop() } else { native.dispose() }
isRegistered = false
}
@@ -69,9 +81,17 @@ public final class GlobalShortcut {
action()
}
private func unregister() throws {
if let registration { try registration.unregister() } else { try native.unregister() }
}
private func activate(_ candidate: KeyboardShortcut) throws {
try Self.rejectSystemConflict(candidate)
try native.register(candidate)
if let registration {
try registration.activate(candidate)
} else {
try Self.rejectSystemConflict(candidate)
try native.register(candidate)
}
isRegistered = true
}
+20 -1
View File
@@ -27,6 +27,13 @@ public final class GlobalShortcuts {
public let bindings: [GlobalShortcut]
private let defaults: UserDefaults
private var isSuspended = false
public var isRecording: Bool { isSuspended }
init(defaults: UserDefaults, bindings: [GlobalShortcut]) {
precondition(bindings.count == Self.destinations.count)
self.defaults = defaults
self.bindings = bindings
}
public init(
defaults: UserDefaults = .standard,
@@ -60,11 +67,23 @@ public final class GlobalShortcuts {
return errors
}
public func suspend() {
public func beginRecording() {
isSuspended = true
bindings.forEach { $0.suspend() }
}
public func cancelRecording() -> [String] {
guard isRecording else { return [] }
return start()
}
public func finishRecording(_ candidate: KeyboardShortcut?, at index: Int) -> [String] {
var errors: [String] = []
do { try setShortcut(candidate, at: index) }
catch { errors.append(error.localizedDescription) }
return errors + cancelRecording()
}
public func stop() {
isSuspended = false
bindings.forEach { $0.stop() }