diff --git a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationController.swift b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationController.swift index cd91d0824..cfab6c9b5 100644 --- a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationController.swift +++ b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationController.swift @@ -56,6 +56,7 @@ class ClipboardConfirmationController: NSWindowController { request: confirmation.kind, programName: confirmation.programName, canRemember: confirmation.canRemember, + previewImage: confirmation.previewImage, delegate: delegate )) } diff --git a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift index c66e0f0e8..101cf5ac8 100644 --- a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift +++ b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift @@ -39,6 +39,10 @@ struct ClipboardConfirmationView: View { /// grant, showing the remember toggle. var canRemember: Bool = false + /// An image decoded from the request contents, shown scaled in + /// place of most of the text area when present. + var previewImage: NSImage? + /// Optional delegate to get results. If this is nil, then this view will never close on its own. weak var delegate: ClipboardConfirmationViewDelegate? @@ -62,9 +66,17 @@ struct ClipboardConfirmationView: View { .padding() } - TextEditor(text: .constant(contents)) - .focusable(false) - .font(.system(.body, design: .monospaced)) + if let previewImage { + Image(nsImage: previewImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.horizontal) + } else { + TextEditor(text: .constant(contents)) + .focusable(false) + .font(.system(.body, design: .monospaced)) + } if canRemember { Toggle("Remember this choice for the session", isOn: $remember) diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 77a3fa1d7..bf06e3ef7 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -391,6 +391,13 @@ extension Ghostty { .flatMap { String(data: $0.data, encoding: .utf8) } ?? reps.map { "\($0.mime) (\($0.data.count) bytes)" }.joined(separator: "\n") + // Decode an image representation so the dialog can preview + // exactly what would be disclosed rather than a byte count. + let previewImage: NSImage? = reps.lazy + .filter { $0.mime.hasPrefix("image/") } + .compactMap { NSImage(data: $0.data) } + .first + // libghostty reaches this callback only when the request attempted // by readClipboard requires confirmation. Reads allowed by policy // complete immediately and never become pending Swift state. @@ -399,7 +406,8 @@ extension Ghostty { contents: display, kind: kind, programName: c.name.map { String(cString: $0) }, - canRemember: c.can_remember + canRemember: c.can_remember, + previewImage: previewImage ) { surfaceView, confirmed, remember in guard let surface = surfaceView.surface else { return } if confirmed { diff --git a/macos/Sources/Ghostty/Ghostty.ClipboardConfirmationRequest.swift b/macos/Sources/Ghostty/Ghostty.ClipboardConfirmationRequest.swift index d41461fa1..ee31b179f 100644 --- a/macos/Sources/Ghostty/Ghostty.ClipboardConfirmationRequest.swift +++ b/macos/Sources/Ghostty/Ghostty.ClipboardConfirmationRequest.swift @@ -1,3 +1,4 @@ +import AppKit import Foundation import GhosttyKit @@ -81,6 +82,11 @@ extension Ghostty { /// grant, showing a remember option in the prompt. let canRemember: Bool + /// An image decoded from the request contents, previewed scaled + /// in the dialog when the request carries an image + /// representation. + let previewImage: NSImage? + /// Called exactly once with whether the user confirmed the /// request and whether their decision should be remembered. private var completion: ((SurfaceView, Bool, Bool) -> Void)? @@ -91,6 +97,7 @@ extension Ghostty { kind: ClipboardRequest, programName: String? = nil, canRemember: Bool = false, + previewImage: NSImage? = nil, completion: @escaping (SurfaceView, Bool, Bool) -> Void ) { self.surface = surface @@ -98,6 +105,7 @@ extension Ghostty { self.kind = kind self.programName = programName self.canRemember = canRemember + self.previewImage = previewImage self.completion = completion }