aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--LICENSE19
-rw-r--r--README.md3
-rw-r--r--build.zig26
-rw-r--r--src/main.zig97
5 files changed, 147 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e73c965
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+zig-cache/
+zig-out/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0c177da
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2023 dec05eba
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5b71d19
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# zbind
+A small program that binds buttons on a joystick device (such as a controller) to commands.\
+To create a small static binary, build with: `zig build run -Doptimize=ReleaseSmall -Dtarget=native-linux-musl -Dcpu=baseline`
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..0c3fcf6
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,26 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const exe = b.addExecutable(.{
+ .name = "zbind",
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+
+ exe.linkLibC();
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ 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..9969741
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,97 @@
+const std = @import("std");
+const c = @cImport({
+ @cInclude("linux/joystick.h");
+});
+
+pub fn main() !void {
+ const allocator = std.heap.c_allocator;
+ var keybinds = std.ArrayList(Keybind).init(allocator);
+ defer keybinds.deinit();
+
+ var args = std.process.args();
+ defer args.deinit();
+
+ if(!args.skip())
+ usage();
+
+ var input_filepath: []const u8 = "";
+ if(args.next()) |arg| {
+ input_filepath = arg[0..arg.len];
+ } else {
+ std.debug.print("error: missing required argument input_device\n", .{});
+ usage();
+ }
+
+ while(args.next()) |arg| {
+ const keybind = Keybind.parse_string(arg) catch {
+ std.debug.print("error: invalid keybind: {s}\n", .{arg});
+ usage();
+ unreachable;
+ };
+ try keybinds.append(keybind);
+ }
+
+ const event_fd = std.os.open(input_filepath, std.os.O.RDONLY, 0) catch |err| {
+ std.debug.print("error: failed to open: {s}\n", .{input_filepath});
+ return err;
+ };
+ defer std.os.close(event_fd);
+
+ var event: c.js_event = undefined;
+ while(true) {
+ const read_size = try std.os.read(event_fd, std.mem.asBytes(&event));
+ if(read_size != @sizeOf(@TypeOf(event)))
+ continue;
+
+ if(event.type & c.JS_EVENT_BUTTON == 0)
+ continue;
+
+ if(event.value != 1)
+ continue;
+
+ for(keybinds.items) |*keybind| {
+ if(event.number != keybind.key)
+ continue;
+
+ std.debug.print("key pressed: {d}, running command: {s}\n", .{keybind.key, keybind.command});
+ keybind.execute_command(allocator);
+ }
+ }
+}
+
+fn usage() void {
+ std.debug.print(
+ \\usage: zbind <input_device> [binds...]
+ \\ binds buttons on a joystick device (such as a controller) to commands
+ \\
+ \\OPTIONS:
+ \\ binds There may be multiple keybindings in the format key,command
+ \\
+ \\EXAMPLES:
+ \\ zbind /dev/input/by-id/usb-Joystick "0,echo 'hello world' > log.log" "4,script.sh"
+ , .{});
+ std.os.exit(1);
+}
+
+const Keybind = struct {
+ key: u8,
+ command: []const u8,
+
+ pub fn parse_string(str: []const u8) !Keybind {
+ var it = std.mem.splitScalar(u8, str, ',');
+ const key_str = it.next() orelse return error.InvalidSyntax;
+ const command = it.next() orelse return error.InvalidSyntax;
+ const key = try std.fmt.parseInt(u8, key_str, 10);
+ return .{
+ .key = key,
+ .command = command,
+ };
+ }
+
+ pub fn execute_command(self: *Keybind, allocator: std.mem.Allocator) void {
+ var child = std.ChildProcess.init(&.{ "/bin/sh", "-c", self.command }, allocator);
+ child.stdin_behavior = .Ignore;
+ child.spawn() catch return;
+ _ = child.wait() catch return;
+ }
+}; \ No newline at end of file