172 lines
7.2 KiB
Swift
172 lines
7.2 KiB
Swift
import AppKit
|
|
import ConjureKit
|
|
|
|
@main
|
|
@MainActor
|
|
struct ConjureApp {
|
|
static func main() {
|
|
let application = NSApplication.shared
|
|
ThemePalette.startObserving()
|
|
let delegate = ApplicationDelegate()
|
|
application.delegate = delegate
|
|
application.setActivationPolicy(.accessory)
|
|
application.mainMenu = makeMainMenu(delegate: delegate)
|
|
|
|
// NSApplication holds its delegate weakly.
|
|
withExtendedLifetime(delegate) {
|
|
application.run()
|
|
}
|
|
}
|
|
|
|
private static func makeMainMenu(delegate: ApplicationDelegate) -> NSMenu {
|
|
let menu = NSMenu()
|
|
let applicationItem = NSMenuItem(title: "Conjure", action: nil, keyEquivalent: "")
|
|
let applicationMenu = NSMenu(title: "Conjure")
|
|
applicationItem.submenu = applicationMenu
|
|
menu.addItem(applicationItem)
|
|
|
|
applicationMenu.addItem(
|
|
withTitle: "About Conjure",
|
|
action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)),
|
|
keyEquivalent: ""
|
|
)
|
|
let settings = applicationMenu.addItem(
|
|
withTitle: "Settings…",
|
|
action: #selector(ApplicationDelegate.showSettings(_:)),
|
|
keyEquivalent: ","
|
|
)
|
|
settings.target = delegate
|
|
applicationMenu.addItem(.separator())
|
|
applicationMenu.addItem(
|
|
withTitle: "Hide Conjure",
|
|
action: #selector(NSApplication.hide(_:)),
|
|
keyEquivalent: "h"
|
|
)
|
|
let hideOthers = applicationMenu.addItem(
|
|
withTitle: "Hide Others",
|
|
action: #selector(NSApplication.hideOtherApplications(_:)),
|
|
keyEquivalent: "h"
|
|
)
|
|
hideOthers.keyEquivalentModifierMask = [.command, .option]
|
|
applicationMenu.addItem(
|
|
withTitle: "Show All",
|
|
action: #selector(NSApplication.unhideAllApplications(_:)),
|
|
keyEquivalent: ""
|
|
)
|
|
applicationMenu.addItem(.separator())
|
|
applicationMenu.addItem(
|
|
withTitle: "Quit Conjure",
|
|
action: #selector(NSApplication.terminate(_:)),
|
|
keyEquivalent: "q"
|
|
)
|
|
|
|
let fileItem = NSMenuItem(title: "File", action: nil, keyEquivalent: "")
|
|
let fileMenu = NSMenu(title: "File")
|
|
fileItem.submenu = fileMenu
|
|
menu.addItem(fileItem)
|
|
fileMenu.addItem(withTitle: "Close Window", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w")
|
|
|
|
let editItem = NSMenuItem(title: "Edit", action: nil, keyEquivalent: "")
|
|
let editMenu = NSMenu(title: "Edit")
|
|
editItem.submenu = editMenu
|
|
menu.addItem(editItem)
|
|
editMenu.addItem(withTitle: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x")
|
|
editMenu.addItem(withTitle: "Undo", action: Selector(("undo:")), keyEquivalent: "z")
|
|
let redo = editMenu.addItem(withTitle: "Redo", action: Selector(("redo:")), keyEquivalent: "z")
|
|
redo.keyEquivalentModifierMask = [.command, .shift]
|
|
editMenu.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c")
|
|
editMenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v")
|
|
editMenu.addItem(.separator())
|
|
editMenu.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a")
|
|
|
|
let windowItem = NSMenuItem(title: "Window", action: nil, keyEquivalent: "")
|
|
let windowMenu = NSMenu(title: "Window")
|
|
windowItem.submenu = windowMenu
|
|
menu.addItem(windowItem)
|
|
windowMenu.addItem(withTitle: "Minimize", action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent: "m")
|
|
windowMenu.addItem(withTitle: "Zoom", action: #selector(NSWindow.performZoom(_:)), keyEquivalent: "")
|
|
windowMenu.addItem(.separator())
|
|
windowMenu.addItem(withTitle: "Bring All to Front", action: #selector(NSApplication.arrangeInFront(_:)), keyEquivalent: "")
|
|
NSApplication.shared.windowsMenu = windowMenu
|
|
return menu
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class ApplicationDelegate: NSObject, NSApplicationDelegate {
|
|
private let uninstaller = ApplicationUninstaller()
|
|
private lazy var launcher = LauncherWindowController(uninstaller: uninstaller)
|
|
private lazy var shortcuts = GlobalShortcuts { [weak self] destination in
|
|
switch destination {
|
|
case .launcher: self?.launcher.toggle()
|
|
case .command(let command): self?.launcher.toggle(command: command)
|
|
}
|
|
}
|
|
private lazy var settings = makeSettings()
|
|
|
|
private func makeSettings() -> SettingsWindowController {
|
|
let controller = SettingsWindowController(shortcuts: shortcuts)
|
|
controller.retryUninstallRecovery = { [uninstaller] in await uninstaller.recover() }
|
|
return controller
|
|
}
|
|
private var startupError = ThemePalette.configurationError
|
|
private var startupRecoveryMessage: String?
|
|
|
|
private var isLoginLaunch: Bool {
|
|
let event = NSAppleEventManager.shared().currentAppleEvent
|
|
return event?.eventID == kAEOpenApplication
|
|
&& event?.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue == keyAELaunchedAsLogInItem
|
|
}
|
|
|
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
let menu = NSMenu()
|
|
let settingsItem = menu.addItem(withTitle: "Settings…", action: #selector(showSettings(_:)), keyEquivalent: ",")
|
|
settingsItem.target = self
|
|
menu.addItem(.separator())
|
|
let quitItem = menu.addItem(withTitle: "Quit Conjure", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
|
|
quitItem.target = NSApplication.shared
|
|
launcher.window?.contentView?.menu = menu
|
|
let loginLaunch = isLoginLaunch
|
|
Task {
|
|
let report = await uninstaller.recover()
|
|
guard !report.messages.isEmpty else { return }
|
|
startupRecoveryMessage = report.messages.joined(separator: "\n")
|
|
settings.setUninstallRecoveryReport(report)
|
|
if !loginLaunch { showSettings(nil) }
|
|
}
|
|
if !isLoginLaunch {
|
|
launcher.present()
|
|
}
|
|
let shortcutErrors = shortcuts.start()
|
|
startupError = ([startupError].compactMap { $0 } + shortcutErrors).joined(separator: "\n")
|
|
if startupError?.isEmpty == true { startupError = nil }
|
|
if startupError != nil && !loginLaunch { showSettings(nil) }
|
|
}
|
|
|
|
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows: Bool) -> Bool {
|
|
guard !isLoginLaunch else { return false }
|
|
launcher.present()
|
|
if startupError != nil || startupRecoveryMessage != nil {
|
|
showSettings(nil)
|
|
}
|
|
return false
|
|
}
|
|
|
|
@objc func showSettings(_ sender: Any?) {
|
|
settings.present(error: startupError)
|
|
startupError = nil
|
|
startupRecoveryMessage = nil
|
|
}
|
|
|
|
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
|
launcher.saveNotes() ? .terminateNow : .terminateCancel
|
|
}
|
|
func applicationWillTerminate(_ notification: Notification) {
|
|
shortcuts.stop()
|
|
}
|
|
|
|
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
|
false
|
|
}
|
|
}
|