home
tpb

repository View

feat(NSPasteboard): uses backend when not ssh and on MacOS

change ID
txqmlmtwqsptqryuxyxxtztvvrknrltm
git commit
9a377fa209cfecf53343247d197dc8578d6c2a1d
author
Grace Yoder <grace_yoder@outlook.com> - 2026-06-30T10:02:29-06:00
committer
Grace Yoder <grace_yoder@outlook.com> - 2026-06-30T16:33:26-06:00
parents
lopkzsklxzok
refs
signature
Present (not validated)
feat(NSPasteboard): uses backend when not ssh and on MacOS

tree

type name size
file .crow/build.yml 2096 bytes
file .crow/release.yml 566 bytes
file .gitignore 354 bytes
file LICENSE 1073 bytes
file README.md 632 bytes
file build.zig 1579 bytes
file build.zig.zon 247 bytes
file src/Clip/NSPBBridge.m 382 bytes
file src/Clip/NSPasteboard.zig 1456 bytes
file src/Clip/OSC52.zig 4611 bytes
file src/Clip.zig 1506 bytes
file src/main.zig 2190 bytes

diff

src/Clip/NSPasteboard.zig

modified (file → file)

--- 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 @@
 
     try std.testing.expectEqualStrings("test_string", std.mem.trim(u8, result.stdout, "\n"));
 }
-

src/Clip.zig

modified (file → file)

--- 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 @@
 }
 
 test {
-    _ = @import("Clip/NSPasteBoard.zig");
-
     inline for (@typeInfo(@This()).@"struct".decls) |decl| {
         // Only grab types of structs
         const T = @field(@This(), decl.name);

src/main.zig

modified (file → file)

--- 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 @@
 
     // 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 {