feat: add destination-specific global shortcuts
This commit is contained in:
@@ -4,7 +4,10 @@ import ServiceManagement
|
||||
|
||||
@MainActor
|
||||
final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
private let shortcut: GlobalShortcut
|
||||
private let shortcuts: GlobalShortcuts
|
||||
private let destinationSelector = NSPopUpButton(frame: .zero, pullsDown: false)
|
||||
private let commandSpaceButton = NSButton(title: "Use ⌘Space", target: nil, action: nil)
|
||||
private var shortcut: GlobalShortcut { shortcuts.bindings[destinationSelector.indexOfSelectedItem] }
|
||||
private let recorder = ShortcutRecorderButton(title: "Record Shortcut…", target: nil, action: nil)
|
||||
private let clearButton = NSButton(title: "Clear", target: nil, action: nil)
|
||||
private let statusLabel = NSTextField(wrappingLabelWithString: "")
|
||||
@@ -17,10 +20,10 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
private var recoveryReport: UninstallReport?
|
||||
var retryUninstallRecovery: (() async -> UninstallReport)?
|
||||
|
||||
init(shortcut: GlobalShortcut) {
|
||||
self.shortcut = shortcut
|
||||
init(shortcuts: GlobalShortcuts) {
|
||||
self.shortcuts = shortcuts
|
||||
let window = ShortcutSettingsWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 620, height: 640),
|
||||
contentRect: NSRect(x: 0, y: 0, width: 620, height: 690),
|
||||
styleMask: [.titled, .closable, .miniaturizable, .fullSizeContentView],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
@@ -69,9 +72,14 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
}
|
||||
|
||||
private func configureContent(in window: NSWindow) {
|
||||
let heading = NSTextField(labelWithString: "Global Shortcut")
|
||||
let heading = NSTextField(labelWithString: "Global Shortcuts")
|
||||
heading.font = .systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)
|
||||
|
||||
destinationSelector.addItems(withTitles: GlobalShortcuts.destinations.map(\.title))
|
||||
destinationSelector.target = self
|
||||
destinationSelector.action = #selector(changeDestination)
|
||||
destinationSelector.setAccessibilityLabel("Global shortcut destination")
|
||||
|
||||
recorder.bezelStyle = .rounded
|
||||
recorder.target = self
|
||||
recorder.action = #selector(beginRecording)
|
||||
@@ -82,7 +90,8 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
self?.errorLabel.stringValue = ""
|
||||
self?.statusLabel.stringValue = "Press a key together with Command, Option, or Control. Escape cancels."
|
||||
}
|
||||
let commandSpaceButton = NSButton(title: "Use ⌘Space", target: self, action: #selector(useCommandSpace))
|
||||
commandSpaceButton.target = self
|
||||
commandSpaceButton.action = #selector(useCommandSpace)
|
||||
commandSpaceButton.bezelStyle = .rounded
|
||||
commandSpaceButton.setAccessibilityLabel("Use Command Space as the global shortcut")
|
||||
clearButton.bezelStyle = .rounded
|
||||
@@ -95,7 +104,7 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
controls.spacing = 8
|
||||
|
||||
let instructions = NSTextField(wrappingLabelWithString:
|
||||
"Click the shortcut button, then press a key with Command, Option, or Control. Press Escape to cancel. The global shortcut is paused while recording."
|
||||
"Choose a destination, click the shortcut button, then press a key with Command, Option, or Control. Press Escape to cancel. All global shortcuts are paused while recording."
|
||||
)
|
||||
instructions.textColor = .secondaryLabelColor
|
||||
statusLabel.textColor = .secondaryLabelColor
|
||||
@@ -139,7 +148,7 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
loginHeading.textColor = SunsetPalette.text
|
||||
|
||||
let stack = NSStackView(views: [
|
||||
heading, controls, instructions, statusLabel, errorLabel, spotlight, keyboardSettings,
|
||||
heading, destinationSelector, controls, instructions, statusLabel, errorLabel, spotlight, keyboardSettings,
|
||||
loginHeading, loginToggle, loginStatus, loginSettings, recoveryLabel, recoveryButton
|
||||
])
|
||||
stack.orientation = .vertical
|
||||
@@ -240,11 +249,13 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
|
||||
private func refresh() {
|
||||
refreshLogin()
|
||||
commandSpaceButton.isHidden = destinationSelector.indexOfSelectedItem != 0
|
||||
recorder.setAccessibilityLabel("Record \(GlobalShortcuts.destinations[destinationSelector.indexOfSelectedItem].title) global shortcut")
|
||||
let name = shortcut.shortcut?.displayName
|
||||
recorder.title = recorder.isRecording ? "Type shortcut…" : (name ?? "Record Shortcut…")
|
||||
recorder.setAccessibilityValue(name ?? "Not set")
|
||||
if recorder.isRecording {
|
||||
statusLabel.stringValue = "Global shortcut paused while recording."
|
||||
statusLabel.stringValue = "All global shortcuts are paused while recording."
|
||||
} else if shortcut.isRegistered {
|
||||
statusLabel.stringValue = "Global shortcut is active."
|
||||
} else if shortcut.shortcut != nil {
|
||||
@@ -257,7 +268,7 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
@objc private func beginRecording() {
|
||||
guard !recorder.isRecording else { return }
|
||||
errorLabel.stringValue = ""
|
||||
shortcut.suspend()
|
||||
shortcuts.suspend()
|
||||
recorder.isRecording = true
|
||||
refresh()
|
||||
if window?.makeFirstResponder(recorder) != true {
|
||||
@@ -274,7 +285,7 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
guard let candidate = KeyboardShortcut(keyCode: event.keyCode, modifiers: event.modifierFlags) else {
|
||||
recorder.isRecording = false
|
||||
errorLabel.stringValue = "Choose a supported key with Command, Option, or Control. A modifier by itself is not a shortcut."
|
||||
restoreSavedShortcut()
|
||||
restoreSavedShortcuts()
|
||||
refresh()
|
||||
return
|
||||
}
|
||||
@@ -286,11 +297,11 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
recorder.isRecording = false
|
||||
errorLabel.stringValue = ""
|
||||
do {
|
||||
try shortcut.setShortcut(candidate)
|
||||
try shortcuts.setShortcut(candidate, at: destinationSelector.indexOfSelectedItem)
|
||||
} catch {
|
||||
errorLabel.stringValue = error.localizedDescription
|
||||
if wasRecording { restoreSavedShortcut() }
|
||||
}
|
||||
if wasRecording { restoreSavedShortcuts() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
@@ -298,19 +309,23 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
guard recorder.isRecording else { return }
|
||||
recorder.isRecording = false
|
||||
errorLabel.stringValue = ""
|
||||
restoreSavedShortcut()
|
||||
restoreSavedShortcuts()
|
||||
refresh()
|
||||
}
|
||||
|
||||
private func restoreSavedShortcut() {
|
||||
do {
|
||||
try shortcut.setShortcut(shortcut.shortcut)
|
||||
} catch {
|
||||
let message = "Could not reactivate the saved shortcut: \(error.localizedDescription)"
|
||||
private func restoreSavedShortcuts() {
|
||||
let errors = shortcuts.start()
|
||||
if !errors.isEmpty {
|
||||
let message = errors.joined(separator: "\n")
|
||||
errorLabel.stringValue = errorLabel.stringValue.isEmpty ? message : "\(errorLabel.stringValue)\n\(message)"
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func changeDestination() {
|
||||
cancelRecording()
|
||||
refresh()
|
||||
}
|
||||
|
||||
@objc private func useCommandSpace() {
|
||||
apply(.commandSpace)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user