Drag files in Finder, hold a modifier, and drop on a wheel of outputs. Conversions run entirely on-device through ImageIO, PDFKit, AVFoundation, the bundled CLI tools, and optional ffmpeg/LibreOffice. Includes a --self-test suite (41 checks) and a --convert CLI mode so the conversion matrix can be verified without the UI.
92 lines
2.9 KiB
Swift
92 lines
2.9 KiB
Swift
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<NSString>
|
|
) {
|
|
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)
|
|
}
|
|
}
|
|
}
|