From 9a377fa209cfecf53343247d197dc8578d6c2a1d Mon Sep 17 00:00:00 2001 From: Grace Yoder Date: Tue, 30 Jun 2026 10:02:29 -0600 Subject: [PATCH] feat(NSPasteboard): uses backend when not ssh and on MacOS --- src/Clip.zig | 9 ++++++--- src/Clip/NSPasteboard.zig | 37 ++++++++++++++++++++++++++++++++++++- src/main.zig | 29 ++++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/Clip.zig b/src/Clip.zig index 1adeeb0..b4e1187 100644 --- a/src/Clip.zig +++ b/src/Clip.zig @@ -1,5 +1,10 @@ +const std = @import("std"); +const builtin = @import("builtin"); + pub const OSC52 = @import("Clip/OSC52.zig"); -// pub const PBCopy = if (@import("builtin").os.tag == .macos) @import("Clip/OSC52.zig") else null; +// Ideally we would make else be `@compileError` but that +// messes up with the interface checking +pub const NSPasteboard = if (builtin.target.os.tag == .macos) @import("Clip/NSPasteboard.zig") else void; // I am not sure if this is super idiomatic Zig or if this is bad practice, but // by ensuring that all Clip backends implement the same methods, we are able @@ -16,8 +21,6 @@ fn assertFn(comptime T: type, comptime name: []const u8, comptime Sig: type) voi } test { - _ = @import("Clip/NSPasteBoard.zig"); - inline for (@typeInfo(@This()).@"struct".decls) |decl| { // Only grab types of structs const T = @field(@This(), decl.name); diff --git a/src/Clip/NSPasteboard.zig b/src/Clip/NSPasteboard.zig index 92c3403..acb895a 100644 --- a/src/Clip/NSPasteboard.zig +++ b/src/Clip/NSPasteboard.zig @@ -1,8 +1,44 @@ +/// NSPasteBoard Clipboard backend +/// Fulfills `Clip` interface +pub const NSPasteboard = @This(); + const std = @import("std"); extern fn sendPB(text: [*:0]const u8) void; extern fn initPB() void; +alloc: std.mem.Allocator, +io: std.Io, + +buffer: std.ArrayList(u8), + +pub fn init(alloc: std.mem.Allocator, io: std.Io, buf_size: usize) !NSPasteboard { + return .{ + .alloc = alloc, + .io = io, + .buffer = try std.ArrayList(u8).initCapacity(alloc, buf_size), + }; +} + +pub fn free(self: *NSPasteboard) void { + self.buffer.clearAndFree(self.alloc); +} + +pub fn writeCopyBuffer(self: *NSPasteboard, buf: []const u8) anyerror!void { + try self.buffer.appendSlice(self.alloc, buf); +} + +pub fn writePasteboard(self: *NSPasteboard) anyerror!void { + // Null Terminate string for C function Call + if (self.buffer.items[self.buffer.items.len - 1] == '\n') { + self.buffer.items[self.buffer.items.len - 1] = '\x00'; + } else { + try self.buffer.append(self.alloc, '\x00'); + } + initPB(); + sendPB(@ptrCast(self.buffer.items.ptr)); +} + test "objc bridge" { initPB(); sendPB("test_string"); @@ -15,4 +51,3 @@ test "objc bridge" { try std.testing.expectEqualStrings("test_string", std.mem.trim(u8, result.stdout, "\n")); } - diff --git a/src/main.zig b/src/main.zig index 4f45742..0725ae7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,4 +1,7 @@ const std = @import("std"); +const builtin = @import("builtin"); + +const os = builtin.target.os.tag; const File = std.Io.File; const Clip = @import("Clip.zig"); @@ -13,10 +16,30 @@ pub fn main(init: std.process.Init) !void { // We want to avoid doing as many allocations as possible so having raw // pages with it being larger should be totally fine - var clip = try Clip.OSC52.init(std.heap.page_allocator, init.io, std.heap.pageSize()); - defer clip.free(); + const alloc = std.heap.page_allocator; + const size = std.heap.pageSize(); - try run(io, &clip); + const is_ssh = init.environ_map.get("SSH_CONNECTION") != null or + init.environ_map.get("SSH_CLIENT") != null or + init.environ_map.get("SSH_TTY") != null; + + // This can be cleaned up + if (is_ssh) { + var clip = try Clip.OSC52.init(alloc, io, size); + defer clip.free(); + try run(io, &clip); + } else { + if (comptime os == .macos) { + var clip = try Clip.NSPasteboard.init(alloc, io, size); + defer clip.free(); + try run(io, &clip); + } else { + // No Linux Clipboards yet + var clip = try Clip.OSC52.init(alloc, io, size); + defer clip.free(); + try run(io, &clip); + } + } } pub fn run(io: std.Io, clip: anytype) !void {