feature: user-selectable capture hotkey with recorder in Settings

This commit is contained in:
2026-09-01 22:33:13 +04:00
parent 3c55be174a
commit fa3e7b0a4a
4 changed files with 258 additions and 15 deletions
+33 -7
View File
@@ -1,5 +1,4 @@
import AppKit
import Carbon.HIToolbox
import Foundation
import Observation
import SwiftUI
@@ -37,6 +36,9 @@ public final class AppModel {
public private(set) var outboxURL: URL
/// Live watch folder; WP-4c updates this alongside `ReturnWatcher.updateWatchFolder`.
public private(set) var watchFolderURL: URL
/// Currently bound capture combo (the last one Carbon accepted, or the preferred load).
private(set) var captureHotkey: HotkeyPreference
var hotkeyDisplayString: String { captureHotkey.displayString }
let paths: AppSupportPaths
let spool: SpoolStore
@@ -80,6 +82,7 @@ public final class AppModel {
self.watchFolderURL = folders.watch
self.outboxDisplayName = folders.outbox.lastPathComponent
self.watchFolderDisplayName = folders.watch.lastPathComponent
self.captureHotkey = HotkeyPreference.load()
}
// MARK: Seam mutators the only way a WP-4b/4c extension changes state.
@@ -146,16 +149,39 @@ public final class AppModel {
setStatus((error as? ShotdeckError)?.errorDescription ?? "Could not watch the return folder.")
}
let registered = hotkeys.register(
let pref = HotkeyPreference.load()
captureHotkey = pref
if !bindCaptureHotkey(pref) {
setStatus("\(pref.displayString) is already used by another app — capture only works from the menu.")
}
}
/// Unregisters `capture` and binds `HotkeyPreference.load()`. If Carbon rejects the new
/// combo, restores the previous preference (UserDefaults + Carbon) so the old one keeps working.
func reRegisterHotkey() {
let previous = captureHotkey
let next = HotkeyPreference.load()
hotkeys.unregister(id: "capture")
if bindCaptureHotkey(next) {
captureHotkey = next
return
}
setStatus("That combination is taken — pick another.")
previous.save()
if bindCaptureHotkey(previous) {
captureHotkey = previous
}
}
@discardableResult
private func bindCaptureHotkey(_ pref: HotkeyPreference) -> Bool {
hotkeys.register(
id: "capture",
keyCode: UInt32(kVK_ANSI_2),
modifiers: UInt32(optionKey | shiftKey)
keyCode: pref.keyCode,
modifiers: pref.modifiers
) { [weak self] in
Task { await self?.captureNow() }
}
if !registered {
setStatus("⌥⇧2 is already used by another app — capture only works from the menu.")
}
}
public func captureNow() async {