feat: configure a persistent global launcher shortcut

This commit is contained in:
2026-10-11 23:29:40 +02:00
parent 5842d86d9a
commit 7aed5c323c
7 changed files with 728 additions and 12 deletions
+32 -5
View File
@@ -14,6 +14,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
private var locationLabels: [URL: String] = [:]
private var discoveryTask: Task<Void, Never>?
private var isLaunching = false
private var previousApplication: NSRunningApplication?
init(history: LaunchHistory = LaunchHistory()) {
self.history = history
@@ -36,6 +37,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
configureContent(in: window)
window.delegate = self
window.navigateSelection = { [weak self] offset in self?.moveSelection(by: offset) }
window.dismiss = { [weak self] in self?.dismiss() }
}
required init?(coder: NSCoder) {
@@ -43,6 +45,10 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
func present() {
let frontmost = NSWorkspace.shared.frontmostApplication
if frontmost?.processIdentifier != ProcessInfo.processInfo.processIdentifier {
previousApplication = frontmost
}
searchField.stringValue = ""
updateResults()
window?.deminiaturize(nil)
@@ -54,9 +60,29 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
refreshApplications()
}
func toggle() {
guard !isLaunching, window?.attachedSheet == nil else { return }
if window?.isVisible == true, window?.isKeyWindow == true {
dismiss()
} else {
present()
}
}
func dismiss(restoringFocus: Bool = true) {
guard !restoringFocus || (!isLaunching && window?.attachedSheet == nil) else { return }
let previous = previousApplication
previousApplication = nil
window?.orderOut(nil)
if restoringFocus, let previous, !previous.isTerminated {
NSApp.yieldActivation(to: previous)
previous.activate(from: .current, options: [])
}
}
func windowDidResignKey(_ notification: Notification) {
guard !isLaunching, window?.attachedSheet == nil else { return }
window?.orderOut(nil)
dismiss(restoringFocus: false)
}
private func configureContent(in window: NSWindow) {
@@ -120,7 +146,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
tableView.target = self
tableView.doubleAction = #selector(openSelection)
tableView.openSelection = { [weak self] in self?.openSelection() }
tableView.dismiss = { [weak self] in self?.window?.orderOut(nil) }
tableView.dismiss = { [weak self] in self?.dismiss() }
tableView.editSearch = { [weak self] event in
guard let self, let window = self.window else { return }
window.makeFirstResponder(self.searchField)
@@ -295,7 +321,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
case #selector(NSResponder.insertNewline(_:)):
openSelection()
case #selector(NSResponder.cancelOperation(_:)):
window?.orderOut(nil)
dismiss()
default:
return false
}
@@ -397,7 +423,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
do {
try await AppLauncher.launch(application.url)
history.recordLaunch(of: application)
window?.orderOut(nil)
dismiss(restoringFocus: false)
} catch {
let alert = NSAlert(error: error)
alert.messageText = "Couldn’t open \(application.name)"
@@ -445,6 +471,7 @@ private final class LauncherPanel: NSPanel {
override var canBecomeKey: Bool { true }
override var canBecomeMain: Bool { true }
var navigateSelection: ((Int) -> Void)?
var dismiss: (() -> Void)?
override func sendEvent(_ event: NSEvent) {
if event.type == .keyDown, attachedSheet == nil,
@@ -464,7 +491,7 @@ private final class LauncherPanel: NSPanel {
}
override func performClose(_ sender: Any?) {
close()
dismiss?()
}
}