enable uploads

This commit is contained in:
Emil Lerch 2022-01-05 10:24:14 -08:00
parent eb8058c930
commit b6a03c8634
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
8 changed files with 586 additions and 31 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ zig-out
libx11-libX11-1.7.2
libxfixes-libXfixes-5.0.3
xorgproto-xorgproto-2021.5
.clippy
libs/

206
GitRepoStep.zig Normal file
View File

@ -0,0 +1,206 @@
//! Publish Date: 2021_10_17
//! This file is hosted at github.com/marler8997/zig-build-repos and is meant to be copied
//! to projects that use it.
const std = @import("std");
const GitRepoStep = @This();
pub const ShaCheck = enum {
none,
warn,
err,
pub fn reportFail(self: ShaCheck, comptime fmt: []const u8, args: anytype) void {
switch (self) {
.none => unreachable,
.warn => std.log.warn(fmt, args),
.err => {
std.log.err(fmt, args);
std.os.exit(0xff);
},
}
}
};
step: std.build.Step,
builder: *std.build.Builder,
url: []const u8,
name: []const u8,
branch: ?[]const u8 = null,
sha: []const u8,
path: []const u8 = null,
sha_check: ShaCheck = .warn,
fetch_enabled: bool,
var cached_default_fetch_option: ?bool = null;
pub fn defaultFetchOption(b: *std.build.Builder) bool {
if (cached_default_fetch_option) |_| {} else {
cached_default_fetch_option = if (b.option(bool, "fetch", "automatically fetch network resources")) |o| o else false;
}
return cached_default_fetch_option.?;
}
pub fn create(b: *std.build.Builder, opt: struct {
url: []const u8,
branch: ?[]const u8 = null,
sha: []const u8,
path: ?[]const u8 = null,
sha_check: ShaCheck = .warn,
fetch_enabled: ?bool = null,
}) *GitRepoStep {
var result = b.allocator.create(GitRepoStep) catch @panic("memory");
const name = std.fs.path.basename(opt.url);
result.* = GitRepoStep{
.step = std.build.Step.init(.custom, "clone a git repository", b.allocator, make),
.builder = b,
.url = opt.url,
.name = name,
.branch = opt.branch,
.sha = opt.sha,
.path = if (opt.path) |p| (b.allocator.dupe(u8, p) catch @panic("memory")) else (std.fs.path.resolve(b.allocator, &[_][]const u8{
b.build_root,
"libs",
name,
})) catch @panic("memory"),
.sha_check = opt.sha_check,
.fetch_enabled = if (opt.fetch_enabled) |fe| fe else defaultFetchOption(b),
};
return result;
}
// TODO: this should be included in std.build, it helps find bugs in build files
fn hasDependency(step: *const std.build.Step, dep_candidate: *const std.build.Step) bool {
for (step.dependencies.items) |dep| {
// TODO: should probably use step.loop_flag to prevent infinite recursion
// when a circular reference is encountered, or maybe keep track of
// the steps encounterd with a hash set
if (dep == dep_candidate or hasDependency(dep, dep_candidate))
return true;
}
return false;
}
fn make(step: *std.build.Step) !void {
const self = @fieldParentPtr(GitRepoStep, "step", step);
std.fs.accessAbsolute(self.path, std.fs.File.OpenFlags{ .read = true }) catch {
const branch_args = if (self.branch) |b| &[2][]const u8{ " -b ", b } else &[2][]const u8{ "", "" };
if (!self.fetch_enabled) {
std.debug.print("Error: git repository '{s}' does not exist\n", .{self.path});
std.debug.print(" Use -Dfetch to download it automatically, or run the following to clone it:\n", .{});
std.debug.print(" git clone {s}{s}{s} {s} && git -C {3s} checkout {s} -b for_ziget\n", .{ self.url, branch_args[0], branch_args[1], self.path, self.sha });
std.os.exit(1);
}
{
var args = std.ArrayList([]const u8).init(self.builder.allocator);
defer args.deinit();
try args.append("git");
try args.append("clone");
try args.append("--recurse-submodules");
try args.append(self.url);
// TODO: clone it to a temporary location in case of failure
// also, remove that temporary location before running
try args.append(self.path);
if (self.branch) |branch| {
try args.append("-b");
try args.append(branch);
}
try run(self.builder, args.items);
}
try run(self.builder, &[_][]const u8{
"git",
"-C",
self.path,
"checkout",
self.sha,
"-b",
"fordep",
});
};
try self.checkSha();
}
fn checkSha(self: GitRepoStep) !void {
if (self.sha_check == .none)
return;
const result: union(enum) { failed: anyerror, output: []const u8 } = blk: {
const result = std.ChildProcess.exec(.{
.allocator = self.builder.allocator,
.argv = &[_][]const u8{
"git",
"-C",
self.path,
"rev-parse",
"HEAD",
},
.cwd = self.builder.build_root,
.env_map = self.builder.env_map,
}) catch |e| break :blk .{ .failed = e };
try std.io.getStdErr().writer().writeAll(result.stderr);
switch (result.term) {
.Exited => |code| {
if (code == 0) break :blk .{ .output = result.stdout };
break :blk .{ .failed = error.GitProcessNonZeroExit };
},
.Signal => break :blk .{ .failed = error.GitProcessFailedWithSignal },
.Stopped => break :blk .{ .failed = error.GitProcessWasStopped },
.Unknown => break :blk .{ .failed = error.GitProcessFailed },
}
};
switch (result) {
.failed => |err| {
return self.sha_check.reportFail("failed to retreive sha for repository '{s}': {s}", .{ self.name, @errorName(err) });
},
.output => |output| {
if (!std.mem.eql(u8, std.mem.trimRight(u8, output, "\n\r"), self.sha)) {
return self.sha_check.reportFail("repository '{s}' sha does not match\nexpected: {s}\nactual : {s}\n", .{ self.name, self.sha, output });
}
},
}
}
fn run(builder: *std.build.Builder, argv: []const []const u8) !void {
{
var msg = std.ArrayList(u8).init(builder.allocator);
defer msg.deinit();
const writer = msg.writer();
var prefix: []const u8 = "";
for (argv) |arg| {
try writer.print("{s}\"{s}\"", .{ prefix, arg });
prefix = " ";
}
std.log.info("[RUN] {s}", .{msg.items});
}
const child = try std.ChildProcess.init(argv, builder.allocator);
defer child.deinit();
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
child.cwd = builder.build_root;
child.env_map = builder.env_map;
try child.spawn();
const result = try child.wait();
switch (result) {
.Exited => |code| if (code != 0) {
std.log.err("git clone failed with exit code {}", .{code});
std.os.exit(0xff);
},
else => {
std.log.err("git clone failed with: {}", .{result});
std.os.exit(0xff);
},
}
}
// Get's the repository path and also verifies that the step requesting the path
// is dependent on this step.
pub fn getPath(self: *const GitRepoStep, who_wants_to_know: *const std.build.Step) []const u8 {
if (!hasDependency(who_wants_to_know, &self.step))
@panic("a step called GitRepoStep.getPath but has not added it as a dependency");
return self.path;
}

