// Destinations.swift // GreenshotMac // // Port of Greenshot's IDestination implementations for macOS. import AppKit import UniformTypeIdentifiers @MainActor enum Destinations { /// Sends a capture to a destination. `anchor` is used to position pickers and share sheets. @discardableResult static func export(_ capture: Capture, to destination: DestinationID, anchor: NSView? = nil) -> Bool { switch destination { case .picker: DestinationPickerWindowController.present(for: capture) return true case .editor: EditorWindowController.open(with: capture) return true case .clipboard: copyToClipboard(capture) Notifier.show(title: "Copied to clipboard", body: nil) return true case .file: return saveToFile(capture) case .fileWithDialog: return saveWithDialog(capture) case .printer: printCapture(capture) return true case .share: share(capture, anchor: anchor) return true case .email: sendAsEmail(capture, anchor: anchor) return true case .openWith: openWith(capture) return true } } // MARK: - Clipboard static func copyToClipboard(_ capture: Capture) { let pasteboard = NSPasteboard.general pasteboard.clearContents() let image = capture.nsImage pasteboard.writeObjects([image]) if let data = ImageExporter.data(from: capture.image, format: .png, jpegQuality: 100) { pasteboard.setData(data, forType: .png) } } // MARK: - Files @discardableResult static func saveToFile(_ capture: Capture) -> Bool { let url = ImageExporter.suggestedURL(for: capture.details) do { try ImageExporter.write(capture.image, to: url, format: Configuration.shared.outputFileFormat) capture.details.filePath = url.path if Configuration.shared.copyPathToClipboard { NSPasteboard.general.clearContents() NSPasteboard.general.setString(url.path, forType: .string) } Notifier.show(title: "Screenshot saved", body: url.lastPathComponent, revealing: url) return true } catch { presentError(error) return false } } @discardableResult static func saveWithDialog(_ capture: Capture) -> Bool { let panel = NSSavePanel() let configuration = Configuration.shared panel.nameFieldStringValue = FilenamePattern.filename(pattern: configuration.outputFileFilenamePattern, details: capture.details, format: configuration.outputFileFormat) panel.directoryURL = URL(fileURLWithPath: configuration.outputFilePath, isDirectory: true) panel.allowedContentTypes = [.png, .jpeg, .tiff, .bmp, .gif] panel.canCreateDirectories = true panel.isExtensionHidden = false NSApp.activate(ignoringOtherApps: true) guard panel.runModal() == .OK, let url = panel.url else { return false } let format = ImageExporter.format(forPathExtension: url.pathExtension) do { try ImageExporter.write(capture.image, to: url, format: format) capture.details.filePath = url.path return true } catch { presentError(error) return false } } // MARK: - Print static func printCapture(_ capture: Capture) { let image = capture.nsImage let info = NSPrintInfo.shared info.horizontalPagination = .fit info.verticalPagination = .fit info.isHorizontallyCentered = true info.isVerticallyCentered = true info.orientation = image.size.width > image.size.height ? .landscape : .portrait let imageView = NSImageView(frame: CGRect(origin: .zero, size: image.size)) imageView.image = image imageView.imageScaling = .scaleProportionallyUpOrDown let operation = NSPrintOperation(view: imageView, printInfo: info) operation.showsPrintPanel = true operation.showsProgressPanel = true NSApp.activate(ignoringOtherApps: true) operation.run() } // MARK: - Share static func share(_ capture: Capture, anchor: NSView?) { guard let url = writeTemporaryFile(capture) else { return } let picker = NSSharingServicePicker(items: [url]) if let anchor { picker.show(relativeTo: anchor.bounds, of: anchor, preferredEdge: .minY) } else if let window = anchorWindow() { let view = window.contentView ?? NSView() picker.show(relativeTo: view.bounds, of: view, preferredEdge: .minY) } } // MARK: - Open with static func openWith(_ capture: Capture) { guard let url = writeTemporaryFile(capture) else { return } let panel = NSOpenPanel() panel.message = "Choose an application to open the screenshot with" panel.directoryURL = URL(fileURLWithPath: "/Applications") panel.allowedContentTypes = [.application] panel.allowsMultipleSelection = false panel.canChooseDirectories = false NSApp.activate(ignoringOtherApps: true) guard panel.runModal() == .OK, let application = panel.url else { return } NSWorkspace.shared.open([url], withApplicationAt: application, configuration: NSWorkspace.OpenConfiguration()) { _, error in if let error { DispatchQueue.main.async { presentError(error) } } } } // MARK: - Email static func sendAsEmail(_ capture: Capture, anchor: NSView?) { guard let url = writeTemporaryFile(capture) else { return } let subject = capture.details.title.isEmpty ? "Screenshot" : "Screenshot: \(capture.details.title)" if let service = NSSharingService(named: .composeEmail) { service.subject = subject service.perform(withItems: [url]) } else { // No mail client is configured for the share service; fall back to the share sheet. share(capture, anchor: anchor) } } // MARK: - Helpers static func writeTemporaryFile(_ capture: Capture) -> URL? { let configuration = Configuration.shared let name = FilenamePattern.filename(pattern: configuration.outputFileFilenamePattern, details: capture.details, format: configuration.outputFileFormat) let directory = FileManager.default.temporaryDirectory .appendingPathComponent("GreenshotMac", isDirectory: true) let url = ImageExporter.uniqueURL(directory.appendingPathComponent(name)) do { try ImageExporter.write(capture.image, to: url, format: configuration.outputFileFormat) return url } catch { presentError(error) return nil } } private static func anchorWindow() -> NSWindow? { NSApp.keyWindow ?? NSApp.windows.first { $0.isVisible } } static func presentError(_ error: Error) { let alert = NSAlert() alert.messageText = "GreenshotMac" alert.informativeText = error.localizedDescription alert.alertStyle = .warning alert.addButton(withTitle: "OK") NSApp.activate(ignoringOtherApps: true) alert.runModal() } }