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

This commit is contained in:
Grace Yoder 2026-06-30 10:02:29 -06:00
parent 565174c4b8
commit 9a377fa209
Signed by: grace
SSH key fingerprint: SHA256:oG9v0ZlohsoMAg083HAwBGOcaPLF3nIuOlAW4MtASMo
3 changed files with 68 additions and 7 deletions

View file

@ -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);

View file

@ -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"));
}

View file

@ -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();
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 {