Compare commits
3 commits
62c89c5bf2
...
eda3acce66
| Author | SHA1 | Date | |
|---|---|---|---|
| eda3acce66 | |||
| e92e3603fd | |||
| 805392ddca |
4 changed files with 121 additions and 23 deletions
13
build.zig
13
build.zig
|
|
@ -14,6 +14,19 @@ pub fn build(b: *std.Build) void {
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
const tests = b.addTest(.{
|
||||||
|
.name = "tpb",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
const run_tests = b.addRunArtifact(tests);
|
||||||
|
test_step.dependOn(&run_tests.step);
|
||||||
|
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Run the app");
|
||||||
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
|
|
||||||
29
src/Clip.zig
29
src/Clip.zig
|
|
@ -1 +1,30 @@
|
||||||
pub const OSC52 = @import("Clip/OSC52.zig");
|
pub const OSC52 = @import("Clip/OSC52.zig");
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// to use the `anytype` type and we are able to have an interface that is able
|
||||||
|
// to dispatch statically
|
||||||
|
|
||||||
|
/// Check if a function with the given name and type exists.
|
||||||
|
/// `@compileError` if not
|
||||||
|
fn assertFn(comptime T: type, comptime name: []const u8, comptime Sig: type) void {
|
||||||
|
if (!@import("std").meta.hasFn(T, name))
|
||||||
|
@compileError(@typeName(T) ++ " missing fn " ++ name);
|
||||||
|
if (@TypeOf(@field(T, name)) != Sig)
|
||||||
|
@compileError(@typeName(T) ++ "." ++ name ++ " must be " ++ @typeName(Sig));
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
inline for (@typeInfo(@This()).@"struct".decls) |decl| {
|
||||||
|
// Only grab types of structs
|
||||||
|
const T = @field(@This(), decl.name);
|
||||||
|
if (@TypeOf(T) != type) continue;
|
||||||
|
if (@typeInfo(T) != .@"struct") continue;
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
// === Interface Requirements ===
|
||||||
|
// ==============================
|
||||||
|
assertFn(T, "writeCopyBuffer", fn (*T, []const u8) anyerror!void);
|
||||||
|
assertFn(T, "writePasteboard", fn (*T) anyerror!void);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ base64buffer: std.ArrayList(u8),
|
||||||
leftover_bytes: [3]u8,
|
leftover_bytes: [3]u8,
|
||||||
leftover_count: u8,
|
leftover_count: u8,
|
||||||
|
|
||||||
const base64encoder = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, null);
|
const base64encoder = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '=');
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, io: Io, buf_size: usize) !OSC52 {
|
pub fn init(alloc: std.mem.Allocator, io: Io, buf_size: usize) !OSC52 {
|
||||||
return .{
|
return .{
|
||||||
|
|
@ -25,31 +25,34 @@ pub fn init(alloc: std.mem.Allocator, io: Io, buf_size: usize) !OSC52 {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeCopyBuffer(self: *OSC52, buf: []u8) !void {
|
pub fn free(self: *OSC52) void {
|
||||||
|
self.base64buffer.clearAndFree(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeCopyBuffer(self: *OSC52, buf: []const u8) anyerror!void {
|
||||||
if (buf.len == 0) return;
|
if (buf.len == 0) return;
|
||||||
|
|
||||||
// Start of buffer after dealing with leftover bytes
|
// Start of buffer after dealing with leftover bytes
|
||||||
const buf_idx = 3 - self.leftover_count;
|
const buf_idx = 3 - self.leftover_count;
|
||||||
|
|
||||||
if (buf.len + self.leftover_count < 3) {
|
// When we dont fill the leftover buffer, just copy data
|
||||||
const len = buf.len + self.leftover_count;
|
if (buf_idx > buf.len) {
|
||||||
@memcpy(self.leftover_bytes[self.leftover_count..len], buf[0..buf.len]);
|
const end = self.leftover_count + buf.len;
|
||||||
self.leftover_count = @intCast(len);
|
@memcpy(self.leftover_bytes[self.leftover_count..end], buf[0..buf.len]);
|
||||||
|
self.leftover_count = @intCast(end);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.leftover_count != 0) {
|
@memcpy(self.leftover_bytes[self.leftover_count..3], buf[0..buf_idx]);
|
||||||
@memcpy(self.leftover_bytes[self.leftover_count..3], buf[0..buf_idx]);
|
var temp: [5]u8 = undefined;
|
||||||
var temp: [5]u8 = undefined;
|
var s = OSC52.base64encoder.encode(&temp, &self.leftover_bytes);
|
||||||
const s = OSC52.base64encoder.encode(&temp, &self.leftover_bytes);
|
try self.base64buffer.appendSlice(self.alloc, s);
|
||||||
try self.base64buffer.appendSlice(self.alloc, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunkable_length = buf.len - ((buf.len - buf_idx) % 3);
|
const chunkable_length = buf.len - ((buf.len - buf_idx) % 3);
|
||||||
var chunker = std.mem.window(u8, buf[0..chunkable_length], 3, 3);
|
var chunker = std.mem.window(u8, buf[buf_idx..chunkable_length], 3, 3);
|
||||||
while (chunker.next()) |chunk| {
|
while (chunker.next()) |chunk| {
|
||||||
var temp: [5]u8 = undefined;
|
// var temp: [5]u8 = undefined;
|
||||||
const s = OSC52.base64encoder.encode(&temp, chunk);
|
s = OSC52.base64encoder.encode(&temp, chunk);
|
||||||
try self.base64buffer.appendSlice(self.alloc, s);
|
try self.base64buffer.appendSlice(self.alloc, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +62,14 @@ pub fn writeCopyBuffer(self: *OSC52, buf: []u8) !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writePasteboard(self: *OSC52) !void {
|
pub fn encodeRemaining(self: *OSC52) anyerror!void {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writePasteboard(self: *OSC52) anyerror!void {
|
||||||
|
try self.encodeRemaining();
|
||||||
|
|
||||||
// https://ghostty.org/docs/vt/osc/52
|
// https://ghostty.org/docs/vt/osc/52
|
||||||
const osc_header = &[_]u8{
|
const osc_header = &[_]u8{
|
||||||
|
|
@ -76,14 +86,51 @@ pub fn writePasteboard(self: *OSC52) !void {
|
||||||
0x5c, // '\'
|
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
|
// Write data to stdout
|
||||||
const stdout = Io.File.stdout();
|
const stdout = Io.File.stdout();
|
||||||
try stdout.writeStreamingAll(self.io, osc_header);
|
try stdout.writeStreamingAll(self.io, osc_header);
|
||||||
try stdout.writeStreamingAll(self.io, self.base64buffer.items);
|
try stdout.writeStreamingAll(self.io, self.base64buffer.items);
|
||||||
try stdout.writeStreamingAll(self.io, osc_footer);
|
try stdout.writeStreamingAll(self.io, osc_footer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "basic base64 encode" {
|
||||||
|
var osc = try OSC52.init(std.testing.allocator, std.testing.io, 1024);
|
||||||
|
defer osc.free();
|
||||||
|
try osc.writeCopyBuffer("part1");
|
||||||
|
try osc.encodeRemaining();
|
||||||
|
try std.testing.expectEqualSlices(u8, "cGFydDE=", osc.base64buffer.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "multi part base64 encode" {
|
||||||
|
var osc = try OSC52.init(std.testing.allocator, std.testing.io, 1024);
|
||||||
|
defer osc.free();
|
||||||
|
try osc.writeCopyBuffer("part1");
|
||||||
|
try osc.writeCopyBuffer("part2");
|
||||||
|
try osc.encodeRemaining();
|
||||||
|
try std.testing.expectEqualSlices(u8, "cGFydDFwYXJ0Mg==", osc.base64buffer.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "tiny buffers base64 encode" {
|
||||||
|
var osc = try OSC52.init(std.testing.allocator, std.testing.io, 1024);
|
||||||
|
defer osc.free();
|
||||||
|
try osc.writeCopyBuffer("part1");
|
||||||
|
try osc.writeCopyBuffer("part2");
|
||||||
|
try osc.writeCopyBuffer("1");
|
||||||
|
try osc.writeCopyBuffer("2");
|
||||||
|
try osc.writeCopyBuffer("3");
|
||||||
|
try osc.writeCopyBuffer("45");
|
||||||
|
try osc.writeCopyBuffer("6");
|
||||||
|
try osc.writeCopyBuffer("78");
|
||||||
|
try osc.writeCopyBuffer("90fin");
|
||||||
|
try osc.encodeRemaining();
|
||||||
|
try std.testing.expectEqualSlices(u8, "cGFydDFwYXJ0MjEyMzQ1Njc4OTBmaW4=", osc.base64buffer.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "big buffers base64 encode" {
|
||||||
|
var osc = try OSC52.init(std.testing.allocator, std.testing.io, 1024);
|
||||||
|
defer osc.free();
|
||||||
|
try osc.writeCopyBuffer("part1part1part1part1part1part1part1part1part1|");
|
||||||
|
try osc.writeCopyBuffer("part2part2part2part2part2part2part2part2part2");
|
||||||
|
try osc.encodeRemaining();
|
||||||
|
try std.testing.expectEqualSlices(u8, "cGFydDFwYXJ0MXBhcnQxcGFydDFwYXJ0MXBhcnQxcGFydDFwYXJ0MXBhcnQxfHBhcnQycGFydDJwYXJ0MnBhcnQycGFydDJwYXJ0MnBhcnQycGFydDJwYXJ0Mg==", osc.base64buffer.items);
|
||||||
|
}
|
||||||
|
|
|
||||||
13
src/main.zig
13
src/main.zig
|
|
@ -11,11 +11,16 @@ pub fn main(init: std.process.Init) !void {
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
std.debug.print("\x1b[0;32mCopying the following to Pasteboard\x1b[0m\n", .{});
|
|
||||||
|
|
||||||
// We want to avoid doing as many allocations as possible so having raw
|
// We want to avoid doing as many allocations as possible so having raw
|
||||||
// pages with it being larger should be totally fine
|
// pages with it being larger should be totally fine
|
||||||
var clip = try Clip.OSC52.init(std.heap.page_allocator, init.io, std.heap.pageSize());
|
var clip = try Clip.OSC52.init(std.heap.page_allocator, init.io, std.heap.pageSize());
|
||||||
|
defer clip.free();
|
||||||
|
|
||||||
|
run(io, clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(io: *std.Io, clip: anytype) !void {
|
||||||
|
std.debug.print("\x1b[0;32mCopying the following to Pasteboard\x1b[0m\n", .{});
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -38,3 +43,7 @@ pub fn main(init: std.process.Init) !void {
|
||||||
|
|
||||||
std.debug.print("\x1b[0;32mCopied To Pasteboard!\x1b[0m\n", .{});
|
std.debug.print("\x1b[0;32mCopied To Pasteboard!\x1b[0m\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = @import("Clip.zig");
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue