diff --git a/macos/Sources/Features/Paste Protection/PasteProtectionController.swift b/macos/Sources/Features/Paste Protection/PasteProtectionController.swift index bad16bf06..ef0668c02 100644 --- a/macos/Sources/Features/Paste Protection/PasteProtectionController.swift +++ b/macos/Sources/Features/Paste Protection/PasteProtectionController.swift @@ -6,10 +6,24 @@ import GhosttyKit class PasteProtectionController: NSWindowController { override var windowNibName: NSNib.Name? { "PasteProtection" } + weak private var delegate: PasteProtectionViewDelegate? = nil + + init(delegate: PasteProtectionViewDelegate) { + self.delegate = delegate + super.init(window: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) is not supported for this view") + } + //MARK: - NSWindowController override func windowDidLoad() { guard let window = window else { return } - window.contentView = NSHostingView(rootView: PasteProtectionView()) + window.contentView = NSHostingView(rootView: PasteProtectionView( + contents: "Hello", + delegate: delegate + )) } } diff --git a/macos/Sources/Features/Paste Protection/PasteProtectionView.swift b/macos/Sources/Features/Paste Protection/PasteProtectionView.swift index 61d084cbe..6c1cd08d2 100644 --- a/macos/Sources/Features/Paste Protection/PasteProtectionView.swift +++ b/macos/Sources/Features/Paste Protection/PasteProtectionView.swift @@ -1,18 +1,57 @@ import SwiftUI +protocol PasteProtectionViewDelegate: AnyObject { + func pasteProtectionComplete(_ action: PasteProtectionView.Action) +} + struct PasteProtectionView: View { + enum Action : String { + case cancel + case paste + } + + let contents: String + weak var delegate: PasteProtectionViewDelegate? = nil + var body: some View { - HStack { - Image("AppIconImage") - .resizable() - .scaledToFit() - .frame(width: 128, height: 128) - - VStack(alignment: .leading) { - Text("Oh, no. 😭").font(.title) - Text("Something went fatally wrong.\nCheck the logs and restart Ghostty.") + VStack { + HStack { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.yellow) + .font(.system(size: 42)) + .padding() + .frame(alignment: .center) + + Text("Pasting this text to the terminal may be dangerous as it looks like " + + "some commands may be executed.") + .frame(maxWidth: .infinity, alignment: .leading) + .padding() } + + TextEditor(text: .constant(contents)) + .disabled(true) + .textSelection(.enabled) + .font(.system(.body, design: .monospaced)) + .padding(.all, 4) + + HStack { + Spacer() + Button("Cancel") { onCancel() } + .keyboardShortcut(.cancelAction) + Button("Paste") { onPaste() } + .keyboardShortcut(.defaultAction) + Spacer() + } + .padding(.bottom) } - .padding() + } + + private func onCancel() { + AppDelegate.logger.warning("PASTE onCancel") + delegate?.pasteProtectionComplete(.cancel) + } + + private func onPaste() { + delegate?.pasteProtectionComplete(.paste) } } diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 7aa4b7b26..b277a1f6d 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -4,7 +4,10 @@ import SwiftUI import GhosttyKit /// The terminal controller is an NSWindowController that maps 1:1 to a terminal window. -class TerminalController: NSWindowController, NSWindowDelegate, TerminalViewDelegate, TerminalViewModel { +class TerminalController: NSWindowController, NSWindowDelegate, + TerminalViewDelegate, TerminalViewModel, + PasteProtectionViewDelegate +{ override var windowNibName: NSNib.Name? { "Terminal" } /// The app instance that this terminal view will represent. @@ -28,6 +31,9 @@ class TerminalController: NSWindowController, NSWindowDelegate, TerminalViewDele /// True when an alert is active so we don't overlap multiple. private var alert: NSAlert? = nil + /// The paste protection window, if shown. + private var pasteProtection: NSWindow? = nil + init(_ ghostty: Ghostty.AppState, withBaseConfig base: Ghostty.SurfaceConfiguration? = nil) { self.ghostty = ghostty super.init(window: nil) @@ -117,7 +123,8 @@ class TerminalController: NSWindowController, NSWindowDelegate, TerminalViewDele )) // TODO: remove this, just for dev - let pp = PasteProtectionController() + let pp = PasteProtectionController(delegate: self) + self.pasteProtection = pp.window window.beginSheet(pp.window!) } @@ -313,6 +320,17 @@ class TerminalController: NSWindowController, NSWindowDelegate, TerminalViewDele self.window?.close() } + //MARK: - PasteProtectionViewDelegate + + func pasteProtectionComplete(_ action: PasteProtectionView.Action) { + if let pasteWindow = self.pasteProtection { + window?.endSheet(pasteWindow) + self.pasteProtection = nil + } + + AppDelegate.logger.warning("PASTE action=\(action.rawValue)") + } + //MARK: - Notifications @objc private func onGotoTab(notification: SwiftUI.Notification) { diff --git a/macos/Sources/Features/Terminal/TerminalView.swift b/macos/Sources/Features/Terminal/TerminalView.swift index d3b1a66dc..42d15b38f 100644 --- a/macos/Sources/Features/Terminal/TerminalView.swift +++ b/macos/Sources/Features/Terminal/TerminalView.swift @@ -4,7 +4,7 @@ import GhosttyKit /// This delegate is notified of actions and property changes regarding the terminal view. This /// delegate is optional and can be used by a TerminalView caller to react to changes such as /// titles being set, cell sizes being changed, etc. -protocol TerminalViewDelegate: AnyObject, ObservableObject { +protocol TerminalViewDelegate: AnyObject { /// Called when the currently focused surface changed. This can be nil. func focusedSurfaceDidChange(to: Ghostty.SurfaceView?)