Files
TZGyn 2d031e58a4 Add Konverter: a local-only menu bar file converter
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.
2026-10-20 00:36:40 +08:00

114 lines
3.9 KiB
Swift

import AppKit
import Foundation
/// The menu bar item: the app's only permanent UI, and the place to turn the
/// wheel off or fall back to picking files by hand.
final class MenuBarController: NSObject, NSMenuDelegate {
private var statusItem: NSStatusItem?
func install() {
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = item.button {
let image = NSImage(systemSymbolName: "arrow.triangle.2.circlepath", accessibilityDescription: "Konverter")
image?.isTemplate = true
button.image = image
button.setAccessibilityLabel("Konverter")
}
let menu = NSMenu()
menu.delegate = self
item.menu = menu
statusItem = item
rebuild(menu)
}
func menuNeedsUpdate(_ menu: NSMenu) {
rebuild(menu)
}
private func rebuild(_ menu: NSMenu) {
menu.removeAllItems()
let enabled = Settings.shared.enabled
let toggle = NSMenuItem(
title: enabled ? "Konverter Active" : "Konverter Paused",
action: #selector(toggleEnabled),
keyEquivalent: ""
)
toggle.target = self
toggle.state = enabled ? .on : .off
menu.addItem(toggle)
menu.addItem(.separator())
let convert = NSMenuItem(title: "Konvert Files…", action: #selector(convertFiles), keyEquivalent: "o")
convert.target = self
menu.addItem(convert)
let jobs = NSMenuItem(title: "Show Jobs", action: #selector(showJobs), keyEquivalent: "j")
jobs.target = self
menu.addItem(jobs)
menu.addItem(.separator())
let settings = NSMenuItem(title: "Settings…", action: #selector(showSettings), keyEquivalent: ",")
settings.target = self
menu.addItem(settings)
let about = NSMenuItem(title: "About Konverter", action: #selector(showAbout), keyEquivalent: "")
about.target = self
menu.addItem(about)
menu.addItem(.separator())
let quit = NSMenuItem(title: "Quit Konverter", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
quit.target = NSApp
menu.addItem(quit)
}
@objc private func toggleEnabled() {
Settings.shared.enabled.toggle()
if !Settings.shared.enabled {
WheelController.shared.cancel()
}
if let menu = statusItem?.menu {
rebuild(menu)
}
}
@objc private func convertFiles() {
let panel = NSOpenPanel()
panel.canChooseFiles = true
panel.canChooseDirectories = true
panel.allowsMultipleSelection = true
panel.prompt = "Choose"
panel.message = "Pick files to convert, then choose an output format"
NSApp.activate(ignoringOtherApps: true)
guard panel.runModal() == .OK, !panel.urls.isEmpty else { return }
// Let the panel close before the menu opens over it.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
QuickMenu.show(urls: panel.urls, at: NSEvent.mouseLocation)
}
}
@objc private func showJobs() {
JobStore.shared.panelVisible = true
JobPanelController.shared.show()
}
@objc private func showSettings() {
PreferencesWindowController.shared.show()
}
@objc private func showAbout() {
NSApp.activate(ignoringOtherApps: true)
NSApp.orderFrontStandardAboutPanel(options: [
.applicationName: "Konverter",
.applicationVersion: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0",
.credits: NSAttributedString(
string: "Drag a file in Finder, then hold a key.\nShift converts · Command shows tools · Control shows file actions.\nEverything runs on this Mac.",
attributes: [.font: NSFont.systemFont(ofSize: 11)]
)
])
}
}