From 7500c0aa83ee5474b58e48e7a304eecd02fc52a2 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Sun, 14 Jun 2026 20:36:53 +0200 Subject: [PATCH] feat(terminal): support OSC 52 to yank to system clipboard Helper function for `App` which writes the necessary escape sequence directly to yank the provided string. --- examples/demo.zig | 8 +++++++- src/app.zig | 5 +++++ src/terminal.zig | 12 ++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/examples/demo.zig b/examples/demo.zig index 92f1812..f5b98c1 100644 --- a/examples/demo.zig +++ b/examples/demo.zig @@ -1,5 +1,5 @@ const QuitText = struct { - const text = "Press ctrl+c to quit. Press ctrl+n to launch `vim`."; + const text = "Press ctrl+c to quit. Press ctrl+n to launch `vim`. Press space to yank to system clipboard."; pub fn element(this: *@This()) App.Element { return .{ .ptr = this, .vtable = &.{ .content = content } }; @@ -150,6 +150,12 @@ pub fn main(init: std.process.Init) !void { .key => |key| { if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) try app.quit(); + if (key.eql(.{ .cp = zterm.input.Space })) try app.yankToSystemClipboard( + w, + gpa, + "This is a test of the OSC52 support:\n\tWith multiple lines and some special characters $?/-[]().╭─╮", + ); + if (key.eql(.{ .cp = 'n', .mod = .{ .ctrl = true } })) { try app.stop(w); try w.flush(); diff --git a/src/app.zig b/src/app.zig index cb11f9e..f7ea13a 100644 --- a/src/app.zig +++ b/src/app.zig @@ -177,6 +177,11 @@ pub fn App(comptime M: type, comptime E: type) type { try this.queue.push(this.io, e); } + pub fn yankToSystemClipboard(this: *@This(), w: *std.Io.Writer, gpa: Allocator, str: []const u8) !void { + _ = this; + try terminal.yankToSystemClipboard(w, gpa, str); + } + fn run(this: *@This()) !void { // thread to read user inputs var buf: [512]u8 = undefined; diff --git a/src/terminal.zig b/src/terminal.zig index ee35efa..e1f4404 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -63,6 +63,17 @@ pub fn ringBell(w: *std.Io.Writer) !void { _ = try w.write(&.{7}); } +pub fn yankToSystemClipboard(w: *std.Io.Writer, gpa: Allocator, str: []const u8) !void { + const encoder: std.base64.Base64Encoder = .init(std.base64.url_safe_alphabet_chars, '='); + const encoded_str: []u8 = try gpa.alloc(u8, encoder.calcSize(str.len)); + defer gpa.free(encoded_str); + _ = encoder.encode(encoded_str, str); + // NOTE for `tmux` support it should be configured to pass through the escape sequence for OSC52 + const value = try std.fmt.allocPrint(gpa, ctlseqs.osc52_clipboard_copy, .{encoded_str}); + defer gpa.free(value); + try w.writeAll(value); +} + pub fn read(buf: []u8) !usize { return try posix.read(posix.STDIN_FILENO, buf); } @@ -227,6 +238,7 @@ const std = @import("std"); const mem = std.mem; const posix = std.posix; const assert = std.debug.assert; +const Allocator = mem.Allocator; const ctlseqs = @import("ctlseqs.zig"); const input = @import("input.zig"); const Key = input.Key;