View File

@ -1,6 +1,12 @@
const std = @import("std");
const GitRepoStep = @import("GitRepoStep.zig");
pub fn build(b: *std.build.Builder) void {
const zfetch_repo = GitRepoStep.create(b, .{
.url = "https://github.com/truemedian/zfetch",
// .branch = "0.1.10", // branch also takes tags. Tag 0.1.10 isn't quite new enough
.sha = "271cab5da4d12c8f08e67aa0cd5268da100e52f1",
});
// 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
@ -12,8 +18,8 @@ pub fn build(b: *std.build.Builder) void {
const mode = b.standardReleaseOptions();
const exe = switch (target.getOs().tag) {
.linux => b.addExecutable("clipboard", "src/main-linux.zig"),
.windows => b.addExecutable("clipboard", "src/main-windows.zig"),
.linux => b.addExecutable("clipboard-upload", "src/main-linux.zig"),
.windows => b.addExecutable("clipboard-upload", "src/main-windows.zig"),
else => std.os.exit(1),
};
@ -48,6 +54,28 @@ pub fn build(b: *std.build.Builder) void {
exe.linkSystemLibrary("kernel32");
exe.linkSystemLibrary("shell32");
}
const copy_deps = std.build.RunStep.create(b, "copy zfetch deps");
copy_deps.addArgs(&[_][]const u8{
"cp",
"zfetch_deps.zig",
"libs/zfetch/deps.zig",
});
copy_deps.step.dependOn(&zfetch_repo.step);
// exe.step.dependOn(&zfetch_repo.step);
exe.step.dependOn(&copy_deps.step);
// This import won't work unless we're already cloned. The way around
// this is to have a multi-stage build process, but that's a lot of work.
// Instead, I've copied the addPackage and tweaked it for the build prefix
// so we'll have to keep that in sync with upstream
// const zfetch = @import("libs/zfetch/build.zig");
const zpkg = getZfetchPackage(b, "libs/zfetch") catch unreachable;
exe.addPackage(zpkg);
exe.addPackage(.{
.name = "iguanaTLS",
.path = .{ .path = "libs/zfetch/libs/iguanaTLS/src/main.zig" },
});
exe.install();
const run_cmd = exe.run();
@ -59,3 +87,38 @@ pub fn build(b: *std.build.Builder) void {
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
fn getDependency(comptime lib_prefix: []const u8, comptime name: []const u8, comptime root: []const u8) !std.build.Pkg {
const path = lib_prefix ++ "/libs/" ++ name ++ "/" ++ root;
// We don't actually care if the dependency has been checked out, as
// GitRepoStep will handle that for us
// Make sure that the dependency has been checked out.
// std.fs.cwd().access(path, .{}) catch |err| switch (err) {
// error.FileNotFound => {
// std.log.err("zfetch: dependency '{s}' not checked out", .{name});
//
// return err;
// },
// else => return err,
// };
return std.build.Pkg{
.name = name,
.path = .{ .path = path },
};
}
pub fn getZfetchPackage(b: *std.build.Builder, comptime lib_prefix: []const u8) !std.build.Pkg {
var dependencies = b.allocator.alloc(std.build.Pkg, 4) catch unreachable;
dependencies[0] = try getDependency(lib_prefix, "iguanaTLS", "src/main.zig");
dependencies[1] = try getDependency(lib_prefix, "network", "network.zig");
dependencies[2] = try getDependency(lib_prefix, "uri", "uri.zig");
dependencies[3] = try getDependency(lib_prefix, "hzzp", "src/main.zig");
return std.build.Pkg{
.name = "zfetch",
.path = .{ .path = lib_prefix ++ "/src/main.zig" },
.dependencies = dependencies,
};
}

View File

@ -1,28 +1,120 @@
const std = @import("std");
const zfetch = @import("zfetch");
const crypt = @import("crypt.zig");
// const tls = @import("iguanaTLS");
pub fn clipboardChanged(allocator: std.mem.Allocator, contents: []const u8) !void {
var arena_allocator = std.heap.ArenaAllocator.init(allocator);
// NGINX config isn't allowing ECDHE-RSA-CHACHA20-POLY1305 on TLS 1.2
// I need:
//
// nginx to allow that
// iguanaTLS to support tls 1.3
// iguanaTLS to support something else, like ECDHE-ECDSA-CHACHA20-POLY1305
// In the meantime, I've allowed use of http, since we're encrypting anyway
const clipboard_url = "http://clippy.lerch.org/work2";
// const clipboard_url = "https://httpbin.org/post";
const Self = @This();
key: *[crypt.key_size]u8,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) !Self {
const key = try getKey(allocator);
return Self{
.allocator = allocator,
.key = key,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.key);
}
pub fn clipboardChanged(self: *Self, contents: []const u8) !void {
var arena_allocator = std.heap.ArenaAllocator.init(self.allocator);
defer arena_allocator.deinit();
const aa = arena_allocator.allocator();
const clip_contents = try aa.dupe(u8, contents);
defer aa.free(clip_contents);
// Dear lord, change this
const key = [_]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
var buf: []u8 = try aa.alloc(u8, contents.len);
defer aa.free(buf);
std.mem.copy(u8, buf, contents);
const encrypted = crypt.encryptWithKey(aa, self.key.*, buf) catch |e| {
std.log.err("Could not encrypt clipboard contents: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
return;
};
const in = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
var out: [16]u8 = undefined;
var ctx = std.crypto.core.aes.Aes256.initEnc(key);
ctx.encrypt(out[0..], in[0..]);
// Do the work. We are assumed here to
const stdout = std.io.getStdOut().writer();
const writer = stdout;
try writer.print("{s}", .{clip_contents});
defer aa.free(encrypted);
post(aa, encrypted) catch |e| {
std.log.err("error posting clipboard contents {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
return;
};
}
fn getKey(allocator: std.mem.Allocator) !*[crypt.key_size]u8 {
const passfile = std.fs.cwd().openFile(".clippy", .{}) catch |e| {
if (e == error.FileNotFound) {
std.log.err("Could not find '.clippy' file. Please add a password to this file", .{});
}
return e;
};
defer passfile.close();
const pass = try passfile.readToEndAlloc(allocator, std.math.maxInt(usize));
defer allocator.free(pass);
const tmp_key = try crypt.keyFromPassword(allocator, pass, ""); // reuse key - this is slow
return tmp_key;
}
fn post(allocator: std.mem.Allocator, data: []const u8) !void {
// TODO: Windows
// var cert_reader = std.io.fixedBufferStream(
// @embedFile("/etc/ssl/certs/ca-certificates.crt"),
// ).reader();
// const trust = try tls.x509.CertificateChain.from_pem(allocator, cert_reader);
try zfetch.init();
defer zfetch.deinit();
var headers = zfetch.Headers.init(allocator);
defer headers.deinit();
// try headers.appendValue("Accept", "application/json");
// try headers.appendValue("Content-Type", "text/plain");
var req = try zfetch.Request.init(allocator, clipboard_url, null);
defer req.deinit();
try req.do(.PUT, headers, data);
// Printf debugging
// const stdout = std.io.getStdOut().writer();
// try stdout.print("status: {d} {s}\n", .{ req.status.code, req.status.reason });
// try stdout.print("headers:\n", .{});
// for (req.headers.list.items) |header| {
// try stdout.print(" {s}: {s}\n", .{ header.name, header.value });
// }
// try stdout.print("body:\n", .{});
//
// const reader = req.reader();
//
// var buf: [1024]u8 = undefined;
// while (true) {
// const read = try reader.read(&buf);
// if (read == 0) break;
//
// try stdout.writeAll(buf[0..read]);
// }
}
test "full integration" {
var allocator = std.testing.allocator;
try clipboardChanged(allocator, "hello world");
var watcher = init(allocator);
defer watcher.deinit();
try watcher.clipboardChanged("hello world");
}

171
src/crypt.zig Normal file
View File

@ -0,0 +1,171 @@
const std = @import("std");
pub const key_size = 256 / 8;
const test_key = [_]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
/// Get key from password. The symmetric key will be derived using PBKDF2
/// Salt can be anything, including empty if a salt is not to be used.
/// Function execution is slow by design. Memory allocated for key will be
/// owned by the caller.
pub fn keyFromPassword(allocator: std.mem.Allocator, password: []const u8, salt: []const u8) !*[key_size]u8 {
var key = try allocator.alloc(u8, key_size);
const rounds = 4096;
try std.crypto.pwhash.pbkdf2(key, password, salt, rounds, std.crypto.auth.hmac.HmacSha1);
return key[0..key_size];
}
/// Symmetric encryption with a key. Key size is defined as a public constant.
/// Key can be provided directly or through the use of the keyFromPassword
/// function.
///
/// Encryption currently AES 256, though this should be considered an implementation
/// detail. Padding performed with PKCS#7.
///
/// Example:
///
/// const str = "0123456789ABCDEFG";
/// var buf: [str.len]u8 = undefined;
/// var data = std.mem.copy(u8, buf, str);
/// var key = try keyFromPassword(allocator, "foo", ""); // reuse key - this is slow
/// defer allocator.free(key);
/// const encrypted = try encryptWithKey(allocator, key.*, data);
///
pub fn encryptWithKey(allocator: std.mem.Allocator, key: [key_size]u8, data: []u8) ![]const u8 {
const block_size = 16;
var in: *[block_size]u8 = undefined;
var out: [block_size]u8 = undefined;
var ctx = std.crypto.core.aes.Aes256.initEnc(key);
const total_blocks = data.len / block_size;
var current_block: usize = 0;
var encrypted = try std.ArrayList(u8).initCapacity(allocator, block_size * (total_blocks + 1));
defer encrypted.deinit();
while (current_block < total_blocks) {
in = @ptrCast(*[block_size]u8, data[(current_block * block_size)..(((current_block + 1) * block_size) - 1)]);
ctx.encrypt(out[0..], in.*[0..]);
encrypted.appendSliceAssumeCapacity(out[0..]);
current_block += 1;
}
// deal with final block, PKCS#7 padding
{
const padding: u8 = @intCast(u8, block_size - (data.len % block_size));
var inx: u8 = 0;
for (data[(total_blocks * block_size)..]) |b| {
in[inx] = b;
inx += 1;
}
while (inx < out.len) {
in[inx] = padding;
inx += 1;
}
ctx.encrypt(out[0..], in[0..]);
encrypted.appendSliceAssumeCapacity(out[0..]);
}
return encrypted.toOwnedSlice();
}
/// Symmetric decryption with a key. Key size is defined as a public constant.
/// Key can be provided directly or through the use of the keyFromPassword
/// function.
///
/// Decryption currently AES 256, though this should be considered an implementation
/// detail. Padding assumed to be PKCS#7.
///
/// Example:
///
/// const str = "0123456789ABCDEFG";
/// var buf: [str.len]u8 = undefined;
/// var data = std.mem.copy(u8, buf, str);
/// var key = try keyFromPassword(allocator, "foo", ""); // reuse key - this is slow
/// defer allocator.free(key);
/// const encrypted = try encryptWithKey(allocator, key.*, data);
///
pub fn decryptWithKey(allocator: std.mem.Allocator, key: [key_size]u8, data: []const u8) ![]const u8 {
const block_size = 16;
var in: *const [block_size]u8 = undefined;
var out: [block_size]u8 = undefined;
var ctx = std.crypto.core.aes.Aes256.initDec(key);
const total_blocks = data.len / block_size;
var current_block: usize = 0;
var decrypted = try std.ArrayList(u8).initCapacity(allocator, block_size * (total_blocks - 1));
defer decrypted.deinit();
while (current_block < total_blocks - 1) {
in = @ptrCast(*const [block_size]u8, data[(current_block * block_size)..(((current_block + 1) * block_size) - 1)]);
ctx.decrypt(out[0..], in.*[0..]);
decrypted.appendSliceAssumeCapacity(out[0..]);
current_block += 1;
}
// deal with final block, PKCS#7 padding
in = @ptrCast(*const [block_size]u8, data[(current_block * block_size)..(((current_block + 1) * block_size) - 1)]);
ctx.decrypt(out[0..], in.*[0..]);
std.debug.assert(out[block_size - 1] <= block_size);
try decrypted.appendSlice(out[0 .. block_size - out[block_size - 1]]);
return decrypted.toOwnedSlice();
}
test "Appendix C.3" {
var in = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
// const exp_out = [_]u8{ 0x8e, 0xa2, 0xb7, 0xca, 0x51, 0x67, 0x45, 0xbf, 0xea, 0xfc, 0x49, 0x90, 0x4b, 0x49, 0x60, 0x89 };
var allocator = std.testing.allocator;
const encrypted = try encryptWithKey(allocator, test_key, in[0..]);
defer allocator.free(encrypted);
const size: usize = 32;
try std.testing.expectEqual(size, encrypted.len);
}
test "correct size, even block" {
const str = "0123456789ABCDEF";
var allocator = std.testing.allocator;
var buf: [str.len]u8 = undefined;
var data = try std.fmt.bufPrint(&buf, str, .{});
const encrypted = try encryptWithKey(allocator, test_key, data);
defer allocator.free(encrypted);
const size: usize = 32;
try std.testing.expectEqual(size, encrypted.len);
}
test "round trip, even block" {
const str = "0123456789ABCDEF";
var allocator = std.testing.allocator;
var buf: [str.len]u8 = undefined;
var data = try std.fmt.bufPrint(&buf, str, .{});
const encrypted = try encryptWithKey(allocator, test_key, data);
defer allocator.free(encrypted);
const decrypted = try decryptWithKey(allocator, test_key, encrypted);
defer allocator.free(decrypted);
try std.testing.expectEqual(str.len, decrypted.len);
try std.testing.expectEqualStrings(str, decrypted);
}
test "round trip, uneven block" {
const str = "0123456789ABCDEFG";
var allocator = std.testing.allocator;
var buf: [str.len]u8 = undefined;
var data = try std.fmt.bufPrint(&buf, str, .{});
const encrypted = try encryptWithKey(allocator, test_key, data);
defer allocator.free(encrypted);
const decrypted = try decryptWithKey(allocator, test_key, encrypted);
defer allocator.free(decrypted);
try std.testing.expectEqualStrings(str, decrypted);
}
test "round trip, uneven block, using password" {
const str = "0123456789ABCDEFG";
var allocator = std.testing.allocator;
var buf: [str.len]u8 = undefined;
var data = try std.fmt.bufPrint(&buf, str, .{});
var key = try keyFromPassword(allocator, "foo", "");
defer allocator.free(key);
const encrypted = try encryptWithKey(allocator, key.*, data);
defer allocator.free(encrypted);
const decrypted = try decryptWithKey(allocator, key.*, encrypted);
defer allocator.free(decrypted);
try std.testing.expectEqualStrings(str, decrypted);
}

View File

@ -1,5 +1,5 @@
const std = @import("std");
const clip = @import("clipboard.zig");
const Clipboard = @import("clipboard.zig");
const c = @cImport({
@cInclude("limits.h");
@ -22,11 +22,13 @@ pub fn main() !u8 {
break;
}
}
clipboardAction(allocator, watch) catch return 1;
var clip = try Clipboard.init(allocator);
defer clip.deinit();
clipboardAction(allocator, watch, &clip) catch return 1;
return 0;
}
pub fn clipboardAction(allocator: std.mem.Allocator, watch: bool) !void {
fn clipboardAction(allocator: std.mem.Allocator, watch: bool, clip: *Clipboard) !void {
var display: *c.Display = c.XOpenDisplay(null).?;
defer _ = c.XCloseDisplay(display);
const default_screen = c.XDefaultScreen(display);
@ -34,13 +36,13 @@ pub fn clipboardAction(allocator: std.mem.Allocator, watch: bool) !void {
var window = c.XCreateSimpleWindow(display, c.XDefaultRootWindow(display), 0, 0, 1, 1, 0, color, color);
defer _ = c.XDestroyWindow(display, window);
// Watch will not return
if (watch) try watchClip(allocator, display, window, "CLIPBOARD");
var result = (try printSelection(allocator, display, window, "CLIPBOARD", "UTF8_STRING")) or
(try printSelection(allocator, display, window, "CLIPBOARD", "STRING"));
if (watch) try watchClip(allocator, display, window, "CLIPBOARD", clip);
var result = (try printSelection(allocator, display, window, "CLIPBOARD", "UTF8_STRING", clip)) or
(try printSelection(allocator, display, window, "CLIPBOARD", "STRING", clip));
if (!result) return error.ClipboardActionFailed;
}
fn watchClip(allocator: std.mem.Allocator, display: *c.Display, window: c.Window, bufname: [*c]const u8) !void {
fn watchClip(allocator: std.mem.Allocator, display: *c.Display, window: c.Window, bufname: [*c]const u8, clip: *Clipboard) !void {
var event_base: c_int = undefined;
var error_base: c_int = undefined;
var event: c.XEvent = undefined;
@ -55,13 +57,20 @@ fn watchClip(allocator: std.mem.Allocator, display: *c.Display, window: c.Window
if (event.type == event_base + c.XFixesSelectionNotify and
event.xselection.property == bufid)
{
if (!try printSelection(allocator, display, window, bufname, "UTF8_STRING"))
_ = try printSelection(allocator, display, window, bufname, "STRING");
if (!try printSelection(allocator, display, window, bufname, "UTF8_STRING", clip))
_ = try printSelection(allocator, display, window, bufname, "STRING", clip);
}
}
}
fn printSelection(allocator: std.mem.Allocator, display: *c.Display, window: c.Window, bufname: [*c]const u8, fmtname: [*c]const u8) !bool {
fn printSelection(
allocator: std.mem.Allocator,
display: *c.Display,
window: c.Window,
bufname: [*c]const u8,
fmtname: [*c]const u8,
clip: *Clipboard,
) !bool {
var ressize: c_ulong = undefined;
var restail: c_ulong = undefined;
var resbits: c_int = undefined;
@ -82,7 +91,7 @@ fn printSelection(allocator: std.mem.Allocator, display: *c.Display, window: c.W
_ = 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)
_ = async clip.clipboardChanged(allocator, std.mem.span(result)); // TODO: Ensure we don't start queueing these things up
_ = async clip.clipboardChanged(std.mem.span(result)); // TODO: Ensure we don't start queueing these things up
if (fmtid == incrid) {
ressize = 1;
@ -101,7 +110,7 @@ fn printSelection(allocator: std.mem.Allocator, display: *c.Display, window: c.W
//defer _ = c.XFree(result); // Creates double free error, but not sure why
try buffer.appendSlice(try std.fmt.allocPrint(local_allocator, "{s}", .{result}));
}
_ = async clip.clipboardChanged(allocator, buffer.items);
_ = async clip.clipboardChanged(buffer.items);
}
return true;
} else // request failed, e.g. owner can't convert to the target format

View File

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const clip = @import("clipboard.zig");
const Clipboard = @import("clipboard.zig");
const w = std.os.windows;
extern "user32" fn MessageBoxA(hWnd: ?w.HANDLE, lpText: ?w.LPCSTR, lpCaption: ?w.LPCSTR, uType: w.UINT) callconv(w.WINAPI) c_int;
@ -901,6 +901,7 @@ var window_state: union(enum(u8)) {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = undefined;
var contents: [:0]const u8 = "";
var free_contents = false;
var clip: *Clipboard = undefined;
pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCmdLine: w.PWSTR, nCmdShow: w.INT) w.INT {
_ = hPrevInstance;
@ -910,6 +911,16 @@ pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCm
gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var clippy = Clipboard.init(gpa.allocator()) catch |e| {
std.log.err("Could not initialize common clipboard action. {}. Exiting", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
return 1;
};
clip = &clippy;
defer clippy.deinit();
h_instance = hInstance;
// Register the window class.
@ -1195,5 +1206,5 @@ fn UpdateContents(hwnd: w.HWND) void {
break :blk "";
};
}
_ = async clip.clipboardChanged(allocator, contents); // Notify common handler
_ = async clip.clipboardChanged(contents); // Notify common handler
}

1
zfetch_deps.zig Normal file
View File

@ -0,0 +1 @@
const use_submodules = 1;