Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions macos/Ghostty.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,7 @@
Features/AppleScript/ScriptTab.swift,
Features/AppleScript/ScriptTerminal.swift,
Features/AppleScript/ScriptWindow.swift,
Features/ClipboardConfirmation/ClipboardConfirmation.xib,
Features/ClipboardConfirmation/ClipboardConfirmationController.swift,
Features/ClipboardConfirmation/ClipboardConfirmationView.swift,
"Features/Command Palette/CommandPalette.swift",
"Features/Command Palette/TerminalCommandPalette.swift",
"Features/Custom App Icon/AppIcon.swift",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,49 +1,119 @@
import Foundation
import Cocoa
import AppKit
import SwiftUI
import GhosttyKit

/// This initializes a clipboard confirmation warning window. The window itself
/// This initializes a clipboard confirmation warning alert. The window itself
/// WILL NOT show automatically and the caller must show the window via
/// showWindow, beginSheet, etc.
class ClipboardConfirmationController: NSWindowController {
override var windowNibName: NSNib.Name? { "ClipboardConfirmation" }

class ClipboardConfirmationAlert: NSAlert, NSAlertDelegate {
let surface: ghostty_surface_t
let contents: String
let request: Ghostty.ClipboardRequest
let state: UnsafeMutableRawPointer?
weak private var delegate: ClipboardConfirmationViewDelegate?

init(surface: ghostty_surface_t, contents: String, request: Ghostty.ClipboardRequest, state: UnsafeMutableRawPointer?, delegate: ClipboardConfirmationViewDelegate) {
enum Action: String {
case cancel
case confirm

static func text(_ action: Action, _ reason: Ghostty.ClipboardRequest) -> String {
switch (action, reason) {
case (.cancel, .paste):
return "Cancel"
case (.cancel, .osc_52_read), (.cancel, .osc_52_write):
return "Deny"
case (.confirm, .paste):
return "Paste"
case (.confirm, .osc_52_read), (.confirm, .osc_52_write):
return "Allow"
}
}
}

init(surface: ghostty_surface_t, contents: String, request: Ghostty.ClipboardRequest, state: UnsafeMutableRawPointer?) {
self.surface = surface
self.contents = contents
self.request = request
self.state = state
self.delegate = delegate
super.init(window: nil)
super.init()

showsHelp = true
switch request {
case .paste:
messageText = "Potentially Unsafe Paste"
alertStyle = .critical
helpAnchor = "clipboard-paste-protection"
case .osc_52_read, .osc_52_write:
messageText = "Authorize Clipboard Access"
alertStyle = .warning
helpAnchor = "clipboard-write"
}

informativeText = request.text()
let accessoryView = NSTextView.scrollableTextView()
// Maximum frame when calculating the content size
accessoryView.frame = .init(x: 0, y: 0, width: 400, height: 270)
if let textView = accessoryView.documentView as? NSTextView {
textView.drawsBackground = false
textView.isEditable = false
textView.font = .monospacedSystemFont(ofSize: NSFont.systemFontSize, weight: .medium)
textView.textContainerInset = .zero

textView.string = contents
}

self.accessoryView = accessoryView

addCancelButton(Action.text(.cancel, request))
addConfirmButton(Action.text(.confirm, request))
layout()
updateContentHeight()

delegate = self
}

required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported for this view")
private func updateContentHeight() {
guard
let accessoryView,
let textView = (accessoryView as? NSScrollView)?.documentView as? NSTextView,
let layoutManager = textView.layoutManager,
let textContainer = textView.textContainer
else {
return
}

textContainer.containerSize = CGSize(
width: accessoryView.frame.width,
height: .greatestFiniteMagnitude,
)
textContainer.widthTracksTextView = false

layoutManager.ensureLayout(for: textContainer)

let usedRect = layoutManager.usedRect(for: textContainer)
accessoryView.frame.size.height = .minimum(
accessoryView.frame.height,
.maximum(10, usedRect.height),
)
}

// MARK: - NSWindowController
func addCancelButton(_ buttonTitle: String) {
addButton(withTitle: buttonTitle)
.keyEquivalent = .init([KeyboardShortcut(.escape).key.character])
}

override func windowDidLoad() {
guard let window = window else { return }
func addConfirmButton(_ buttonTitle: String) {
addButton(withTitle: buttonTitle)
.keyEquivalent = .init([KeyboardShortcut(.return).key.character])
}

switch request {
case .paste:
window.title = "Warning: Potentially Unsafe Paste"
case .osc_52_read, .osc_52_write:
window.title = "Authorize Clipboard Access"
func alertShowHelp(_ alert: NSAlert) -> Bool {
var components = URLComponents(string: "https://ghostty.org/docs/config/reference")
components?.fragment = alert.helpAnchor
guard let url = components?.url else {
return false
}

window.contentView = NSHostingView(rootView: ClipboardConfirmationView(
contents: contents,
request: request,
delegate: delegate
))
NSWorkspace.shared.open(url)
return true
}
}

This file was deleted.

24 changes: 15 additions & 9 deletions macos/Sources/Features/Terminal/BaseTerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ class BaseTerminalController: NSWindowController,
NSWindowDelegate,
TerminalViewDelegate,
TerminalViewModel,
ClipboardConfirmationViewDelegate,
FullscreenDelegate {
/// The app instance that this terminal view will represent.
let ghostty: Ghostty.App
Expand Down Expand Up @@ -63,7 +62,7 @@ class BaseTerminalController: NSWindowController,
private var alert: NSAlert?

/// The clipboard confirmation window, if shown.
private var clipboardConfirmation: ClipboardConfirmationController?
private var clipboardConfirmation: ClipboardConfirmationAlert?

/// Fullscreen state management.
private(set) var fullscreenStyle: FullscreenStyle?
Expand Down Expand Up @@ -1117,25 +1116,32 @@ class BaseTerminalController: NSWindowController,
}

// Show our paste confirmation
self.clipboardConfirmation = ClipboardConfirmationController(
self.clipboardConfirmation = ClipboardConfirmationAlert(
surface: surface,
contents: str,
request: request,
state: state,
delegate: self
)
window.beginSheet(self.clipboardConfirmation!.window!)

clipboardConfirmation?.beginSheetModal(for: window) { [weak self] response in
switch response {
case .alertFirstButtonReturn:
self?.clipboardConfirmationComplete(.cancel, request)
case .alertSecondButtonReturn:
self?.clipboardConfirmationComplete(.confirm, request)
default:
break
}
}
}

func clipboardConfirmationComplete(_ action: ClipboardConfirmationView.Action, _ request: Ghostty.ClipboardRequest) {
func clipboardConfirmationComplete(_ action: ClipboardConfirmationAlert.Action, _ request: Ghostty.ClipboardRequest) {
// End our clipboard confirmation no matter what
guard let cc = self.clipboardConfirmation else { return }
self.clipboardConfirmation = nil

// Close the sheet
if let ccWindow = cc.window {
window?.endSheet(ccWindow)
}
cc.window.orderOut(nil)

switch request {
case let .osc_52_write(pasteboard):
Expand Down
Loading