diff --git a/include/ghostty.h b/include/ghostty.h index c2d7e6990..59ceef503 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -36,10 +36,10 @@ typedef enum { } ghostty_clipboard_e; typedef enum { - GHOSTTY_CLIPBOARD_PROMPT_UNSAFE = 1, - GHOSTTY_CLIPBOARD_PROMPT_READ = 2, - GHOSTTY_CLIPBOARD_PROMPT_WRITE = 3, -} ghostty_clipboard_prompt_reason_e; + GHOSTTY_CLIPBOARD_REQUEST_PASTE, + GHOSTTY_CLIPBOARD_REQUEST_OSC_52_READ, + GHOSTTY_CLIPBOARD_REQUEST_OSC_52_WRITE, +} ghostty_clipboard_request_e; typedef enum { GHOSTTY_SPLIT_RIGHT, @@ -346,7 +346,7 @@ typedef void (*ghostty_runtime_set_title_cb)(void *, const char *); typedef void (*ghostty_runtime_set_mouse_shape_cb)(void *, ghostty_mouse_shape_e); typedef void (*ghostty_runtime_set_mouse_visibility_cb)(void *, bool); typedef void (*ghostty_runtime_read_clipboard_cb)(void *, ghostty_clipboard_e, void *); -typedef void (*ghostty_runtime_confirm_read_clipboard_cb)(void *, const char*, void *, ghostty_clipboard_prompt_reason_e); +typedef void (*ghostty_runtime_confirm_read_clipboard_cb)(void *, const char*, void *, ghostty_clipboard_request_e); typedef void (*ghostty_runtime_write_clipboard_cb)(void *, const char *, ghostty_clipboard_e, bool); typedef void (*ghostty_runtime_new_split_cb)(void *, ghostty_split_direction_e, ghostty_surface_config_s); typedef void (*ghostty_runtime_new_tab_cb)(void *, ghostty_surface_config_s); diff --git a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationController.swift b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationController.swift index bd68668a2..2040dcfae 100644 --- a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationController.swift +++ b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationController.swift @@ -11,14 +11,14 @@ class ClipboardConfirmationController: NSWindowController { let surface: ghostty_surface_t let contents: String - let reason: Ghostty.ClipboardPromptReason + let request: Ghostty.ClipboardRequest let state: UnsafeMutableRawPointer? weak private var delegate: ClipboardConfirmationViewDelegate? = nil - init(surface: ghostty_surface_t, contents: String, reason: Ghostty.ClipboardPromptReason, state: UnsafeMutableRawPointer?, delegate: ClipboardConfirmationViewDelegate) { + init(surface: ghostty_surface_t, contents: String, request: Ghostty.ClipboardRequest, state: UnsafeMutableRawPointer?, delegate: ClipboardConfirmationViewDelegate) { self.surface = surface self.contents = contents - self.reason = reason + self.request = request self.state = state self.delegate = delegate super.init(window: nil) @@ -33,16 +33,16 @@ class ClipboardConfirmationController: NSWindowController { override func windowDidLoad() { guard let window = window else { return } - switch (reason) { - case .unsafe: + switch (request) { + case .paste: window.title = "Warning: Potentially Unsafe Paste" - case .read, .write: + case .osc_52_read, .osc_52_write: window.title = "Authorize Clipboard Access" } window.contentView = NSHostingView(rootView: ClipboardConfirmationView( contents: contents, - reason: reason, + request: request, delegate: delegate )) } diff --git a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift index 7f7faa8de..239b493c2 100644 --- a/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift +++ b/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift @@ -2,7 +2,7 @@ import SwiftUI /// This delegate is notified of the completion result of the clipboard confirmation dialog. protocol ClipboardConfirmationViewDelegate: AnyObject { - func clipboardConfirmationComplete(_ action: ClipboardConfirmationView.Action, _ reason: Ghostty.ClipboardPromptReason) + func clipboardConfirmationComplete(_ action: ClipboardConfirmationView.Action, _ request: Ghostty.ClipboardRequest) } /// The SwiftUI view for showing a clipboard confirmation dialog. @@ -11,18 +11,16 @@ struct ClipboardConfirmationView: View { case cancel case confirm - static func text(_ action: Action, _ reason: Ghostty.ClipboardPromptReason) -> String { - switch (action) { - case .cancel: - switch (reason) { - case .unsafe: return "Cancel" - case .read, .write: return "Deny" - } - case .confirm: - switch (reason) { - case .unsafe: return "Paste" - case .read, .write: return "Allow" - } + 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" } } } @@ -30,8 +28,8 @@ struct ClipboardConfirmationView: View { /// The contents of the paste. let contents: String - /// The reason for displaying the view - let reason: Ghostty.ClipboardPromptReason + /// 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? = nil @@ -45,7 +43,7 @@ struct ClipboardConfirmationView: View { .padding() .frame(alignment: .center) - Text(reason.text()) + Text(request.text()) .frame(maxWidth: .infinity, alignment: .leading) .padding() } @@ -57,9 +55,9 @@ struct ClipboardConfirmationView: View { HStack { Spacer() - Button(Action.text(.cancel, reason)) { onCancel() } + Button(Action.text(.cancel, request)) { onCancel() } .keyboardShortcut(.cancelAction) - Button(Action.text(.confirm, reason)) { onPaste() } + Button(Action.text(.confirm, request)) { onPaste() } .keyboardShortcut(.defaultAction) Spacer() } @@ -68,10 +66,10 @@ struct ClipboardConfirmationView: View { } private func onCancel() { - delegate?.clipboardConfirmationComplete(.cancel, reason) + delegate?.clipboardConfirmationComplete(.cancel, request) } private func onPaste() { - delegate?.clipboardConfirmationComplete(.confirm, reason) + delegate?.clipboardConfirmationComplete(.confirm, request) } } diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index fe7069ad4..61853fa71 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -348,7 +348,7 @@ class TerminalController: NSWindowController, NSWindowDelegate, //MARK: - Clipboard Confirmation - func clipboardConfirmationComplete(_ action: ClipboardConfirmationView.Action, _ reason: Ghostty.ClipboardPromptReason) { + func clipboardConfirmationComplete(_ action: ClipboardConfirmationView.Action, _ request: Ghostty.ClipboardRequest) { // End our clipboard confirmation no matter what guard let cc = self.clipboardConfirmation else { return } self.clipboardConfirmation = nil @@ -358,13 +358,13 @@ class TerminalController: NSWindowController, NSWindowDelegate, window?.endSheet(ccWindow) } - switch (reason) { - case .write: + switch (request) { + case .osc_52_write: guard case .confirm = action else { break } let pb = NSPasteboard.general pb.declareTypes([.string], owner: nil) pb.setString(cc.contents, forType: .string) - case .read, .unsafe: + case .osc_52_read, .paste: let str: String switch (action) { case .cancel: @@ -448,7 +448,7 @@ class TerminalController: NSWindowController, NSWindowDelegate, // Check whether we use non-native fullscreen guard let str = notification.userInfo?[Ghostty.Notification.ConfirmClipboardStrKey] as? String else { return } guard let state = notification.userInfo?[Ghostty.Notification.ConfirmClipboardStateKey] as? UnsafeMutableRawPointer? else { return } - guard let reason = notification.userInfo?[Ghostty.Notification.ConfirmClipboardReasonKey] as? Ghostty.ClipboardPromptReason else { return } + guard let request = notification.userInfo?[Ghostty.Notification.ConfirmClipboardRequestKey] as? Ghostty.ClipboardRequest else { return } // If we already have a clipboard confirmation view up, we ignore this request. // This shouldn't be possible... @@ -461,7 +461,7 @@ class TerminalController: NSWindowController, NSWindowDelegate, self.clipboardConfirmation = ClipboardConfirmationController( surface: surface, contents: str, - reason: reason, + request: request, state: state, delegate: self ) diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 7cbdac8e5..f7c3d3bff 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -150,7 +150,7 @@ extension Ghostty { set_mouse_shape_cb: { userdata, shape in AppState.setMouseShape(userdata, shape: shape) }, set_mouse_visibility_cb: { userdata, visible in AppState.setMouseVisibility(userdata, visible: visible) }, read_clipboard_cb: { userdata, loc, state in AppState.readClipboard(userdata, location: loc, state: state) }, - confirm_read_clipboard_cb: { userdata, str, state, reason in AppState.confirmReadClipboard(userdata, string: str, state: state, reason: reason ) }, + confirm_read_clipboard_cb: { userdata, str, state, request in AppState.confirmReadClipboard(userdata, string: str, state: state, request: request ) }, write_clipboard_cb: { userdata, str, loc, confirm in AppState.writeClipboard(userdata, string: str, location: loc, confirm: confirm) }, new_split_cb: { userdata, direction, surfaceConfig in AppState.newSplit(userdata, direction: direction, config: surfaceConfig) }, new_tab_cb: { userdata, surfaceConfig in AppState.newTab(userdata, config: surfaceConfig) }, @@ -434,18 +434,18 @@ extension Ghostty { _ userdata: UnsafeMutableRawPointer?, string: UnsafePointer?, state: UnsafeMutableRawPointer?, - reason: ghostty_clipboard_prompt_reason_e + request: ghostty_clipboard_request_e ) { guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let valueStr = String(cString: string!, encoding: .utf8) else { return } - guard let reason = Ghostty.ClipboardPromptReason.from(reason: reason) else { return } + guard let request = Ghostty.ClipboardRequest.from(request: request) else { return } NotificationCenter.default.post( name: Notification.confirmClipboard, object: surface, userInfo: [ Notification.ConfirmClipboardStrKey: valueStr, Notification.ConfirmClipboardStateKey: state as Any, - Notification.ConfirmClipboardReasonKey: reason, + Notification.ConfirmClipboardRequestKey: request, ] ) } @@ -478,7 +478,7 @@ extension Ghostty { object: surface, userInfo: [ Notification.ConfirmClipboardStrKey: valueStr, - Notification.ConfirmClipboardReasonKey: Ghostty.ClipboardPromptReason.write, + Notification.ConfirmClipboardRequestKey: Ghostty.ClipboardRequest.osc_52_write, ] ) } diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index 1fa17d96a..49ab84ab6 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -95,29 +95,30 @@ extension Ghostty { } } - /// The reason a clipboard prompt is shown to the user - enum ClipboardPromptReason { - /// An unsafe paste may cause commands to be executed - case unsafe + /// The type of a clipboard request + enum ClipboardRequest { + /// A direct paste of clipboard contents + case paste - /// An application is attempting to read from the clipboard - case read + /// An application is attempting to read from the clipboard using OSC 52 + case osc_52_read - /// An applciation is attempting to write to the clipboard - case write + /// An applciation is attempting to write to the clipboard using OSC 52 + case osc_52_write + /// The text to show in the clipboard confirmation prompt for a given request type func text() -> String { switch (self) { - case .unsafe: + case .paste: return """ Pasting this text to the terminal may be dangerous as it looks like some commands may be executed. """ - case .read: + case .osc_52_read: return """ An application is attempting to read from the clipboard. The current clipboard contents are shown below. """ - case .write: + case .osc_52_write: return """ An application is attempting to write to the clipboard. The content to write is shown below. @@ -125,29 +126,18 @@ extension Ghostty { } } - static func from(reason: ghostty_clipboard_prompt_reason_e) -> ClipboardPromptReason? { - switch (reason) { - case GHOSTTY_CLIPBOARD_PROMPT_UNSAFE: - return .unsafe - case GHOSTTY_CLIPBOARD_PROMPT_READ: - return .read - case GHOSTTY_CLIPBOARD_PROMPT_WRITE: - return .write + static func from(request: ghostty_clipboard_request_e) -> ClipboardRequest? { + switch (request) { + case GHOSTTY_CLIPBOARD_REQUEST_PASTE: + return .paste + case GHOSTTY_CLIPBOARD_REQUEST_OSC_52_READ: + return .osc_52_read + case GHOSTTY_CLIPBOARD_REQUEST_OSC_52_WRITE: + return .osc_52_write default: return nil } } - - func toNative() -> ghostty_clipboard_prompt_reason_e { - switch (self) { - case .unsafe: - return GHOSTTY_CLIPBOARD_PROMPT_UNSAFE - case .read: - return GHOSTTY_CLIPBOARD_PROMPT_READ - case .write: - return GHOSTTY_CLIPBOARD_PROMPT_WRITE - } - } } } @@ -200,7 +190,7 @@ extension Ghostty.Notification { static let confirmClipboard = Notification.Name("com.mitchellh.ghostty.confirmClipboard") static let ConfirmClipboardStrKey = confirmClipboard.rawValue + ".str" static let ConfirmClipboardStateKey = confirmClipboard.rawValue + ".state" - static let ConfirmClipboardReasonKey = confirmClipboard.rawValue + ".reason" + static let ConfirmClipboardRequestKey = confirmClipboard.rawValue + ".request" /// Notification sent to the active split view to resize the split. static let didResizeSplit = Notification.Name("com.mitchellh.ghostty.didResizeSplit") diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 7b9d2f6f4..60faae7a8 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -17,7 +17,6 @@ const CoreInspector = @import("../inspector/main.zig").Inspector; const CoreSurface = @import("../Surface.zig"); const configpkg = @import("../config.zig"); const Config = configpkg.Config; -const ClipboardPromptReason = @import("../apprt/structs.zig").ClipboardPromptReason; const log = std.log.scoped(.embedded_window); @@ -69,7 +68,7 @@ pub const App = struct { SurfaceUD, [*:0]const u8, *apprt.ClipboardRequest, - ClipboardPromptReason, + apprt.ClipboardRequestType, ) callconv(.C) void, /// Write the clipboard value. @@ -508,6 +507,8 @@ pub const Surface = struct { ) void { const alloc = self.app.core_app.alloc; + const request_type = @as(apprt.ClipboardRequestType, state.*); + // Attempt to complete the request, but we may request // confirmation. self.core_surface.completeClipboardRequest( @@ -515,22 +516,14 @@ pub const Surface = struct { str, confirmed, ) catch |err| switch (err) { - error.UnsafePaste => { + error.UnsafePaste, + error.UnauthorizedPaste, + => { self.app.opts.confirm_read_clipboard( self.opts.userdata, str.ptr, state, - .unsafe, - ); - - return; - }, - error.UnauthorizedPaste => { - self.app.opts.confirm_read_clipboard( - self.opts.userdata, - str.ptr, - state, - .read, + request_type, ); return; diff --git a/src/apprt/gtk/ClipboardConfirmationWindow.zig b/src/apprt/gtk/ClipboardConfirmationWindow.zig index 30f231093..e4ad080e3 100644 --- a/src/apprt/gtk/ClipboardConfirmationWindow.zig +++ b/src/apprt/gtk/ClipboardConfirmationWindow.zig @@ -4,9 +4,8 @@ const ClipboardConfirmation = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; +const apprt = @import("../../apprt.zig"); const CoreSurface = @import("../../Surface.zig"); -const ClipboardRequest = @import("../structs.zig").ClipboardRequest; -const ClipboardPromptReason = @import("../structs.zig").ClipboardPromptReason; const App = @import("App.zig"); const View = @import("View.zig"); const c = @import("c.zig"); @@ -19,14 +18,13 @@ view: PrimaryView, data: [:0]u8, core_surface: CoreSurface, -pending_req: ClipboardRequest, -reason: ClipboardPromptReason, +pending_req: apprt.ClipboardRequest, pub fn create( app: *App, data: []const u8, core_surface: CoreSurface, - request: ClipboardRequest, + request: apprt.ClipboardRequest, ) !void { if (app.clipboard_confirmation_window != null) return error.WindowAlreadyExists; @@ -34,18 +32,11 @@ pub fn create( const self = try alloc.create(ClipboardConfirmation); errdefer alloc.destroy(self); - const reason: ClipboardPromptReason = switch (request) { - .paste => .unsafe, - .osc_52_read => .read, - .osc_52_write => .write, - }; - try self.init( app, data, core_surface, request, - reason, ); app.clipboard_confirmation_window = self; @@ -64,14 +55,13 @@ fn init( app: *App, data: []const u8, core_surface: CoreSurface, - request: ClipboardRequest, - reason: ClipboardPromptReason, + request: apprt.ClipboardRequest, ) !void { // Create the window const window = c.gtk_window_new(); const gtk_window: *c.GtkWindow = @ptrCast(window); errdefer c.gtk_window_destroy(gtk_window); - c.gtk_window_set_title(gtk_window, titleText(reason)); + c.gtk_window_set_title(gtk_window, titleText(request)); c.gtk_window_set_default_size(gtk_window, 550, 275); c.gtk_window_set_resizable(gtk_window, 0); _ = c.g_signal_connect_data( @@ -91,7 +81,6 @@ fn init( .data = try app.core_app.alloc.dupeZ(u8, data), .core_surface = core_surface, .pending_req = request, - .reason = reason, }; // Show the window @@ -116,7 +105,7 @@ const PrimaryView = struct { pub fn init(root: *ClipboardConfirmation, data: []const u8) !PrimaryView { // All our widgets - const label = c.gtk_label_new(promptText(root.reason)); + const label = c.gtk_label_new(promptText(root.pending_req)); const buf = unsafeBuffer(data); defer c.g_object_unref(buf); const buttons = try ButtonsView.init(root); @@ -168,10 +157,9 @@ const ButtonsView = struct { root: *c.GtkWidget, pub fn init(root: *ClipboardConfirmation) !ButtonsView { - const cancel_text, const confirm_text = switch (root.reason) { - .unsafe => .{ "Cancel", "Paste" }, - .read, .write => .{ "Deny", "Allow" }, - _ => unreachable, + const cancel_text, const confirm_text = switch (root.pending_req) { + .paste => .{ "Cancel", "Paste" }, + .osc_52_read, .osc_52_write => .{ "Deny", "Allow" }, }; const cancel_button = c.gtk_button_new_with_label(cancel_text); @@ -235,29 +223,27 @@ const ButtonsView = struct { }; /// The title of the window, based on the reason the prompt is being shown. -fn titleText(reason: ClipboardPromptReason) [:0]const u8 { - return switch (reason) { - .unsafe => "Warning: Potentially Unsafe Paste", - .read, .write => "Authorize Clipboard Access", - _ => unreachable, +fn titleText(req: apprt.ClipboardRequest) [:0]const u8 { + return switch (req) { + .paste => "Warning: Potentially Unsafe Paste", + .osc_52_read, .osc_52_write => "Authorize Clipboard Access", }; } /// The text to display in the prompt window, based on the reason the prompt /// is being shown. -fn promptText(reason: ClipboardPromptReason) [:0]const u8 { - return switch (reason) { - .unsafe => +fn promptText(req: apprt.ClipboardRequest) [:0]const u8 { + return switch (req) { + .paste => \\Pasting this text into the terminal may be dangerous as it looks like some commands may be executed. , - .read => + .osc_52_read => \\An appliclication is attempting to read from the clipboard. \\The current clipboard contents are shown below. , - .write => + .osc_52_write => \\An application is attempting to write to the clipboard. \\The content to write is shown below. , - _ => unreachable, }; } diff --git a/src/apprt/structs.zig b/src/apprt/structs.zig index 438a96376..d452bc8eb 100644 --- a/src/apprt/structs.zig +++ b/src/apprt/structs.zig @@ -32,9 +32,15 @@ pub const Clipboard = enum(u1) { selection = 1, // also known as the "primary" clipboard }; +pub const ClipboardRequestType = enum(u8) { + paste, + osc_52_read, + osc_52_write, +}; + /// Clipboard request. This is used to request clipboard contents and must /// be sent as a response to a ClipboardRequest event. -pub const ClipboardRequest = union(enum) { +pub const ClipboardRequest = union(ClipboardRequestType) { /// A direct paste of clipboard contents. paste: void, @@ -44,18 +50,3 @@ pub const ClipboardRequest = union(enum) { /// A request to write clipboard contents via OSC 52. osc_52_write: Clipboard, }; - -/// The reason for displaying a clipboard prompt to the user -pub const ClipboardPromptReason = enum(i32) { - /// For pasting data only. Pasted data contains potentially unsafe - /// characters - unsafe = 1, - - /// The user must authorize the application to read from the clipboard - read = 2, - - /// The user must authorize the application to write to the clipboard - write = 3, - - _, -};