diff --git a/include/ghostty.h b/include/ghostty.h index 5b75ea1ad..664ae8011 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -29,6 +29,11 @@ typedef void *ghostty_config_t; typedef void *ghostty_surface_t; // Enums are up top so we can reference them later. +typedef enum { + GHOSTTY_CLIPBOARD_STANDARD, + GHOSTTY_CLIPBOARD_SELECTION, +} ghostty_clipboard_e; + typedef enum { GHOSTTY_SPLIT_RIGHT, GHOSTTY_SPLIT_DOWN @@ -238,8 +243,8 @@ typedef struct { typedef void (*ghostty_runtime_wakeup_cb)(void *); typedef const ghostty_config_t (*ghostty_runtime_reload_config_cb)(void *); typedef void (*ghostty_runtime_set_title_cb)(void *, const char *); -typedef const char* (*ghostty_runtime_read_clipboard_cb)(void *); -typedef void (*ghostty_runtime_write_clipboard_cb)(void *, const char *); +typedef const char* (*ghostty_runtime_read_clipboard_cb)(void *, ghostty_clipboard_e); +typedef void (*ghostty_runtime_write_clipboard_cb)(void *, const char *, ghostty_clipboard_e); typedef void (*ghostty_runtime_new_split_cb)(void *, ghostty_split_direction_e); typedef void (*ghostty_runtime_close_surface_cb)(void *, bool); typedef void (*ghostty_runtime_focus_split_cb)(void *, ghostty_split_focus_direction_e); @@ -248,6 +253,7 @@ typedef void (*ghostty_runtime_toggle_fullscreen_cb)(void *, bool); typedef struct { void *userdata; + bool supports_selection_clipboard; ghostty_runtime_wakeup_cb wakeup_cb; ghostty_runtime_reload_config_cb reload_config_cb; ghostty_runtime_set_title_cb set_title_cb; diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 4f5a1358d..3bc180288 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -54,11 +54,12 @@ extension Ghostty { // uses to interface with the application runtime environment. var runtime_cfg = ghostty_runtime_config_s( userdata: Unmanaged.passUnretained(self).toOpaque(), + supports_selection_clipboard: false, wakeup_cb: { userdata in AppState.wakeup(userdata) }, reload_config_cb: { userdata in AppState.reloadConfig(userdata) }, set_title_cb: { userdata, title in AppState.setTitle(userdata, title: title) }, - read_clipboard_cb: { userdata in AppState.readClipboard(userdata) }, - write_clipboard_cb: { userdata, str in AppState.writeClipboard(userdata, string: str) }, + read_clipboard_cb: { userdata, loc in AppState.readClipboard(userdata, location: loc) }, + write_clipboard_cb: { userdata, str, loc in AppState.writeClipboard(userdata, string: str, location: loc) }, new_split_cb: { userdata, direction in AppState.newSplit(userdata, direction: direction) }, close_surface_cb: { userdata, processAlive in AppState.closeSurface(userdata, processAlive: processAlive) }, focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) }, @@ -170,7 +171,10 @@ extension Ghostty { ) } - static func readClipboard(_ userdata: UnsafeMutableRawPointer?) -> UnsafePointer? { + static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e) -> UnsafePointer? { + // We only support the standard clipboard + if (location != GHOSTTY_CLIPBOARD_STANDARD) { return nil } + guard let appState = self.appState(fromSurface: userdata) else { return nil } guard let str = NSPasteboard.general.string(forType: .string) else { return nil } @@ -180,7 +184,10 @@ extension Ghostty { return (str as NSString).utf8String } - static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer?) { + static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer?, location: ghostty_clipboard_e) { + // We only support the standard clipboard + if (location != GHOSTTY_CLIPBOARD_STANDARD) { return } + guard let valueStr = String(cString: string!, encoding: .utf8) else { return } let pb = NSPasteboard.general pb.declareTypes([.string], owner: nil) diff --git a/src/Surface.zig b/src/Surface.zig index ddbc050e6..d40a1dfa5 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -144,6 +144,7 @@ const DerivedConfig = struct { clipboard_read: bool, clipboard_write: bool, clipboard_trim_trailing_spaces: bool, + copy_on_select: configpkg.CopyOnSelect, confirm_close_surface: bool, mouse_interval: u64, macos_non_native_fullscreen: bool, @@ -159,6 +160,7 @@ const DerivedConfig = struct { .clipboard_read = config.@"clipboard-read", .clipboard_write = config.@"clipboard-write", .clipboard_trim_trailing_spaces = config.@"clipboard-trim-trailing-spaces", + .copy_on_select = config.@"copy-on-select", .confirm_close_surface = config.@"confirm-close-surface", .mouse_interval = config.@"click-repeat-interval" * 1_000_000, // 500ms .macos_non_native_fullscreen = config.@"macos-non-native-fullscreen", @@ -601,11 +603,11 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { .clipboard_read => |kind| try self.clipboardRead(kind), .clipboard_write => |req| switch (req) { - .small => |v| try self.clipboardWrite(v.data[0..v.len]), - .stable => |v| try self.clipboardWrite(v), + .small => |v| try self.clipboardWrite(v.data[0..v.len], .standard), + .stable => |v| try self.clipboardWrite(v, .standard), .alloc => |v| { defer v.alloc.free(v.data); - try self.clipboardWrite(v.data); + try self.clipboardWrite(v.data, .standard); }, }, @@ -719,13 +721,60 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos { return .{ .x = x, .y = y }; } +/// Paste from the clipboard +fn clipboardPaste( + self: *Surface, + loc: apprt.Clipboard, + lock: bool, +) !void { + const data = self.rt_surface.getClipboardString(loc) catch |err| { + log.warn("error reading clipboard: {}", .{err}); + return; + }; + + if (data.len > 0) { + const bracketed = bracketed: { + if (lock) self.renderer_state.mutex.lock(); + defer if (lock) self.renderer_state.mutex.unlock(); + + // With the lock held, we must scroll to the bottom. + // We always scroll to the bottom for these inputs. + self.scrollToBottom() catch |err| { + log.warn("error scrolling to bottom err={}", .{err}); + }; + + break :bracketed self.io.terminal.modes.bracketed_paste; + }; + + if (bracketed) { + _ = self.io_thread.mailbox.push(.{ + .write_stable = "\x1B[200~", + }, .{ .forever = {} }); + } + + _ = self.io_thread.mailbox.push(try termio.Message.writeReq( + self.alloc, + data, + ), .{ .forever = {} }); + + if (bracketed) { + _ = self.io_thread.mailbox.push(.{ + .write_stable = "\x1B[201~", + }, .{ .forever = {} }); + } + + try self.io_thread.wakeup.notify(); + } +} + +/// This is similar to clipboardPaste but is used specifically for OSC 52 fn clipboardRead(self: *const Surface, kind: u8) !void { if (!self.config.clipboard_read) { log.info("application attempted to read clipboard, but 'clipboard-read' setting is off", .{}); return; } - const data = self.rt_surface.getClipboardString() catch |err| { + const data = self.rt_surface.getClipboardString(.standard) catch |err| { log.warn("error reading clipboard: {}", .{err}); return; }; @@ -755,7 +804,7 @@ fn clipboardRead(self: *const Surface, kind: u8) !void { self.io_thread.wakeup.notify() catch {}; } -fn clipboardWrite(self: *const Surface, data: []const u8) !void { +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", .{}); return; @@ -773,7 +822,52 @@ fn clipboardWrite(self: *const Surface, data: []const u8) !void { try dec.decode(buf, data); assert(buf[buf.len] == 0); - self.rt_surface.setClipboardString(buf) catch |err| { + self.rt_surface.setClipboardString(buf, loc) catch |err| { + log.err("error setting clipboard string err={}", .{err}); + return; + }; +} + +/// Set the selection contents. +/// +/// This must be called with the renderer mutex held. +fn setSelection(self: *Surface, sel_: ?terminal.Selection) void { + const prev_ = self.io.terminal.screen.selection; + self.io.terminal.screen.selection = sel_; + + // Determine the clipboard we want to copy selection to, if it is enabled. + const clipboard: apprt.Clipboard = switch (self.config.copy_on_select) { + .false => return, + .true => .selection, + .clipboard => .standard, + }; + + // Set our selection clipboard. If the selection is cleared we do not + // clear the clipboard. If the selection is set, we only set the clipboard + // again if it changed, since setting the clipboard can be an expensive + // operation. + const sel = sel_ orelse return; + if (prev_) |prev| if (std.meta.eql(sel, prev)) return; + + // Check if our runtime supports the selection clipboard at all. + // We can save a lot of work if it doesn't. + if (@hasDecl(apprt.runtime.Surface, "supportsClipboard")) { + if (!self.rt_surface.supportsClipboard(clipboard)) { + return; + } + } + + var buf = self.io.terminal.screen.selectionString( + self.alloc, + sel, + self.config.clipboard_trim_trailing_spaces, + ) catch |err| { + log.err("error reading selection string err={}", .{err}); + return; + }; + defer self.alloc.free(buf); + + self.rt_surface.setClipboardString(buf, clipboard) catch |err| { log.err("error setting clipboard string err={}", .{err}); return; }; @@ -905,7 +999,7 @@ pub fn charCallback(self: *Surface, codepoint: u21) !void { // Clear the selection if we have one. if (self.io.terminal.screen.selection != null) { - self.io.terminal.screen.selection = null; + self.setSelection(null); try self.queueRender(); } @@ -1212,7 +1306,7 @@ pub fn scrollCallback( // The selection can occur if the user uses the shift mod key to // override mouse grabbing from the window. if (self.io.terminal.modes.mouse_event != .none) { - self.io.terminal.screen.selection = null; + self.setSelection(null); } // If we're in alternate screen with alternate scroll enabled, then @@ -1523,7 +1617,7 @@ pub fn mouseButtonCallback( // In any other mouse button scenario without shift pressed we // clear the selection since the underlying application can handle // that in any way (i.e. "scrolling"). - self.io.terminal.screen.selection = null; + self.setSelection(null); const pos = try self.rt_surface.getCursorPos(); @@ -1591,7 +1685,7 @@ pub fn mouseButtonCallback( switch (self.mouse.left_click_count) { // First mouse click, clear selection 1 => if (self.io.terminal.screen.selection != null) { - self.io.terminal.screen.selection = null; + self.setSelection(null); try self.queueRender(); }, @@ -1599,7 +1693,7 @@ pub fn mouseButtonCallback( 2 => { const sel_ = self.io.terminal.screen.selectWord(self.mouse.left_click_point); if (sel_) |sel| { - self.io.terminal.screen.selection = sel; + self.setSelection(sel); try self.queueRender(); } }, @@ -1608,7 +1702,7 @@ pub fn mouseButtonCallback( 3 => { const sel_ = self.io.terminal.screen.selectLine(self.mouse.left_click_point); if (sel_) |sel| { - self.io.terminal.screen.selection = sel; + self.setSelection(sel); try self.queueRender(); } }, @@ -1617,6 +1711,18 @@ pub fn mouseButtonCallback( else => unreachable, } } + + // Middle-click pastes from our selection clipboard + if (button == .middle and action == .press) { + if (self.config.copy_on_select != .false) { + const clipboard: apprt.Clipboard = switch (self.config.copy_on_select) { + .true => .selection, + .clipboard => .standard, + .false => unreachable, + }; + try self.clipboardPaste(clipboard, false); + } + } } pub fn cursorPosCallback( @@ -1705,7 +1811,7 @@ fn dragLeftClickDouble( // We may not have a selection if we started our dbl-click in an area // that had no data, then we dragged our mouse into an area with data. var sel = self.io.terminal.screen.selectWord(self.mouse.left_click_point) orelse { - self.io.terminal.screen.selection = word; + self.setSelection(word); return; }; @@ -1715,7 +1821,7 @@ fn dragLeftClickDouble( } else { sel.end = word.end; } - self.io.terminal.screen.selection = sel; + self.setSelection(sel); } /// Triple-click dragging moves the selection one "line" at a time. @@ -1730,7 +1836,7 @@ fn dragLeftClickTriple( // We may not have a selection if we started our dbl-click in an area // that had no data, then we dragged our mouse into an area with data. var sel = self.io.terminal.screen.selectLine(self.mouse.left_click_point) orelse { - self.io.terminal.screen.selection = word; + self.setSelection(word); return; }; @@ -1740,7 +1846,7 @@ fn dragLeftClickTriple( } else { sel.end = word.end; } - self.io.terminal.screen.selection = sel; + self.setSelection(sel); } fn dragLeftClickSingle( @@ -1763,7 +1869,7 @@ fn dragLeftClickSingle( else screen_point.before(sel.start); - if (reset) self.io.terminal.screen.selection = null; + if (reset) self.setSelection(null); } // Our logic for determining if the starting cell is selected: @@ -1798,10 +1904,10 @@ fn dragLeftClickSingle( else cell_xpos < cell_xboundary; - self.io.terminal.screen.selection = if (selected) .{ + self.setSelection(if (selected) .{ .start = screen_point, .end = screen_point, - } else null; + } else null); return; } @@ -1840,7 +1946,7 @@ fn dragLeftClickSingle( } }; - self.io.terminal.screen.selection = .{ .start = start, .end = screen_point }; + self.setSelection(.{ .start = start, .end = screen_point }); return; } @@ -1850,7 +1956,9 @@ fn dragLeftClickSingle( // We moved! Set the selection end point. The start point should be // set earlier. assert(self.io.terminal.screen.selection != null); - self.io.terminal.screen.selection.?.end = screen_point; + var sel = self.io.terminal.screen.selection.?; + sel.end = screen_point; + self.setSelection(sel); } fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Viewport { @@ -1972,53 +2080,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !void }; defer self.alloc.free(buf); - self.rt_surface.setClipboardString(buf) catch |err| { + self.rt_surface.setClipboardString(buf, .standard) catch |err| { log.err("error setting clipboard string err={}", .{err}); return; }; } }, - .paste_from_clipboard => { - const data = self.rt_surface.getClipboardString() catch |err| { - log.warn("error reading clipboard: {}", .{err}); - return; - }; - - if (data.len > 0) { - const bracketed = bracketed: { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); - - // With the lock held, we must scroll to the bottom. - // We always scroll to the bottom for these inputs. - self.scrollToBottom() catch |err| { - log.warn("error scrolling to bottom err={}", .{err}); - }; - - break :bracketed self.io.terminal.modes.bracketed_paste; - }; - - if (bracketed) { - _ = self.io_thread.mailbox.push(.{ - .write_stable = "\x1B[200~", - }, .{ .forever = {} }); - } - - _ = self.io_thread.mailbox.push(try termio.Message.writeReq( - self.alloc, - data, - ), .{ .forever = {} }); - - if (bracketed) { - _ = self.io_thread.mailbox.push(.{ - .write_stable = "\x1B[201~", - }, .{ .forever = {} }); - } - - try self.io_thread.wakeup.notify(); - } - }, + .paste_from_clipboard => try self.clipboardPaste(.standard, true), .increase_font_size => |delta| { log.debug("increase font size={}", .{delta}); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 5c4c7bbc5..ca6b0eaa9 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -32,6 +32,9 @@ pub const App = struct { /// Userdata that is passed to all the callbacks. userdata: AppUD = null, + /// True if the selection clipboard is supported. + supports_selection_clipboard: bool = false, + /// Callback called to wakeup the event loop. This should trigger /// a full tick of the app loop. wakeup: *const fn (AppUD) callconv(.C) void, @@ -47,10 +50,10 @@ pub const App = struct { /// Read the clipboard value. The return value must be preserved /// by the host until the next call. If there is no valid clipboard /// value then this should return null. - read_clipboard: *const fn (SurfaceUD) callconv(.C) ?[*:0]const u8, + read_clipboard: *const fn (SurfaceUD, c_int) callconv(.C) ?[*:0]const u8, /// Write the clipboard value. - write_clipboard: *const fn (SurfaceUD, [*:0]const u8) callconv(.C) void, + write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int) callconv(.C) void, /// Create a new split view. If the embedder doesn't support split /// views then this can be null. @@ -239,13 +242,37 @@ pub const Surface = struct { ); } - pub fn getClipboardString(self: *const Surface) ![:0]const u8 { - const ptr = self.app.opts.read_clipboard(self.opts.userdata) orelse return ""; + pub fn supportsClipboard( + self: *const Surface, + clipboard_type: apprt.Clipboard, + ) bool { + return switch (clipboard_type) { + .standard => true, + .selection => self.app.opts.supports_selection_clipboard, + }; + } + + pub fn getClipboardString( + self: *const Surface, + clipboard_type: apprt.Clipboard, + ) ![:0]const u8 { + const ptr = self.app.opts.read_clipboard( + self.opts.userdata, + @intCast(@intFromEnum(clipboard_type)), + ) orelse return ""; return std.mem.sliceTo(ptr, 0); } - pub fn setClipboardString(self: *const Surface, val: [:0]const u8) !void { - self.app.opts.write_clipboard(self.opts.userdata, val.ptr); + pub fn setClipboardString( + self: *const Surface, + val: [:0]const u8, + clipboard_type: apprt.Clipboard, + ) !void { + self.app.opts.write_clipboard( + self.opts.userdata, + val.ptr, + @intCast(@intFromEnum(clipboard_type)), + ); } pub fn setShouldClose(self: *Surface) void { diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 6dd7c431d..83de490d2 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -25,6 +25,7 @@ const DevMode = @import("../DevMode.zig"); // Get native API access on certain platforms so we can do more customization. const glfwNative = glfw.Native(.{ .cocoa = builtin.target.isDarwin(), + .x11 = builtin.os.tag == .linux, }); const log = std.log.scoped(.glfw); @@ -503,15 +504,39 @@ pub const Surface = struct { /// Read the clipboard. The windowing system is responsible for allocating /// a buffer as necessary. This should be a stable pointer until the next /// time getClipboardString is called. - pub fn getClipboardString(self: *const Surface) ![:0]const u8 { + pub fn getClipboardString( + self: *const Surface, + clipboard_type: apprt.Clipboard, + ) ![:0]const u8 { _ = self; - return glfw.getClipboardString() orelse return glfw.mustGetErrorCode(); + return switch (clipboard_type) { + .standard => glfw.getClipboardString() orelse glfw.mustGetErrorCode(), + .selection => selection: { + // Not supported except on Linux + if (comptime builtin.os.tag != .linux) return ""; + + const raw = glfwNative.getX11SelectionString() orelse + return glfw.mustGetErrorCode(); + break :selection std.mem.span(raw); + }, + }; } /// Set the clipboard. - pub fn setClipboardString(self: *const Surface, val: [:0]const u8) !void { + pub fn setClipboardString( + self: *const Surface, + val: [:0]const u8, + clipboard_type: apprt.Clipboard, + ) !void { _ = self; - glfw.setClipboardString(val); + switch (clipboard_type) { + .standard => glfw.setClipboardString(val), + .selection => { + // Not supported except on Linux + if (comptime builtin.os.tag != .linux) return; + glfwNative.setX11SelectionString(val.ptr); + }, + } } /// The cursor position from glfw directly is in screen coordinates but diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index 5c1465ac1..e1f7be92f 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -15,6 +15,9 @@ pub const c = @cImport({ @cInclude("gtk/gtk.h"); }); +// We need native X11 access to access the primary clipboard. +const glfw_native = glfw.Native(.{ .x11 = true }); + /// Compatibility with gobject < 2.74 const G_CONNECT_DEFAULT = if (@hasDecl(c, "G_CONNECT_DEFAULT")) c.G_CONNECT_DEFAULT @@ -913,14 +916,25 @@ pub const Surface = struct { // )); } - pub fn getClipboardString(self: *Surface) ![:0]const u8 { - const clipboard = c.gtk_widget_get_clipboard(@ptrCast(self.gl_area)); - + pub fn getClipboardString( + self: *Surface, + clipboard_type: apprt.Clipboard, + ) ![:0]const u8 { + const clipboard = getClipboard(@ptrCast(self.gl_area), clipboard_type); const content = c.gdk_clipboard_get_content(clipboard) orelse { // On my machine, this NEVER works, so we fallback to glfw's - // implementation... + // implementation... I believe this never works because we need to + // use the async mechanism with GTK but that doesn't play nice + // with what our core expects. log.debug("no GTK clipboard contents, falling back to glfw", .{}); - return glfw.getClipboardString() orelse return glfw.mustGetErrorCode(); + return switch (clipboard_type) { + .standard => glfw.getClipboardString() orelse glfw.mustGetErrorCode(), + .selection => value: { + const raw = glfw_native.getX11SelectionString() orelse + return glfw.mustGetErrorCode(); + break :value std.mem.span(raw); + }, + }; }; c.g_value_unset(&self.clipboard); @@ -933,12 +947,22 @@ pub const Surface = struct { return std.mem.sliceTo(ptr, 0); } - pub fn setClipboardString(self: *const Surface, val: [:0]const u8) !void { - const clipboard = c.gtk_widget_get_clipboard(@ptrCast(self.gl_area)); - + pub fn setClipboardString( + self: *const Surface, + val: [:0]const u8, + clipboard_type: apprt.Clipboard, + ) !void { + const clipboard = getClipboard(@ptrCast(self.gl_area), clipboard_type); c.gdk_clipboard_set_text(clipboard, val.ptr); } + fn getClipboard(widget: *c.GtkWidget, clipboard: apprt.Clipboard) ?*c.GdkClipboard { + return switch (clipboard) { + .standard => c.gtk_widget_get_clipboard(widget), + .selection => c.gtk_widget_get_primary_clipboard(widget), + }; + } + pub fn getCursorPos(self: *const Surface) !apprt.CursorPos { return self.cursor_pos; } diff --git a/src/apprt/structs.zig b/src/apprt/structs.zig index 9ca2434d4..5e379af71 100644 --- a/src/apprt/structs.zig +++ b/src/apprt/structs.zig @@ -23,3 +23,11 @@ pub const IMEPos = struct { x: f64, y: f64, }; + +/// The clipboard type. +/// +/// If this is changed, you must also update ghostty.h +pub const Clipboard = enum(u1) { + standard = 0, // ctrl+c/v + selection = 1, // also known as the "primary" clipboard +}; diff --git a/src/config.zig b/src/config.zig index b5afc0684..ae41072a0 100644 --- a/src/config.zig +++ b/src/config.zig @@ -186,6 +186,17 @@ pub const Config = struct { /// This does not affect data sent to the clipboard via "clipboard-write". @"clipboard-trim-trailing-spaces": bool = true, + /// Whether to automatically copy selected text to the clipboard. "true" + /// will only copy on systems that support a selection clipboard. + /// + /// The value "clipboard" will copy to the system clipboard, making this + /// work on macOS. Note that middle-click will also paste from the system + /// clipboard in this case. + /// + /// Note that if this is disabled, middle-click paste will also be + /// disabled. + @"copy-on-select": CopyOnSelect = .true, + /// The time in milliseconds between clicks to consider a click a repeat /// (double, triple, etc.) or an entirely new single click. A value of /// zero will use a platform-specific default. The default on macOS @@ -1375,6 +1386,20 @@ pub const Keybinds = struct { } }; +/// Options for copy on select behavior. +pub const CopyOnSelect = enum { + /// Disables copy on select entirely. + false, + + /// Copy on select is enabled, but goes to the selection clipboard. + /// This is not supported on platforms such as macOS. This is the default. + true, + + /// Copy on select is enabled and goes to the system clipboard. + clipboard, +}; + +/// Shell integration values pub const ShellIntegration = enum { none, detect, diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index a02428c73..a3d8a45ab 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1417,7 +1417,7 @@ const StreamHandler = struct { } pub fn clipboardContents(self: *StreamHandler, kind: u8, data: []const u8) !void { - // Note: we ignore the "kind" field and always use the primary clipboard. + // Note: we ignore the "kind" field and always use the standard clipboard. // iTerm also appears to do this but other terminals seem to only allow // certain. Let's investigate more.