import SwiftUI import AppKit import CoreGraphics import ServiceManagement @main struct KaptureApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate var body: some Scene { MenuBarExtra("Kapture", systemImage: "text.viewfinder") { MenuView() } .menuBarExtraStyle(.menu) } } final class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { if CommandLine.arguments.contains("--test-ocr") { runOCRSelfTest() return } // Single instance: stale copies keep their own hotkey + overlays alive // after app updates and eat clicks meant for the new instance. let myPid = ProcessInfo.processInfo.processIdentifier let others = NSRunningApplication.runningApplications(withBundleIdentifier: "dev.tzgyn.kapture") .filter { $0.processIdentifier != myPid } if !others.isEmpty { CaptureController.log("launch: other Kapture instances pids=\(others.map { String($0.processIdentifier) }.joined(separator: ",")) — terminating them") for other in others { other.forceTerminate() } } HotkeyManager.shared.onTrigger = { CaptureController.shared.begin() } HotkeyManager.shared.register() } } struct MenuView: View { @State private var granted = PermissionManager.isGranted var body: some View { Button("Capture & OCR") { CaptureController.shared.begin() } .keyboardShortcut("s", modifiers: [.command, .shift]) Button("Extract from Image File…") { ResultWindow.shared.showOpenPanel() } Divider() if granted { Text("Screen Recording: Granted") } else { Button("Grant Screen Recording Permission…") { PermissionManager.request() granted = PermissionManager.isGranted } } Divider() Button("Copy Diagnostics") { let diag = CaptureController.shared.diagnostics() let pasteboard = NSPasteboard.general pasteboard.clearContents() pasteboard.setString(diag, forType: .string) } Button(LoginItem.isEnabled ? "Disable Launch at Login" : "Launch at Login") { if !LoginItem.toggle() { let alert = NSAlert() alert.messageText = "Could not update Launch at Login" alert.informativeText = "Launch at Login requires a signed app bundle in /Applications. It is not available when running via `swift run`." alert.alertStyle = .warning alert.addButton(withTitle: "OK") alert.runModal() } } Divider() Button("Quit Kapture") { NSApp.terminate(nil) } .keyboardShortcut("q") } } enum PermissionManager { static var isGranted: Bool { CGPreflightScreenCaptureAccess() } static func request() { CGRequestScreenCaptureAccess() } static var isAccessibilityGranted: Bool { AXIsProcessTrusted() } static func requestAccessibility() { let opts = [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary _ = AXIsProcessTrustedWithOptions(opts) } } enum LoginItem { static var isEnabled: Bool { SMAppService.mainApp.status == .enabled } static func toggle() -> Bool { let service = SMAppService.mainApp do { if service.status == .enabled { try service.unregister() } else { try service.register() } return true } catch { return false } } } func renderTestImage(_ text: String, fontSize: CGFloat = 56) -> CGImage? { let size = NSSize(width: 1900, height: 220) guard let rep = NSBitmapImageRep( bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0 ) else { return nil } NSGraphicsContext.saveGraphicsState() NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep) NSColor.white.setFill() NSRect(origin: .zero, size: size).fill() let attrs: [NSAttributedString.Key: Any] = [ .font: NSFont.systemFont(ofSize: fontSize), .foregroundColor: NSColor.black, ] text.draw(at: NSPoint(x: 60, y: 75), withAttributes: attrs) NSGraphicsContext.restoreGraphicsState() return rep.cgImage } func runOCRSelfTest() { let cases: [(expected: String, contains: String)] = [ ("Kapture OCR test 1234, hello world", "Kapture OCR test 1234"), ("你好世界,这是一个测试", "你好世界"), ] var index = 0 func runNext() { guard index < cases.count else { exit(0) } let current = cases[index] index += 1 guard let image = renderTestImage(current.expected) else { exit(1) } OCRService.shared.recognize(image) { recognized in print("case: \(current.expected)") print("got: \(recognized)") let ok = recognized.contains(current.contains) print(ok ? "PASS" : "FAIL") if !ok { exit(1) } runNext() } } runNext() RunLoop.main.run() }