X-Git-Url: https://git.r.bdr.sh/rbdr/captura/blobdiff_plain/533cd932281300fb444c07e80f81fc683a410b60..refs/heads/rbdr-multimonitor:/Captura/CapturaApp.swift?ds=inline diff --git a/Captura/CapturaApp.swift b/Captura/CapturaApp.swift index e1c4e7e..00c5cc4 100644 --- a/Captura/CapturaApp.swift +++ b/Captura/CapturaApp.swift @@ -1,12 +1,11 @@ import SwiftUI -import SwiftData import Cocoa import Combine import AVFoundation @main struct CapturaApp: App { - + @NSApplicationDelegateAdaptor(CapturaAppDelegate.self) var appDelegate var body: some Scene { @@ -20,7 +19,7 @@ struct CapturaApp: App { } } -class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { +@objc(CapturaAppDelegate) class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { @Environment(\.openURL) var openURL var statusItem: NSStatusItem! @@ -34,10 +33,12 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { var images: [CGImage] = [] var outputFile: CapturaFile? = nil var gifCallbackTimer = ContinuousClock.now - var fps = CapturaSettings.frameRate var pixelDensity: CGFloat = 1.0 var stopTimer: DispatchWorkItem? var remoteFiles: [CapturaRemoteFile] = [] + var captureSessionConfiguration: CaptureSessionConfiguration = CaptureSessionConfiguration() + + @objc dynamic var scriptedPreferences: ScriptedPreferences = ScriptedPreferences() func applicationDidFinishLaunching(_ notification: Notification) { setupStatusBar() @@ -97,14 +98,48 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { window.close() } } + + // MARK: - URL Event Handler + + func application(_ application: NSApplication, open urls: [URL]) { + if (CapturaSettings.shouldAllowURLAutomation) { + for url in urls { + if let action = CapturaURLDecoder.decodeParams(url: url) { + switch action { + case let .configure(config): + NotificationCenter.default.post(name: .setConfiguration, object: nil, userInfo: [ + "config": config + ]) + case let .record(config): + NotificationCenter.default.post(name: .setCaptureSessionConfiguration, object: nil, userInfo: [ + "config": config + ]) + NotificationCenter.default.post(name: .startAreaSelection, object: nil, userInfo: nil) + } + } + } + } else { + let alert = NSAlert() + alert.messageText = "URL Automation Prevented" + alert.informativeText = "A website or application attempted to record your screen using URL Automation. If you want to allow this, enable it in Preferences." + alert.alertStyle = .warning + alert.addButton(withTitle: "OK") + alert.runModal() + } + } // MARK: - UI Event Handlers func menuWillOpen(_ menu: NSMenu) { if captureState != .idle { - menu.cancelTracking() + menu.cancelTrackingWithoutAnimation() + if captureState == .selectingArea { + NotificationCenter.default.post(name: .startRecording, object: nil, userInfo: nil) + return + } if captureState == .recording { NotificationCenter.default.post(name: .stopRecording, object: nil, userInfo: nil) + return } } } @@ -171,6 +206,22 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { if let frame = notification.userInfo?["frame"] { receivedFrame(frame as! CVImageBuffer) } + case .setConfiguration: + DispatchQueue.main.async { + if let userInfo = notification.userInfo { + if let config = userInfo["config"] as? ConfigureAction { + self.setConfiguration(config) + } + } + } + case .reloadConfiguration: + reloadConfiguration() + case .setCaptureSessionConfiguration: + if let userInfo = notification.userInfo { + if let config = userInfo["config"] as? RecordAction { + setCaptureSessionConfiguration(config) + } + } case .NSManagedObjectContextObjectsDidChange: DispatchQueue.main.async { self.fetchRemoteItems() @@ -190,7 +241,7 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { let rectInWindow = button.convert(button.bounds, to: nil) let rectInScreen = button.window?.convertToScreen(rectInWindow) NSApp.activate(ignoringOtherApps: true) - recordingWindow = RecordingWindow(rectInScreen) + recordingWindow = RecordingWindow(captureSessionConfiguration, rectInScreen) recordingWindow?.makeKeyAndOrderFront(nil) recordingWindow?.orderFrontRegardless() boxListener = recordingWindow?.recordingContentView.$box @@ -211,7 +262,6 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { func startRecording() { captureState = .recording updateImage() - fps = CapturaSettings.frameRate outputFile = nil images = []; pixelDensity = recordingWindow?.pixelDensity ?? 1.0 @@ -225,10 +275,10 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { stopTimer = DispatchWorkItem { self.stopRecording() } - DispatchQueue.main.asyncAfter(deadline: .now() + 300, execute: stopTimer!) + DispatchQueue.main.asyncAfter(deadline: .now() + Double(captureSessionConfiguration.maxLength), execute: stopTimer!) outputFile = CapturaFile() - if CapturaSettings.shouldSaveMp4 { + if captureSessionConfiguration.shouldSaveMp4 { captureSession.startRecording(to: outputFile!.mp4URL) } else { captureSession.startRunning() @@ -246,9 +296,9 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { stop() Task.detached { - if CapturaSettings.shouldSaveGif { + if self.captureSessionConfiguration.shouldSaveGif { if let outputFile = self.outputFile { - await GifRenderer.render(self.images, at: self.fps, to: outputFile.gifURL) + await GifRenderer.render(self.images, at: self.captureSessionConfiguration.frameRate, to: outputFile.gifURL) } } let wasSuccessful = await self.uploadOrCopy() @@ -271,16 +321,20 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { func reset() { captureState = .idle updateImage() + captureSessionConfiguration = CaptureSessionConfiguration() stop() } func receivedFrame(_ frame: CVImageBuffer) { let now = ContinuousClock.now - if now - gifCallbackTimer > .nanoseconds(1_000_000_000 / UInt64(fps)) { + if now - gifCallbackTimer > .nanoseconds(1_000_000_000 / UInt64(captureSessionConfiguration.frameRate)) { gifCallbackTimer = now DispatchQueue.main.async { - if let cgImage = frame.cgImage?.resize(by: self.pixelDensity) { + if var cgImage = frame.cgImage { + if self.pixelDensity > 1 { + cgImage = cgImage.resize(by: self.pixelDensity) ?? cgImage + } self.images.append(cgImage) } } @@ -299,6 +353,18 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } } + func setConfiguration(_ config: ConfigureAction) { + CapturaSettings.apply(config) + } + + func reloadConfiguration() { + self.captureSessionConfiguration = CaptureSessionConfiguration() + } + + func setCaptureSessionConfiguration(_ config: RecordAction) { + self.captureSessionConfiguration = CaptureSessionConfiguration(from: config) + } + // MARK: - CoreData private func fetchRemoteItems() { @@ -361,9 +427,9 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } private func uploadOrCopy() async -> Bool { - if CapturaSettings.shouldUseBackend { + if captureSessionConfiguration.shouldUseBackend { let result = await uploadToBackend() - if result && !CapturaSettings.shouldKeepLocalFiles { + if result && !captureSessionConfiguration.shouldKeepLocalFiles { deleteLocalFiles() } return result @@ -374,8 +440,8 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } private func copyLocalToClipboard() { - let fileType: NSPasteboard.PasteboardType = .init(rawValue: CapturaSettings.shouldSaveGif ? "com.compuserve.gif" : "public.mpeg-4") - if let url = CapturaSettings.shouldSaveGif ? outputFile?.gifURL : outputFile?.mp4URL { + let fileType: NSPasteboard.PasteboardType = .init(rawValue: captureSessionConfiguration.shouldSaveGif ? "com.compuserve.gif" : "public.mpeg-4") + if let url = captureSessionConfiguration.shouldSaveGif ? outputFile?.gifURL : outputFile?.mp4URL { if let data = try? Data(contentsOf: url) { let pasteboard = NSPasteboard.general pasteboard.declareTypes([fileType], owner: nil) @@ -385,10 +451,10 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } private func uploadToBackend() async -> Bool { - let contentType = CapturaSettings.shouldUploadGif ? "image/gif" : "video/mp4" - if let url = CapturaSettings.shouldUploadGif ? outputFile?.gifURL : outputFile?.mp4URL { + let contentType = captureSessionConfiguration.shouldUploadGif ? "image/gif" : "video/mp4" + if let url = captureSessionConfiguration.shouldUploadGif ? outputFile?.gifURL : outputFile?.mp4URL { if let data = try? Data(contentsOf: url) { - if let remoteUrl = CapturaSettings.backend { + if let remoteUrl = captureSessionConfiguration.backend { var request = URLRequest(url: remoteUrl) request.httpMethod = "POST" request.httpBody = data @@ -424,12 +490,12 @@ class CapturaAppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } private func deleteLocalFiles() { - if CapturaSettings.shouldSaveGif { + if captureSessionConfiguration.shouldSaveGif { if let url = outputFile?.gifURL { try? FileManager.default.removeItem(at: url) } } - if CapturaSettings.shouldSaveMp4 { + if captureSessionConfiguration.shouldSaveMp4 { if let url = outputFile?.mp4URL { try? FileManager.default.removeItem(at: url) }