diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c82b07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +zig-cache +zig-out diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..b6f5fab --- /dev/null +++ b/build.zig @@ -0,0 +1,37 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("clipboard", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + if (std.builtin.os.tag == .linux) { + exe.linkLibC(); + exe.addIncludeDir("/usr/include/"); + exe.linkSystemLibrary("X11"); + } + if (std.builtin.os.tag == .windows) { + // exe.linkLibC(); + // exe.addIncludeDir("/usr/include/"); + // exe.linkSystemLibrary("X11"); + } + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..9eaa0fe --- /dev/null +++ b/src/main.zig @@ -0,0 +1,61 @@ +const std = @import("std"); +const c = @cImport({ + @cInclude("limits.h"); + @cInclude("X11/Xlib.h"); +}); + +fn printSelection(display: *c.Display, window: c.Window, bufname: [*c]const u8, fmtname: [*c]const u8, writer: anytype) !bool { + var ressize: c_ulong = undefined; + var restail: c_ulong = undefined; + var resbits: c_int = undefined; + var bufid = c.XInternAtom(display, bufname, c.False); + var fmtid = c.XInternAtom(display, fmtname, c.False); + var propid = c.XInternAtom(display, "XSEL_DATA", c.False); + var incrid = c.XInternAtom(display, "INCR", c.False); + var event: c.XEvent = undefined; + + _ = c.XSelectInput(display, window, c.PropertyChangeMask); + _ = c.XConvertSelection(display, bufid, fmtid, propid, window, c.CurrentTime); + _ = c.XNextEvent(display, &event); + while (event.type != c.SelectionNotify or event.xselection.selection != bufid) + _ = c.XNextEvent(display, &event); + + if (event.xselection.property > 0) { + var result: [*c]u8 = undefined; + _ = c.XGetWindowProperty(display, window, propid, 0, c.LONG_MAX / 4, c.True, c.AnyPropertyType, &fmtid, &resbits, &ressize, &restail, &result); + defer _ = c.XFree(result); + if (fmtid != incrid) + try writer.print("{s}", .{result}); + + if (fmtid == incrid) { + ressize = 1; + while (ressize > 0) { + _ = c.XNextEvent(display, &event); + while (event.type != c.PropertyNotify or event.xproperty.atom != propid or event.xproperty.state != c.PropertyNewValue) { + _ = c.XNextEvent(display, &event); + } + + _ = c.XGetWindowProperty(display, window, propid, 0, c.LONG_MAX / 4, c.True, c.AnyPropertyType, &fmtid, &resbits, &ressize, &restail, &result); + defer _ = c.XFree(result); + try writer.print("{s}", .{result}); + } + } + return true; + } else // request failed, e.g. owner can't convert to the target format + return false; +} + +pub fn main() !u8 { + const stdout = std.io.getStdOut().writer(); + var display: *c.Display = c.XOpenDisplay(null).?; + defer _ = c.XCloseDisplay(display); + const default_screen = c.XDefaultScreen(display); + var color = c.XBlackPixel(display, default_screen); + var window = c.XCreateSimpleWindow(display, c.XDefaultRootWindow(display), 0, 0, 1, 1, 0, color, color); + defer _ = c.XDestroyWindow(display, window); + var result = (try printSelection(display, window, "CLIPBOARD", "UTF8_STRING", stdout)) or + (try printSelection(display, window, "CLIPBOARD", "STRING", stdout)); + if (result) + return 0; + return 1; +}