Lukas 2026-06-02 21:56:29 -07:00 committed by GitHub
commit 9280630f1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 111 additions and 161 deletions

View File

@ -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",

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22155" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22155"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="PasteProtectionController" customModule="Ghostty" customModuleProvider="target">
<connections>
<outlet property="window" destination="F0z-JX-Cv5" id="V20-69-5rG"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Clipboard Confirmation" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5" userLabel="Clipboard Confirmation">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="944"/>
<view key="contentView" wantsLayer="YES" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<point key="canvasLocation" x="-116" y="100"/>
</window>
</objects>
</document>

View File

@ -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)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported for this view")
}
// MARK: - NSWindowController
override func windowDidLoad() {
guard let window = window else { return }
super.init()
showsHelp = true
switch request {
case .paste:
window.title = "Warning: Potentially Unsafe Paste"
messageText = "Potentially Unsafe Paste"
alertStyle = .critical
helpAnchor = "clipboard-paste-protection"
case .osc_52_read, .osc_52_write:
window.title = "Authorize Clipboard Access"
messageText = "Authorize Clipboard Access"
alertStyle = .warning
helpAnchor = "clipboard-write"
}
window.contentView = NSHostingView(rootView: ClipboardConfirmationView(
contents: contents,
request: request,
delegate: delegate
))
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
}
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),
)
}
func addCancelButton(_ buttonTitle: String) {
addButton(withTitle: buttonTitle)
.keyEquivalent = .init([KeyboardShortcut(.escape).key.character])
}
func addConfirmButton(_ buttonTitle: String) {
addButton(withTitle: buttonTitle)
.keyEquivalent = .init([KeyboardShortcut(.return).key.character])
}
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
}
NSWorkspace.shared.open(url)
return true
}
}

View File

@ -1,96 +0,0 @@
import SwiftUI
/// This delegate is notified of the completion result of the clipboard confirmation dialog.
protocol ClipboardConfirmationViewDelegate: AnyObject {
func clipboardConfirmationComplete(_ action: ClipboardConfirmationView.Action, _ request: Ghostty.ClipboardRequest)
}
/// The SwiftUI view for showing a clipboard confirmation dialog.
struct ClipboardConfirmationView: View {
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"
}
}
}
/// The contents of the paste.
let contents: String
/// The type of the clipboard request
let request: Ghostty.ClipboardRequest
/// Optional delegate to get results. If this is nil, then this view will never close on its own.
weak var delegate: ClipboardConfirmationViewDelegate?
/// Used to track if we should rehide on disappear
@State private var cursorHiddenCount: UInt = 0
var body: some View {
VStack {
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.yellow)
.font(.system(size: 42))
.padding()
.frame(alignment: .center)
Text(request.text())
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
TextEditor(text: .constant(contents))
.focusable(false)
.font(.system(.body, design: .monospaced))
HStack {
Spacer()
Button(Action.text(.cancel, request)) { onCancel() }
.keyboardShortcut(.cancelAction)
Button(Action.text(.confirm, request)) { onPaste() }
.keyboardShortcut(.defaultAction)
Spacer()
}
.padding(.bottom)
}
.onAppear {
// I can't find a better way to handle this. There is no API to detect
// if the cursor is hidden and OTHER THINGS do unhide the cursor. So we
// try to unhide it completely here and hope for the best. Issue #1516.
cursorHiddenCount = Cursor.unhideCompletely()
// If we didn't unhide anything, we just send an unhide to be safe.
// I don't think the count can go negative on NSCursor so this handles
// scenarios cursor is hidden outside of our own NSCursor usage.
if cursorHiddenCount == 0 {
_ = Cursor.unhide()
}
}
.onDisappear {
// Rehide if we unhid
for _ in 0..<cursorHiddenCount {
Cursor.hide()
}
}
}
private func onCancel() {
delegate?.clipboardConfirmationComplete(.cancel, request)
}
private func onPaste() {
delegate?.clipboardConfirmationComplete(.confirm, request)
}
}

View File

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