mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 21:40:29 +00:00
macos: add option to prompt user for confirmation on OSC 52 commands
This commit is contained in:
@@ -140,8 +140,8 @@ const DerivedConfig = struct {
|
||||
/// For docs for these, see the associated config they are derived from.
|
||||
original_font_size: u8,
|
||||
keybind: configpkg.Keybinds,
|
||||
clipboard_read: bool,
|
||||
clipboard_write: bool,
|
||||
clipboard_read: configpkg.Config.ClipboardRequest,
|
||||
clipboard_write: configpkg.Config.ClipboardRequest,
|
||||
clipboard_trim_trailing_spaces: bool,
|
||||
clipboard_paste_protection: bool,
|
||||
clipboard_paste_bracketed_safe: bool,
|
||||
@@ -689,8 +689,8 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
||||
.cell_size => |size| try self.setCellSize(size),
|
||||
|
||||
.clipboard_read => |kind| {
|
||||
if (!self.config.clipboard_read) {
|
||||
log.info("application attempted to read clipboard, but 'clipboard-read' setting is off", .{});
|
||||
if (self.config.clipboard_read == .deny) {
|
||||
log.info("application attempted to read clipboard, but 'clipboard-read' is set to deny", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -822,8 +822,8 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||
}
|
||||
|
||||
fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard) !void {
|
||||
if (!self.config.clipboard_write) {
|
||||
log.info("application attempted to write clipboard, but 'clipboard-write' setting is off", .{});
|
||||
if (self.config.clipboard_write == .deny) {
|
||||
log.info("application attempted to write clipboard, but 'clipboard-write' is set to deny", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -856,7 +856,8 @@ fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard)
|
||||
};
|
||||
assert(buf[buf.len] == 0);
|
||||
|
||||
self.rt_surface.setClipboardString(buf, loc) catch |err| {
|
||||
const confirm = self.config.clipboard_write == .ask;
|
||||
self.rt_surface.setClipboardString(buf, loc, confirm) catch |err| {
|
||||
log.err("error setting clipboard string err={}", .{err});
|
||||
return;
|
||||
};
|
||||
@@ -901,7 +902,7 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) void {
|
||||
};
|
||||
defer self.alloc.free(buf);
|
||||
|
||||
self.rt_surface.setClipboardString(buf, clipboard) catch |err| {
|
||||
self.rt_surface.setClipboardString(buf, clipboard, false) catch |err| {
|
||||
log.err("error setting clipboard string err={}", .{err});
|
||||
return;
|
||||
};
|
||||
@@ -2279,7 +2280,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
};
|
||||
defer self.alloc.free(buf);
|
||||
|
||||
self.rt_surface.setClipboardString(buf, .standard) catch |err| {
|
||||
self.rt_surface.setClipboardString(buf, .standard, false) catch |err| {
|
||||
log.err("error setting clipboard string err={}", .{err});
|
||||
return true;
|
||||
};
|
||||
@@ -2509,19 +2510,25 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
/// only be called once for each request. The data is immediately copied so
|
||||
/// it is safe to free the data after this call.
|
||||
///
|
||||
/// If "allow_unsafe" is false, then the data is checked for "safety" prior.
|
||||
/// If unsafe data is detected, this will return error.UnsafePaste. Unsafe
|
||||
/// data is defined as data that contains newlines, though this definition
|
||||
/// may change later to detect other scenarios.
|
||||
/// If `confirmed` is true then any clipboard confirmation prompts are skipped:
|
||||
///
|
||||
/// - For "regular" pasting this means that unsafe pastes are allowed. Unsafe
|
||||
/// data is defined as data that contains newlines, though this definition
|
||||
/// may change later to detect other scenarios.
|
||||
///
|
||||
/// - For OSC 52 pastes no prompt is shown to the user if `confirmed` is true.
|
||||
///
|
||||
/// If `confirmed` is false and either unsafe data is detected or the
|
||||
/// `clipboard-read` option is set to `ask`, this will return error.UnsafePaste.
|
||||
pub fn completeClipboardRequest(
|
||||
self: *Surface,
|
||||
req: apprt.ClipboardRequest,
|
||||
data: []const u8,
|
||||
allow_unsafe: bool,
|
||||
confirmed: bool,
|
||||
) !void {
|
||||
switch (req) {
|
||||
.paste => try self.completeClipboardPaste(data, allow_unsafe),
|
||||
.osc_52 => |kind| try self.completeClipboardReadOSC52(data, kind),
|
||||
.paste => try self.completeClipboardPaste(data, confirmed),
|
||||
.osc_52 => |kind| try self.completeClipboardReadOSC52(data, kind, confirmed),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2534,9 +2541,9 @@ fn startClipboardRequest(
|
||||
) !void {
|
||||
switch (req) {
|
||||
.paste => {}, // always allowed
|
||||
.osc_52 => if (!self.config.clipboard_read) {
|
||||
.osc_52 => if (self.config.clipboard_read == .deny) {
|
||||
log.info(
|
||||
"application attempted to read clipboard, but 'clipboard-read' setting is off",
|
||||
"application attempted to read clipboard, but 'clipboard-read' is set to deny",
|
||||
.{},
|
||||
);
|
||||
return;
|
||||
@@ -2635,7 +2642,16 @@ fn completeClipboardPaste(
|
||||
try self.io_thread.wakeup.notify();
|
||||
}
|
||||
|
||||
fn completeClipboardReadOSC52(self: *Surface, data: []const u8, kind: u8) !void {
|
||||
fn completeClipboardReadOSC52(self: *Surface, data: []const u8, kind: u8, confirmed: bool) !void {
|
||||
// We should never get here if clipboard-read is set to deny
|
||||
assert(self.config.clipboard_read != .deny);
|
||||
|
||||
// If clipboard-read is set to ask and we haven't confirmed with the user,
|
||||
// do that now
|
||||
if (self.config.clipboard_read == .ask and !confirmed) {
|
||||
return error.UnauthorizedPaste;
|
||||
}
|
||||
|
||||
// Even if the clipboard data is empty we reply, since presumably
|
||||
// the client app is expecting a reply. We first allocate our buffer.
|
||||
// This must hold the base64 encoded data PLUS the OSC code surrounding it.
|
||||
|
||||
@@ -17,6 +17,7 @@ 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);
|
||||
|
||||
@@ -68,10 +69,11 @@ pub const App = struct {
|
||||
SurfaceUD,
|
||||
[*:0]const u8,
|
||||
*apprt.ClipboardRequest,
|
||||
ClipboardPromptReason,
|
||||
) callconv(.C) void,
|
||||
|
||||
/// Write the clipboard value.
|
||||
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int) callconv(.C) void,
|
||||
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int, bool) callconv(.C) void,
|
||||
|
||||
/// Create a new split view. If the embedder doesn't support split
|
||||
/// views then this can be null.
|
||||
@@ -506,7 +508,7 @@ pub const Surface = struct {
|
||||
) void {
|
||||
const alloc = self.app.core_app.alloc;
|
||||
|
||||
// Attempt to complete the request, but if its unsafe we may request
|
||||
// Attempt to complete the request, but we may request
|
||||
// confirmation.
|
||||
self.core_surface.completeClipboardRequest(
|
||||
state.*,
|
||||
@@ -518,6 +520,17 @@ pub const Surface = struct {
|
||||
self.opts.userdata,
|
||||
str.ptr,
|
||||
state,
|
||||
.unsafe,
|
||||
);
|
||||
|
||||
return;
|
||||
},
|
||||
error.UnauthorizedPaste => {
|
||||
self.app.opts.confirm_read_clipboard(
|
||||
self.opts.userdata,
|
||||
str.ptr,
|
||||
state,
|
||||
.read,
|
||||
);
|
||||
|
||||
return;
|
||||
@@ -526,8 +539,8 @@ pub const Surface = struct {
|
||||
else => log.err("error completing clipboard request err={}", .{err}),
|
||||
};
|
||||
|
||||
// We don't defer this because the unsafe paste route preserves
|
||||
// the clipboard request.
|
||||
// We don't defer this because the clipboard confirmation route
|
||||
// preserves the clipboard request.
|
||||
alloc.destroy(state);
|
||||
}
|
||||
|
||||
@@ -535,11 +548,13 @@ pub const Surface = struct {
|
||||
self: *const Surface,
|
||||
val: [:0]const u8,
|
||||
clipboard_type: apprt.Clipboard,
|
||||
confirm: bool,
|
||||
) !void {
|
||||
self.app.opts.write_clipboard(
|
||||
self.opts.userdata,
|
||||
val.ptr,
|
||||
@intCast(@intFromEnum(clipboard_type)),
|
||||
confirm,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -678,7 +678,9 @@ pub const Surface = struct {
|
||||
self: *const Surface,
|
||||
val: [:0]const u8,
|
||||
clipboard_type: apprt.Clipboard,
|
||||
confirm: bool,
|
||||
) !void {
|
||||
_ = confirm;
|
||||
_ = self;
|
||||
switch (clipboard_type) {
|
||||
.standard => glfw.setClipboardString(val),
|
||||
|
||||
@@ -515,7 +515,10 @@ pub fn setClipboardString(
|
||||
self: *const Surface,
|
||||
val: [:0]const u8,
|
||||
clipboard_type: apprt.Clipboard,
|
||||
confirm: bool,
|
||||
) !void {
|
||||
// TODO: implement confirmation dialog when clipboard-write is "ask"
|
||||
_ = confirm;
|
||||
const clipboard = getClipboard(@ptrCast(self.gl_area), clipboard_type);
|
||||
c.gdk_clipboard_set_text(clipboard, val.ptr);
|
||||
}
|
||||
|
||||
@@ -41,3 +41,18 @@ pub const ClipboardRequest = union(enum) {
|
||||
/// A request to write clipboard contents via OSC 52.
|
||||
osc_52: u8,
|
||||
};
|
||||
|
||||
/// 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,
|
||||
|
||||
_,
|
||||
};
|
||||
|
||||
@@ -456,9 +456,10 @@ keybind: Keybinds = .{},
|
||||
|
||||
/// Whether to allow programs running in the terminal to read/write to
|
||||
/// the system clipboard (OSC 52, for googling). The default is to
|
||||
/// disallow clipboard reading but allow writing.
|
||||
@"clipboard-read": bool = false,
|
||||
@"clipboard-write": bool = true,
|
||||
/// allow clipboard reading after prompting the user and allow writing
|
||||
/// unconditionally.
|
||||
@"clipboard-read": ClipboardRequest = .ask,
|
||||
@"clipboard-write": ClipboardRequest = .allow,
|
||||
|
||||
/// Trims trailing whitespace on data that is copied to the clipboard.
|
||||
/// This does not affect data sent to the clipboard via "clipboard-write".
|
||||
@@ -467,8 +468,6 @@ keybind: Keybinds = .{},
|
||||
/// Require confirmation before pasting text that appears unsafe. This helps
|
||||
/// prevent a "copy/paste attack" where a user may accidentally execute unsafe
|
||||
/// commands by pasting text with newlines.
|
||||
///
|
||||
/// This currently only works on Linux (GTK).
|
||||
@"clipboard-paste-protection": bool = true,
|
||||
|
||||
/// If true, bracketed pastes will be considered safe. By default,
|
||||
@@ -2278,7 +2277,7 @@ pub const ShellIntegrationFeatures = packed struct {
|
||||
cursor: bool = true,
|
||||
};
|
||||
|
||||
/// OSC 10 and 11 default color reporting format.
|
||||
/// OSC 4, 10, 11, and 12 default color reporting format.
|
||||
pub const OSCColorReportFormat = enum {
|
||||
none,
|
||||
@"8-bit",
|
||||
@@ -2306,3 +2305,10 @@ pub const MouseShiftCapture = enum {
|
||||
always,
|
||||
never,
|
||||
};
|
||||
|
||||
/// How to treat requests to write to or read from the clipboard
|
||||
pub const ClipboardRequest = enum {
|
||||
allow,
|
||||
deny,
|
||||
ask,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user