import AppKit import SwiftUI @main struct KonverterApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate var body: some Scene { // A menu bar app: no windows of its own. Settings are a custom window // so the app can be opened from the status item on any space. SwiftUI.Settings { EmptyView() } } } final class AppDelegate: NSObject, NSApplicationDelegate { private let menuBar = MenuBarController() func applicationDidFinishLaunching(_ notification: Notification) { if CommandLine.arguments.contains("--self-test") { exit(SelfTest.run()) } if CommandLine.arguments.contains("--convert") { exit(CLI.run()) } terminateOtherInstances() menuBar.install() DragMonitor.shared.onBegan = { urls in WheelController.shared.begin(urls: urls) } DragMonitor.shared.onTick = { _, location, modifiers in guard Settings.shared.enabled else { return } WheelController.shared.tick(location: location, modifiers: modifiers) } DragMonitor.shared.onEnded = { _ in WheelController.shared.end() } DragMonitor.shared.onKey = { keyCode, modifiers in WheelController.shared.handleKey(keyCode, modifiers: modifiers) } DragMonitor.shared.start() NSApp.servicesProvider = self NSUpdateDynamicServices() // Warm the Quick Look machinery so the first Float is instant. DispatchQueue.main.async { _ = NSWorkspace.shared } } func applicationWillTerminate(_ notification: Notification) { DragMonitor.shared.stop() WheelController.shared.cancel() } func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { false } private func terminateOtherInstances() { let identifier = Bundle.main.bundleIdentifier ?? "dev.tzgyn.konverter" let mine = ProcessInfo.processInfo.processIdentifier for other in NSRunningApplication.runningApplications(withBundleIdentifier: identifier) where other.processIdentifier != mine { other.forceTerminate() } } /// Finder Quick Action / Services entry. The wheel needs a pointer to sit /// under, so this opens the same options as a native menu. @objc func convertFiles( _ pasteboard: NSPasteboard, userData: String, error: AutoreleasingUnsafeMutablePointer ) { let urls = (pasteboard.readObjects( forClasses: [NSURL.self], options: [.urlReadingFileURLsOnly: true] ) as? [URL]) ?? [] guard !urls.isEmpty else { error.pointee = "No files were passed to Konverter." as NSString return } DispatchQueue.main.async { QuickMenu.show(urls: urls, at: NSEvent.mouseLocation) } } }