210 lines
7.6 KiB
Swift
210 lines
7.6 KiB
Swift
// 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()
|
|
}
|
|
}
|