+ private func showPopoverWithMessage(_ message: String) {
+ if let button = statusItem.button {
+ (self.popover?.contentViewController as? HelpPopoverViewController)?.updateLabel(message)
+ self.popover?.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
+ self.popover?.performClose(nil)
+ }
+ }
+ }
+
+ private func updateImage() {
+ if let button = statusItem.button {
+ let image: String = switch captureState {
+ case .idle:
+ "rectangle.dashed.badge.record"
+ case .selectingArea:
+ "circle.rectangle.dashed"
+ case .recording:
+ "checkmark.rectangle"
+ case .uploading:
+ "dock.arrow.up.rectangle"
+ case .uploaded:
+ "checkmark.rectangle.fill"
+ case .error:
+ "xmark.rectangle.fill"
+ }
+ button.image = NSImage(systemSymbolName: image, accessibilityDescription: "Captura")
+ }
+ }
+
+ private func stop() {
+ stopTimer?.cancel()
+ captureSession?.stopRunning()
+ captureSession = nil
+ boxListener?.cancel()
+ recordingWindow?.close()
+ recordingWindow = nil
+ }
+
+ private func uploadOrCopy() async -> Bool {
+ if captureSessionConfiguration.shouldUseBackend {
+ let result = await uploadToBackend()
+ if result && !captureSessionConfiguration.shouldKeepLocalFiles {
+ deleteLocalFiles()
+ }
+ return result
+ } else {
+ copyLocalToClipboard()
+ return true
+ }
+ }
+
+ private func copyLocalToClipboard() {
+ 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)
+ pasteboard.setData(data, forType: fileType)
+ }
+ }
+ }
+
+ private func uploadToBackend() async -> Bool {
+ 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 = captureSessionConfiguration.backend {
+ var request = URLRequest(url: remoteUrl)
+ request.httpMethod = "POST"
+ request.httpBody = data
+ request.setValue(contentType, forHTTPHeaderField: "Content-Type")
+ request.setValue("Captura/1.0", forHTTPHeaderField: "User-Agent")
+
+ do {
+ let (data, response) = try await URLSession.shared.data(for: request)
+
+ if let httpResponse = response as? HTTPURLResponse {
+ if httpResponse.statusCode == 201 {
+ let answer = try JSONDecoder().decode(BackendResponse.self, from: data)
+ createRemoteFile(answer.url)
+ return true
+ }
+ }
+ } catch {}
+ }
+ }
+ }
+ return false
+ }
+
+ private func createRemoteFile(_ url: URL) {
+ let viewContext = PersistenceController.shared.container.viewContext
+ let remoteFile = CapturaRemoteFile(context: viewContext)
+ remoteFile.url = url.absoluteString
+ remoteFile.timestamp = Date()
+ try? viewContext.save()
+ let pasteboard = NSPasteboard.general
+ pasteboard.declareTypes([.URL], owner: nil)
+ pasteboard.setString(url.absoluteString, forType: .string)
+ }
+
+ private func deleteLocalFiles() {
+ if captureSessionConfiguration.shouldSaveGif {
+ if let url = outputFile?.gifURL {
+ try? FileManager.default.removeItem(at: url)
+ }
+ }
+ if captureSessionConfiguration.shouldSaveMp4 {
+ if let url = outputFile?.mp4URL {
+ try? FileManager.default.removeItem(at: url)
+ }