diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | LICENSE | 19 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | build.zig | 26 | ||||
-rw-r--r-- | src/main.zig | 97 |
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/ @@ -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 |