macos: preview images in the clipboard read confirmation dialog

This commit is contained in:
Mitchell Hashimoto
2026-08-24 08:33:37 -07:00
parent c1f0ef73a9
commit df14efaf33
4 changed files with 33 additions and 4 deletions

View File

@@ -56,6 +56,7 @@ class ClipboardConfirmationController: NSWindowController {
request: confirmation.kind,
programName: confirmation.programName,
canRemember: confirmation.canRemember,
previewImage: confirmation.previewImage,
delegate: delegate
))
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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
}