From f2a54bde424914876debb8d04a083879a963b488 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 21 Nov 2022 14:21:28 -0800 Subject: [PATCH] OSC 52 read --- src/Window.zig | 32 ++++++++++++++++++++++++++++++++ src/terminal/stream.zig | 6 ++++++ src/termio/Exec.zig | 16 ++++++++++++++++ src/window/message.zig | 3 +++ 4 files changed, 57 insertions(+) diff --git a/src/Window.zig b/src/Window.zig index d76938a1b..b5ec4c470 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -592,9 +592,41 @@ pub fn handleMessage(self: *Window, msg: Message) !void { }, .cell_size => |size| try self.setCellSize(size), + + .clipboard_read => |kind| try self.clipboardRead(kind), } } +fn clipboardRead(self: *const Window, kind: u8) !void { + const data = glfw.getClipboardString() catch |err| { + log.warn("error reading clipboard: {}", .{err}); + return; + }; + + // 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. + const enc = std.base64.standard.Encoder; + const size = enc.calcSize(data.len); + var buf = try self.alloc.alloc(u8, size + 8); // 8 for OSC + defer self.alloc.free(buf); + + // Wrap our data with the OSC code + const prefix = try std.fmt.bufPrint(buf, "\x1b]52;{c};", .{kind}); + assert(prefix.len == 7); + buf[buf.len - 1] = '\x1b'; + + // Do the base64 encoding + const encoded = enc.encode(buf[prefix.len..], data); + assert(encoded.len == size); + + _ = self.io_thread.mailbox.push(try termio.Message.writeReq( + self.alloc, + buf, + ), .{ .forever = {} }); + self.io_thread.wakeup.send() catch {}; +} + /// Change the cell size for the terminal grid. This can happen as /// a result of changing the font size at runtime. fn setCellSize(self: *Window, size: renderer.CellSize) !void { diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index acdfe1c31..cdde8addb 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -464,6 +464,12 @@ pub fn Stream(comptime Handler: type) type { } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, + .clipboard_contents => |clip| { + if (@hasDecl(T, "clipboardContents")) { + try self.handler.clipboardContents(clip.kind, clip.data); + } else log.warn("unimplemented OSC callback: {}", .{cmd}); + }, + else => if (@hasDecl(T, "oscUnimplemented")) try self.handler.oscUnimplemented(cmd) else diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index deea06466..fc7be7c28 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -815,4 +815,20 @@ const StreamHandler = struct { .set_title = buf, }, .{ .forever = {} }); } + + pub fn clipboardContents(self: *StreamHandler, kind: u8, data: []const u8) !void { + // Note: we ignore the "kind" field and always use the primary clipboard. + // iTerm also appears to do this but other terminals seem to only allow + // certain. Let's investigate more. + + // Get clipboard contents + if (data.len == 1 and data[0] == '?') { + _ = self.window_mailbox.push(.{ + .clipboard_read = kind, + }, .{ .forever = {} }); + return; + } + + unreachable; + } }; diff --git a/src/window/message.zig b/src/window/message.zig index ae14ffcdf..a6b49f753 100644 --- a/src/window/message.zig +++ b/src/window/message.zig @@ -12,6 +12,9 @@ pub const Message = union(enum) { /// Change the cell size. cell_size: renderer.CellSize, + + /// Read the clipboard and write to the pty. + clipboard_read: u8, }; /// A window mailbox.