refactor: split OSC52 off into different interface
- change ID
- mwlqoqomsvxkkrvyxvmlllxkzqxrsuzp
- git commit
- d7feea3f86358bea2fc3c2c15875706183530d58
- author
- Grace Yoder <grace_yoder@outlook.com> - 2026-06-24T17:48:16-06:00
- committer
- Grace Yoder <grace_yoder@outlook.com> - 2026-06-25T16:42:38-06:00
- parents
- kvsvtrnmqzlt
- refs
- signature
- Present (not validated)
refactor: split OSC52 off into different interface
tree
| type | name | size |
|---|---|---|
| file | .gitignore | 354 bytes |
| file | LICENSE | 1073 bytes |
| file | README.md | 564 bytes |
| file | build.zig | 680 bytes |
| file | build.zig.zon | 247 bytes |
| file | src/Clip/OSC52.zig | 2746 bytes |
| file | src/Clip.zig | 45 bytes |
| file | src/main.zig | 1331 bytes |
diff
src/Clip/OSC52.zig
added (absent → file)
+/// OSC52 Clipboard backend
+/// Fullfils `Clip` interface
+pub const OSC52 = @This();
+
+const std = @import("std");
+const Io = std.Io;
+
+alloc: std.mem.Allocator,
+io: std.Io,
+
+base64buffer: std.ArrayList(u8),
+
+leftover_bytes: [3]u8,
+leftover_count: u8,
+
+const base64encoder = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, null);
+
+pub fn init(alloc: std.mem.Allocator, io: Io, buf_size: usize) !OSC52 {
+ return .{
+ .alloc = alloc,
+ .io = io,
+ .base64buffer = try std.ArrayList(u8).initCapacity(alloc, buf_size),
+ .leftover_bytes = undefined,
+ .leftover_count = 0,
+ };
+}
+
+pub fn writeCopyBuffer(self: *OSC52, buf: []u8) !void {
+ if (buf.len == 0) return;
+
+ // Start of buffer after dealing with leftover bytes
+ const buf_idx = 3 - self.leftover_count;
+
+ if (buf.len + self.leftover_count < 3) {
+ const len = buf.len + self.leftover_count;
+ @memcpy(self.leftover_bytes[self.leftover_count..len], buf[0..buf.len]);
+ self.leftover_count = @intCast(len);
+ return;
+ }
+
+ if (self.leftover_count != 0) {
+ @memcpy(self.leftover_bytes[self.leftover_count..3], buf[0..buf_idx]);
+ var temp: [5]u8 = undefined;
+ const s = OSC52.base64encoder.encode(&temp, &self.leftover_bytes);
+ try self.base64buffer.appendSlice(self.alloc, s);
+ }
+
+ const chunkable_length = buf.len - ((buf.len - buf_idx) % 3);
+ var chunker = std.mem.window(u8, buf[0..chunkable_length], 3, 3);
+ while (chunker.next()) |chunk| {
+ var temp: [5]u8 = undefined;
+ const s = OSC52.base64encoder.encode(&temp, chunk);
+ try self.base64buffer.appendSlice(self.alloc, s);
+ }
+
+ self.leftover_count = @intCast(buf.len - chunkable_length);
+ if (self.leftover_count != 0) {
+ @memmove(self.leftover_bytes[0..self.leftover_count], buf[chunkable_length..]);
+ }
+}
+
+pub fn writePasteboard(self: *OSC52) !void {
+
+ // https://ghostty.org/docs/vt/osc/52
+ const osc_header = &[_]u8{
+ 0x1b, // ESC
+ 0x5d, // ']'
+ 0x35, 0x32, // Code 52
+ 0x3b, // ';'
+ // 0x70, // 'p' Primary Clipboard
+ 0x63, // 'c' Standard Clipboard
+ 0x3b, // ';'
+ };
+ const osc_footer = &[_]u8{
+ 0x1b, // ESC
+ 0x5c, // '\'
+ };
+
+ // Write leftover bytes
+ var temp: [5]u8 = undefined;
+ const s = OSC52.base64encoder.encode(&temp, self.leftover_bytes[0..self.leftover_count]);
+ try self.base64buffer.appendSlice(self.alloc, s);
+
+ // Write data to stdout
+ const stdout = Io.File.stdout();
+ try stdout.writeStreamingAll(self.io, osc_header);
+ try stdout.writeStreamingAll(self.io, self.base64buffer.items);
+ try stdout.writeStreamingAll(self.io, osc_footer);
+}
src/Clip.zig
added (absent → file)
+pub const OSC52 = @import("Clip/OSC52.zig");
src/main.zig
modified (file → file)
const std = @import("std");
const File = std.Io.File;
+const Clip = @import("Clip.zig");
pub fn main(init: std.process.Init) !void {
const io = init.io;
// We want to avoid doing as many allocations as possible so having raw
// pages with it being larger should be totally fine
- const alloc = std.heap.page_allocator;
+ var clip = try Clip.OSC52.init(std.heap.page_allocator, init.io, std.heap.pageSize());
var buf: [4096]u8 = undefined;
- var copy_index: u8 = 0;
-
- var encoded_buffer = try std.ArrayList(u8).initCapacity(alloc, std.heap.pageSize());
- var encoder = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, null);
-
while (true) {
// Read as much of stdin there is
- const bytes = File.stdin().readStreaming(io, &.{buf[copy_index..]}) catch |err| {
+ const bytes = File.stdin().readStreaming(io, &.{&buf}) catch |err| {
if (err == File.ReadStreamingError.EndOfStream) {
break;
} else {
return err;
}
};
- const length = copy_index + bytes;
-
// Passthrough to stdout
- _ = try File.stdout().writeStreamingAll(io, buf[copy_index..length]);
-
- // Base64 encode
- const chunkable_length = length - (length % 3);
- var chunker = std.mem.window(u8, buf[0..chunkable_length], 3, 3);
- while (chunker.next()) |chunk| {
- var temp: [5]u8 = undefined;
- const s = encoder.encode(&temp, chunk);
- try encoded_buffer.appendSlice(alloc, s);
- }
+ _ = try File.stdout().writeStreamingAll(io, buf[0..bytes]);
- // Move left over bits to the start of the array
- copy_index = @intCast(length - chunkable_length);
- if (copy_index != 0) {
- @memmove(buf[0..copy_index], buf[chunkable_length..length]);
- }
+ // Write to clip
+ try clip.writeCopyBuffer(buf[0..bytes]);
}
- // Encode last bytes
- var temp: [5]u8 = undefined;
- const s = encoder.encode(&temp, buf[0..copy_index]);
- try encoded_buffer.appendSlice(alloc, s);
- // OSC 52 Copying — written as one contiguous buffer so the terminal
- // receives the complete sequence in a single write.
- // https://ghostty.org/docs/vt/osc/52
- const osc_header = &[_]u8{
- 0x1b, // ESC
- 0x5d, // ']'
- 0x35, 0x32, // Code 52
- 0x3b, // ';'
- // 0x70, // 'p' Primary Clipboard
- 0x63, // 'c' Standard Clipboard
- 0x3b, // ';'
- };
- const osc_footer = &[_]u8{
- 0x1b, // ESC
- 0x5c, // '\'
- };
+ try clip.writePasteboard();
- _ = try File.stdout().writeStreamingAll(io, osc_header);
- _ = try File.stdout().writeStreamingAll(io, encoded_buffer.items);
- _ = try File.stdout().writeStreamingAll(io, osc_footer);
std.debug.print("\x1b[0;32mCopied To Pasteboard!\x1b[0m\n", .{});
}