aboutsummaryrefslogtreecommitdiff
path: root/src/main.zig
blob: 718c6ed983b36aa860c01ef91329a945a243d785 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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/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;
    }
};