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 [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/js0 "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; } };