Add launch-at-login, multi-window results, and copy-image; auto-detect Developer ID
Users can now launch Kapture at login, keep multiple screenshot windows open
at once, and copy a screenshot to the clipboard. Builds sign with a Developer
ID identity when present, falling back to the ad-hoc self-signed key, so
Screen Recording permission survives rebuilds.
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4-flash-0731
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
import CoreGraphics
|
||||
import ServiceManagement
|
||||
|
||||
@main
|
||||
struct KaptureApp: App {
|
||||
@@ -47,6 +48,19 @@ struct MenuView: View {
|
||||
|
||||
Divider()
|
||||
|
||||
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")
|
||||
}
|
||||
@@ -60,6 +74,26 @@ enum PermissionManager {
|
||||
}
|
||||
}
|
||||
|
||||
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 runOCRSelfTest() {
|
||||
let text = "Kapture OCR test 1234, hello world"
|
||||
let size = NSSize(width: 1600, height: 220)
|
||||
|
||||
@@ -5,24 +5,44 @@ import UniformTypeIdentifiers
|
||||
final class ResultWindow {
|
||||
static let shared = ResultWindow()
|
||||
|
||||
private var window: NSWindow?
|
||||
private var windows: [NSWindow] = []
|
||||
private var nextOrigin = NSPoint(x: 60, y: 60)
|
||||
private var openCount = 0
|
||||
|
||||
@MainActor
|
||||
func show(image: CGImage) {
|
||||
window?.close()
|
||||
let model = CaptureModel(image: image)
|
||||
let hosting = NSHostingController(rootView: ResultView(model: model) { [weak self] in
|
||||
self?.window?.close()
|
||||
let w = NSWindow()
|
||||
w.contentViewController = NSHostingController(rootView: ResultView(model: model) {
|
||||
w.close()
|
||||
})
|
||||
let w = NSWindow(contentViewController: hosting)
|
||||
w.title = "Kapture — Screenshot"
|
||||
w.title = "Kapture — Screenshot" + (openCount > 0 ? " \(openCount + 1)" : "")
|
||||
w.level = .floating
|
||||
w.styleMask = [.titled, .closable, .resizable]
|
||||
w.setContentSize(NSSize(width: 694, height: 520))
|
||||
w.isReleasedWhenClosed = false
|
||||
w.center()
|
||||
w.setFrameOrigin(nextOrigin)
|
||||
w.makeKeyAndOrderFront(nil)
|
||||
window = w
|
||||
|
||||
windows.append(w)
|
||||
openCount += 1
|
||||
nextOrigin = NSPoint(x: nextOrigin.x + 28, y: nextOrigin.y - 28)
|
||||
|
||||
windowCleanup(for: w)
|
||||
}
|
||||
|
||||
private func windowCleanup(for window: NSWindow) {
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: NSWindow.willCloseNotification,
|
||||
object: window,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
guard let self else { return }
|
||||
self.windows.removeAll { $0 === window }
|
||||
self.openCount = max(0, self.openCount - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@@ -115,6 +135,13 @@ final class CaptureModel: ObservableObject {
|
||||
statusMessage = "Copied to clipboard."
|
||||
}
|
||||
|
||||
func copyImage() {
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.clearContents()
|
||||
pasteboard.writeObjects([NSImage(cgImage: image, size: .zero)])
|
||||
statusMessage = "Image copied to clipboard."
|
||||
}
|
||||
|
||||
func savePNG() {
|
||||
let panel = NSSavePanel()
|
||||
panel.allowedContentTypes = [.png]
|
||||
@@ -156,6 +183,7 @@ struct ResultView: View {
|
||||
.pickerStyle(.segmented)
|
||||
.fixedSize()
|
||||
Spacer()
|
||||
Button("Copy Image") { model.copyImage() }
|
||||
Button("Save PNG…") { model.savePNG() }
|
||||
Button("Get Text") { model.runOCR() }
|
||||
.disabled(model.isOCRRunning || model.extractedText != nil)
|
||||
|
||||
Reference in New Issue
Block a user