summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.c198
-rw-r--r--src/gamepad.c380
-rw-r--r--src/uinput.c80
-rw-r--r--src/util.h7
-rw-r--r--src/waypad.c141
-rw-r--r--src/waypad.h132
6 files changed, 938 insertions, 0 deletions
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..44bd859
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,198 @@
+#include <ctype.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <scfg.h>
+
+#include "waypad.h"
+#include "util.h"
+
+void add_exec(struct action *action, struct scfg_directive *exec) {
+ size_t argc = exec->params_len - 1;
+
+ action->type = ACTION_EXEC;
+ action->exec = calloc(argc + 1, sizeof(*action->exec));
+
+ for (size_t i = 0; i < argc; i++)
+ action->exec[i] = strdup(exec->params[i + 1]);
+}
+
+void add_mouse(struct action *action, struct scfg_directive *mouse) {
+ if (mouse->params_len < 2) // mouse <left|right|center>
+ return;
+ action->type = ACTION_CLICK;
+ if (strcmp(mouse->params[1], "left") == 0)
+ action->click = BTN_LEFT;
+ else if (strcmp(mouse->params[1], "right") == 0)
+ action->click = BTN_RIGHT;
+ else if (strcmp(mouse->params[1], "center") == 0)
+ action->click = BTN_MIDDLE;
+}
+
+static const struct {
+ const char *name;
+ enum keyboard_modifier mod;
+} mod_map[] = {
+ { "shift", KEYMOD_LSHIFT },
+ { "lshift", KEYMOD_LSHIFT },
+ { "rshift", KEYMOD_RSHIFT },
+ { "ctrl", KEYMOD_LCTRL },
+ { "lctrl", KEYMOD_LCTRL },
+ { "rctrl", KEYMOD_RCTRL },
+ { "alt", KEYMOD_LALT },
+ { "lalt", KEYMOD_LALT },
+ { "ralt", KEYMOD_RALT },
+ { "super", KEYMOD_LMETA },
+ { "meta", KEYMOD_LMETA },
+ { "lmeta", KEYMOD_LMETA },
+ { "rmeta", KEYMOD_RMETA },
+};
+
+void add_key(struct action *action, struct scfg_directive *key) {
+ enum keyboard_modifier mod = 0;
+ const char *param;
+ size_t i;
+
+ if (key->params_len < 2) // key [<mod>...] <key>
+ return;
+
+ for (i = 1; i < key->params_len - 1; i++)
+ for (size_t j = 0; j < lenghtof(mod_map); j++)
+ if (strcmp(mod_map[j].name, key->params[i]) == 0)
+ mod |= mod_map[j].mod;
+ param = key->params[i];
+
+ char keysym[strlen(param) + sizeof("KEY_")];
+ char *sym = stpcpy(keysym, "KEY_");
+ for (i = 0; param[i]; i++)
+ sym[i] = toupper(param[i]);
+ sym[i] = '\0';
+
+ action->type = ACTION_KEY;
+ action->key.mod = mod;
+ action->key.code = libevdev_event_code_from_code_name(keysym);
+
+ if (action->key.code == -1)
+ warn("failed to find evdev codename %s", keysym);
+}
+
+void add_mode(struct gamepad *pad, struct action *action, struct scfg_directive *mode_bind) {
+ if (mode_bind->params_len < 2) // mode <name>
+ return;
+ action->type = ACTION_MODE;
+ action->mode = get_make_mode(pad, mode_bind->params[1]);
+}
+
+static void handle_buttons(struct gamepad *pad, struct scfg_block *buttons) {
+ for (size_t i = 0; i < buttons->directives_len; i++) {
+ struct scfg_directive *button_bind = &buttons->directives[i];
+ size_t action_idx;
+ if (sscanf(button_bind->name, "btn%zu", &action_idx) != 1) {
+ enum gamepad_button map_idx = str_to_button(button_bind->name);
+ if (map_idx == GAMEPAD_BUTTON_INVALID)
+ continue;
+ action_idx = pad->mapping->buttons[map_idx];
+ }
+
+ if (action_idx >= pad->nbuttons) {
+ warn("invalid button \"%s\"", button_bind->name);
+ continue;
+ }
+
+ struct action *action = &pad->modes.current->buttons[action_idx];
+ if (button_bind->params_len < 2)
+ continue;
+ if (strcmp(button_bind->params[0], "exec") == 0)
+ add_exec(action, button_bind);
+ else if (strcmp(button_bind->params[0], "key") == 0)
+ add_key(action, button_bind);
+ else if (strcmp(button_bind->params[0], "mouse") == 0)
+ add_mouse(action, button_bind);
+ else if (strcmp(button_bind->params[0], "mode") == 0)
+ add_mode(pad, action, button_bind);
+ else
+ warn("unknown command %s.", button_bind->params[0]);
+ }
+}
+
+static void handle_axis(struct gamepad *pad, struct scfg_block *axis) {
+ for (size_t i = 0; i < axis->directives_len; i++) {
+ struct scfg_directive *axis_bind = &axis->directives[i];
+ size_t action_idx;
+
+ if (sscanf(axis_bind->name, "axis%zu", &action_idx) != 1) {
+ enum gamepad_axis map_idx = str_to_axis(axis_bind->name);
+ if (map_idx == GAMEPAD_AXIS_INVALID)
+ continue;
+ action_idx = pad->mapping->axis[map_idx];
+ }
+
+ if (action_idx >= pad->naxis) {
+ warn("invalid axis \"%s\"", axis_bind->name);
+ continue;
+ }
+
+ if (axis_bind->params_len < 2)
+ continue;
+
+ struct action *action = &pad->modes.current->axis[action_idx];
+
+ action->type = ACTION_MOUSE;
+ switch (axis_bind->params[1][0]) {
+ case 'x':
+ action->mouse = MOUSE_X;
+ break;
+ case 'y':
+ action->mouse = MOUSE_Y;
+ break;
+ default:
+ continue;
+ }
+ }
+}
+
+static void handle_mode(struct gamepad *pad, const char *name, struct scfg_block *mode) {
+ if (!name)
+ return;
+ pad->modes.current = get_make_mode(pad, name);
+ for (size_t i = 0; i < mode->directives_len; i++) {
+ struct scfg_directive *section = &mode->directives[i];
+ if (strcmp(section->name, "button") == 0)
+ handle_buttons(pad, &section->children);
+ else if (strcmp(section->name, "axis") == 0)
+ handle_axis(pad, &section->children);
+ else if (strcmp(section->name, "mode") == 0)
+ warn("can't nest modes.");
+ }
+ pad->modes.current = &pad->modes.base;
+}
+
+static void handle_gamepad(struct gamepad *pad, struct scfg_block *cfg) {
+ for (size_t i = 0; i < cfg->directives_len; i++) {
+ struct scfg_directive *section = &cfg->directives[i];
+ if (strcmp(section->name, "button") == 0)
+ handle_buttons(pad, &section->children);
+ else if (strcmp(section->name, "axis") == 0)
+ handle_axis(pad, &section->children);
+ else if (strcmp(section->name, "mode") == 0)
+ handle_mode(pad, section->params_len >= 1 ? section->params[0] : NULL, &section->children);
+ }
+}
+
+bool load_config(const char *path) {
+ struct scfg_block config;
+ if (scfg_load_file(&config, path) != 0)
+ return false;
+
+ for (size_t i = 0; i < config.directives_len; i++) {
+ struct gamepad *pad = find_gamepad(config.directives[i].name);
+ if (!pad) {
+ fprintf(stderr, "\"%s\" not found.\n", config.directives[i].name);
+ continue;
+ }
+ handle_gamepad(pad, &config.directives[i].children);
+ }
+
+ return true;
+}
diff --git a/src/gamepad.c b/src/gamepad.c
new file mode 100644
index 0000000..519325e
--- /dev/null
+++ b/src/gamepad.c
@@ -0,0 +1,380 @@
+#define _XOPEN_SOURCE
+
+#include <dirent.h>
+#include <endian.h>
+#include <err.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "waypad.h"
+#include "util.h"
+
+struct gamepads gamepads;
+static struct database {
+ size_t len, cap;
+ struct mapping *mappings;
+} database;
+
+static const char *button_str_map[] = {
+ [GAMEPAD_BUTTON_A] = "a",
+ [GAMEPAD_BUTTON_B] = "b",
+ [GAMEPAD_BUTTON_X] = "x",
+ [GAMEPAD_BUTTON_Y] = "y",
+ [GAMEPAD_BUTTON_BACK] = "back",
+ [GAMEPAD_BUTTON_GUIDE] = "guide",
+ [GAMEPAD_BUTTON_START] = "start",
+ [GAMEPAD_BUTTON_LEFTSTICK] = "leftstick",
+ [GAMEPAD_BUTTON_RIGHTSTICK] = "rightstick",
+ [GAMEPAD_BUTTON_LEFTSHOULDER] = "leftshoulder",
+ [GAMEPAD_BUTTON_RIGHTSHOULDER] = "rightshoulder",
+ [GAMEPAD_BUTTON_DPAD_UP] = "dpup",
+ [GAMEPAD_BUTTON_DPAD_DOWN] = "dpdown",
+ [GAMEPAD_BUTTON_DPAD_LEFT] = "dpleft",
+ [GAMEPAD_BUTTON_DPAD_RIGHT] = "dpright",
+ [GAMEPAD_BUTTON_MISC1] = "misc1",
+ [GAMEPAD_BUTTON_PADDLE1] = "paddle1",
+ [GAMEPAD_BUTTON_PADDLE2] = "paddle2",
+ [GAMEPAD_BUTTON_PADDLE3] = "paddle3",
+ [GAMEPAD_BUTTON_PADDLE4] = "paddle4",
+ [GAMEPAD_BUTTON_TOUCHPAD] = "touchpad",
+};
+
+enum gamepad_button str_to_button(const char *str) {
+ for (size_t i = 0; i < GAMEPAD_BUTTON_MAX; i++) {
+ if (strcasecmp(str, button_str_map[i]) == 0)
+ return i;
+ }
+ return GAMEPAD_BUTTON_INVALID;
+}
+
+const char *axis_str_map[] = {
+ [GAMEPAD_AXIS_LEFTX] = "leftx",
+ [GAMEPAD_AXIS_LEFTY] = "lefty",
+ [GAMEPAD_AXIS_RIGHTX] = "rightx",
+ [GAMEPAD_AXIS_RIGHTY] = "righty",
+ [GAMEPAD_AXIS_TRIGGERLEFT] = "triggerleft",
+ [GAMEPAD_AXIS_TRIGGERRIGHT] = "triggerright",
+};
+
+enum gamepad_axis str_to_axis(const char *str) {
+ for (size_t i = 0; i < GAMEPAD_AXIS_MAX; i++) {
+ if (strcasecmp(str, axis_str_map[i]) == 0)
+ return i;
+ }
+ return GAMEPAD_AXIS_INVALID;
+}
+
+static struct guid parse_dev_guid(struct libevdev *dev) {
+ return (struct guid) {
+ .guid = {
+ [0] = htole16(libevdev_get_id_bustype(dev)),
+ [2] = htole16(libevdev_get_id_vendor(dev)),
+ [4] = htole16(libevdev_get_id_product(dev)),
+ [6] = htole16(libevdev_get_id_version(dev)),
+ }
+ };
+}
+
+static struct guid parse_str_guid(const char *str) {
+ struct guid out = {};
+ uint16_t guid[8] = {};
+
+ sscanf(str, "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx",
+ &guid[0], &guid[1], &guid[2], &guid[3],
+ &guid[4], &guid[5], &guid[6], &guid[7]);
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ swab(guid, out.guid, sizeof(out.guid));
+#endif
+
+ return out;
+}
+
+static inline uint64_t fmix64(uint64_t value) {
+ value ^= value >> 33;
+ value *= 0xff51afd7ed558ccd;
+ value ^= value >> 33;
+ value *= 0xc4ceb9fe1a85ec53;
+ value ^= value >> 33;
+
+ return value;
+}
+
+static uint64_t hash_guid(struct guid *guid) {
+ uint64_t hash = 0;
+ for (size_t i = 0; i < 2; i += 4)
+ hash ^= fmix64(((uint64_t)guid->guid[i] << 48)
+ | ((uint64_t)guid->guid[i + 1] << 32)
+ | (guid->guid[i + 2] << 16)
+ | guid->guid[i + 3]);
+ return hash;
+}
+
+static bool guid_cmp(struct guid *lhs, struct guid *rhs) {
+ for (size_t i = 0; i < 8; i++)
+ if (lhs->guid[i] != rhs->guid[i])
+ return false;
+ return true;
+}
+
+static struct mapping *add_mapping(struct mapping map);
+static void realloc_mappings() {
+ struct mapping *old_mappings = database.mappings;
+ size_t items = database.len, cap = database.cap;
+
+ database.mappings = calloc(database.cap *= 2, sizeof(*database.mappings));
+
+ for (size_t i = 0; i < cap; i++) {
+ if (!old_mappings[i].name)
+ continue;
+
+ add_mapping(old_mappings[i]);
+
+ if (!--items)
+ break;
+ }
+}
+
+static struct mapping *find_mapping(struct guid *guid) {
+ uint64_t hash = hash_guid(guid) % database.cap;
+ struct mapping *mapping = NULL;
+
+ mapping = &database.mappings[hash % database.cap];
+
+ while (mapping->name) {
+ if (guid_cmp(&mapping->guid, guid))
+ break;
+ mapping = &database.mappings[++hash % database.cap];
+ }
+
+ return mapping;
+}
+
+static struct mapping *add_mapping(struct mapping map) {
+ struct mapping *mapping;
+
+ if (database.len * 4 >= database.cap * 3)
+ realloc_mappings();
+
+ mapping = find_mapping(&map.guid);
+ free(mapping->name);
+ *mapping = map;
+ database.len++;
+
+ return mapping;
+}
+
+static void parse_mappings(struct mapping *mapping, char *field) {
+ char *button = field, *symbol = strsep(&button, ":");
+ if (!symbol || !button)
+ return;
+
+ switch (button[0]) {
+ case 'b':
+ enum gamepad_button btn = str_to_button(symbol);
+ if (btn == GAMEPAD_BUTTON_INVALID)
+ return;
+ mapping->buttons[btn] = atoi(&button[1]);
+ break;
+ case 'a':
+ enum gamepad_axis axis = str_to_axis(symbol);
+ if (axis == GAMEPAD_AXIS_INVALID)
+ return;
+ mapping->axis[axis] = atoi(&button[1]);
+ break;
+ case 'h':
+ break;
+ }
+}
+
+static bool load_database() {
+ FILE *fp = fopen("/home/navi/.config/waypad/gamecontrollerdb.txt", "r");
+ char *line = NULL;
+ ssize_t len;
+ size_t size;
+
+ if (!fp)
+ return false;
+
+ database.cap = 32;
+ database.mappings = calloc(database.cap, sizeof(*database.mappings));
+
+ while ((len = getline(&line, &size, fp)) != -1) {
+ char *name, *field, *rest = line;
+ struct mapping *mapping;
+ struct guid guid;
+
+ if (len < 1 || line[0] == '#')
+ continue;
+
+ line[len--] = '\0';
+
+ if (!(field = strsep(&rest, ",")))
+ continue;
+ guid = parse_str_guid(field);
+ if (!(field = strsep(&rest, ",")))
+ continue;
+ name = strdup(field);
+
+ mapping = add_mapping((struct mapping) {
+ .name = name,
+ .guid = guid,
+ });
+
+ while ((field = strsep(&rest, ",")))
+ parse_mappings(mapping, field);
+ }
+
+ free(line);
+ fclose(fp);
+ return true;
+}
+
+bool load_gamepads() {
+ if (!load_database())
+ return false;
+
+ DIR *input_dir = opendir("/dev/input/by-id");
+ struct dirent *dirent;
+
+ if (!input_dir)
+ return false;
+
+ gamepads.cap = 5;
+ gamepads.loaded = calloc(gamepads.cap, sizeof(*gamepads.loaded));
+
+ while ((dirent = readdir(input_dir))) {
+ struct libevdev *dev;
+ struct gamepad *pad;
+ int gamepad_fd;
+
+ if (dirent->d_name[0] == '.')
+ continue;
+ if (fnmatch("*-event-joystick", dirent->d_name, FNM_PATHNAME) != 0)
+ continue;
+
+ warnx("opening event joystick /dev/input/by-id/%s.", dirent->d_name);
+ if ((gamepad_fd = openat(dirfd(input_dir), dirent->d_name, O_RDONLY | O_NONBLOCK | O_CLOEXEC)) == -1) {
+ warnx("failed to open /dev/input/by-id/%s", dirent->d_name);
+ continue;
+ }
+
+ warnx("opened /dev/input/by-id/%s on fd %d.", dirent->d_name, gamepad_fd);
+
+ if (libevdev_new_from_fd(gamepad_fd, &dev) < 0) {
+ warnx("failed to create libevdev from fd %d", gamepad_fd);
+ close(gamepad_fd);
+ continue;
+ }
+
+ struct guid guid = parse_dev_guid(dev);
+ struct mapping *mapping = find_mapping(&guid);
+
+ if (!mapping->name)
+ continue;
+
+ if (gamepads.len == gamepads.cap)
+ gamepads.loaded = realloc(gamepads.loaded, (gamepads.cap *= 2) * sizeof(gamepads.loaded));
+ pad = &gamepads.loaded[gamepads.len++];
+ pad->mapping = mapping;
+ pad->dev = dev;
+
+ size_t nbuttons = 0;
+ for (size_t i = BTN_JOYSTICK; i < KEY_MAX; ++i) {
+ if (!libevdev_has_event_code(dev, EV_KEY, i))
+ continue;
+ warnx("%zu - Joystick has button: 0x%zu - %s", nbuttons, i, libevdev_event_code_get_name(EV_KEY, i));
+ pad->map.button[i - BTN_MISC] = nbuttons++;
+ }
+ for (size_t i = BTN_MISC; i < BTN_JOYSTICK; ++i) {
+ if (!libevdev_has_event_code(dev, EV_KEY, i))
+ continue;
+ warnx("%zu - Joystick has button: 0x%zu - %s", nbuttons, i, libevdev_event_code_get_name(EV_KEY, i));
+ pad->map.button[i - BTN_MISC] = nbuttons++;
+ }
+
+ pad->nbuttons = nbuttons;
+ pad->modes.base.buttons = calloc(nbuttons, sizeof(*pad->modes.base.buttons));
+
+ size_t naxis = 0;
+ for (size_t i = 0; i < ABS_MAX; ++i) {
+ if (!libevdev_has_event_code(dev, EV_ABS, i))
+ continue;
+ const struct input_absinfo *absinfo = libevdev_get_abs_info(dev, i);
+ warnx("%zu - Joystick has absolute axis: 0x%.2lx - %s", naxis, i, libevdev_event_code_get_name(EV_ABS, i));
+ warnx("Values = { %d, %d, %d, %d, %d }", absinfo->value,
+ absinfo->minimum, absinfo->maximum, absinfo->fuzz, absinfo->flat);
+ pad->map.axis[i] = naxis++;
+ }
+
+ pad->naxis = naxis;
+ pad->modes.base.axis = calloc(naxis, sizeof(*pad->modes.base.axis));
+
+ pad->modes.current = &pad->modes.base;
+ pad->modes.all = calloc((pad->modes.cap = 2), sizeof(*pad->modes.all));
+ }
+
+ closedir(input_dir);
+
+ return true;
+}
+
+struct gamepad *find_gamepad(const char *name) {
+ for (size_t i = 0; i < gamepads.len; i++)
+ if (strcmp(name, gamepads.loaded[i].mapping->name) == 0)
+ return &gamepads.loaded[i];
+ return NULL;
+}
+
+struct mode *get_make_mode(struct gamepad *pad, const char *name) {
+ for (size_t i = 0; i < pad->modes.len; i++)
+ if (strcmp(name, pad->modes.all[i].name) == 0)
+ return &pad->modes.all[i];
+ if (pad->modes.cap == pad->modes.len)
+ pad->modes.all = realloc(pad->modes.all, (pad->modes.cap *= 2) * sizeof(pad->modes.all));
+ struct mode *mode = &pad->modes.all[pad->modes.len++];
+ mode->name = strdup(name);
+ mode->buttons = calloc(pad->nbuttons, sizeof(*mode->buttons));
+ mode->axis = calloc(pad->naxis, sizeof(*mode->axis));
+ return mode;
+}
+
+static void free_mode(struct mode *mode, size_t nbuttons) {
+ for (size_t i = 0; i < nbuttons; i++) {
+ switch (mode->buttons[i].type) {
+ case ACTION_EXEC:
+ for (char **arg = mode->buttons[i].exec; *arg; arg++)
+ free(*arg);
+ free(mode->buttons[i].exec);
+ case ACTION_MODE:
+ case ACTION_NONE:
+ case ACTION_KEY:
+ case ACTION_MOUSE:
+ case ACTION_CLICK:
+ break;
+ }
+ }
+}
+
+static void free_gamepad(struct gamepad *pad) {
+ for (size_t i = 0; i < pad->modes.len; i++)
+ free_mode(&pad->modes.all[i], pad->nbuttons);
+}
+
+void free_gamepads() {
+ for (size_t i = 0; i < gamepads.len; i++)
+ free_gamepad(&gamepads.loaded[i]);
+
+ for (size_t i = 0; i < database.cap; i++) {
+ if (!database.len)
+ break;
+ if (!database.mappings[i].name)
+ continue;
+ free(database.mappings[i].name);
+ database.len--;
+ }
+}
diff --git a/src/uinput.c b/src/uinput.c
new file mode 100644
index 0000000..51dbc56
--- /dev/null
+++ b/src/uinput.c
@@ -0,0 +1,80 @@
+#include <libevdev/libevdev-uinput.h>
+#include "waypad.h"
+#include "util.h"
+
+struct uinput_dev {
+ struct libevdev *dev;
+ struct libevdev_uinput *uinput;
+} mouse, keyboard;
+
+bool uinput_mouse_init() {
+ mouse.dev = libevdev_new();
+ libevdev_set_name(mouse.dev, "waypad mouse");
+ libevdev_enable_event_type(mouse.dev, EV_REL);
+ libevdev_enable_event_code(mouse.dev, EV_REL, REL_X, NULL);
+ libevdev_enable_event_code(mouse.dev, EV_REL, REL_Y, NULL);
+
+ libevdev_enable_event_type(mouse.dev, EV_KEY);
+ libevdev_enable_event_code(mouse.dev, EV_KEY, BTN_LEFT, NULL);
+ libevdev_enable_event_code(mouse.dev, EV_KEY, BTN_MIDDLE, NULL);
+ libevdev_enable_event_code(mouse.dev, EV_KEY, BTN_RIGHT, NULL);
+
+ return libevdev_uinput_create_from_device(mouse.dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &mouse.uinput) == 0;
+}
+
+void uinput_mouse_rel_move(int x, int y) {
+ if (x)
+ libevdev_uinput_write_event(mouse.uinput, EV_REL, REL_X, x);
+ if (y)
+ libevdev_uinput_write_event(mouse.uinput, EV_REL, REL_Y, y);
+ libevdev_uinput_write_event(mouse.uinput, EV_SYN, SYN_REPORT, 0);
+}
+
+void uinput_mouse_press(int button) {
+ libevdev_uinput_write_event(mouse.uinput, EV_KEY, button, 1);
+ libevdev_uinput_write_event(mouse.uinput, EV_SYN, SYN_REPORT, 0);
+}
+
+void uinput_mouse_release(int button) {
+ libevdev_uinput_write_event(mouse.uinput, EV_KEY, button, 0);
+ libevdev_uinput_write_event(mouse.uinput, EV_SYN, SYN_REPORT, 0);
+}
+
+bool uinput_keyboard_init() {
+ keyboard.dev = libevdev_new();
+ libevdev_set_name(keyboard.dev, "waypad keyboard");
+ libevdev_enable_event_type(keyboard.dev, EV_KEY);
+ for (size_t i = 0; i < KEY_MAX; i++)
+ libevdev_enable_event_code(keyboard.dev, EV_KEY, i, NULL);
+ return libevdev_uinput_create_from_device(keyboard.dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &keyboard.uinput) == 0;
+}
+
+static const struct {
+ enum keyboard_modifier mod;
+ int code;
+} mod_map[] = {
+ { KEYMOD_LSHIFT, KEY_LEFTSHIFT },
+ { KEYMOD_RSHIFT, KEY_RIGHTSHIFT },
+ { KEYMOD_LCTRL, KEY_LEFTCTRL },
+ { KEYMOD_RCTRL, KEY_RIGHTCTRL },
+ { KEYMOD_LALT, KEY_LEFTALT },
+ { KEYMOD_RALT, KEY_RIGHTALT },
+ { KEYMOD_LMETA, KEY_LEFTMETA },
+ { KEYMOD_RMETA, KEY_RIGHTMETA },
+};
+
+void uinput_keyboard_press(enum keyboard_modifier mod, int key) {
+ for (size_t i = 0; i < lenghtof(mod_map); i++)
+ if (mod & mod_map[i].mod)
+ libevdev_uinput_write_event(keyboard.uinput, EV_KEY, mod_map[i].code, 1);
+ libevdev_uinput_write_event(keyboard.uinput, EV_KEY, key, 1);
+ libevdev_uinput_write_event(keyboard.uinput, EV_SYN, SYN_REPORT, 0);
+}
+
+void uinput_keyboard_release(enum keyboard_modifier mod, int key) {
+ libevdev_uinput_write_event(keyboard.uinput, EV_KEY, key, 0);
+ for (size_t i = 0; i < lenghtof(mod_map); i++)
+ if (mod & mod_map[i].mod)
+ libevdev_uinput_write_event(keyboard.uinput, EV_KEY, mod_map[i].code, 0);
+ libevdev_uinput_write_event(keyboard.uinput, EV_SYN, SYN_REPORT, 0);
+}
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..15b2323
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,7 @@
+#ifndef _UTIL_H_
+#define _UTIL_H_
+
+#define lenghtof(arr) (sizeof(arr) / sizeof(*arr))
+#define todo() warn("%s unimplemented.", __func__)
+
+#endif
diff --git a/src/waypad.c b/src/waypad.c
new file mode 100644
index 0000000..ffbe691
--- /dev/null
+++ b/src/waypad.c
@@ -0,0 +1,141 @@
+#define _XOPEN_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <spawn.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <sys/poll.h>
+
+#include <libevdev/libevdev.h>
+
+#include "waypad.h"
+#include "util.h"
+
+bool verbose = false;
+
+static void cleanup() {
+ free_gamepads();
+}
+
+extern char **environ;
+
+void handle_key_event(struct gamepad *pad, struct action *action, bool pressed) {
+ switch (action->type) {
+ case ACTION_NONE: return;
+ case ACTION_EXEC:
+ if (!pressed)
+ return;
+ pid_t pid;
+ if ((errno = posix_spawnp(&pid, action->exec[0], NULL, NULL, action->exec, environ)) != 0)
+ warn("failed to exec %s", action->exec[0]);
+ return;
+ case ACTION_KEY:
+ if (pressed)
+ uinput_keyboard_press(action->key.mod, action->key.code);
+ else
+ uinput_keyboard_release(action->key.mod, action->key.code);
+ break;
+ case ACTION_CLICK:
+ if (pressed)
+ uinput_mouse_press(action->click);
+ else
+ uinput_mouse_release(action->click);
+ break;
+ case ACTION_MODE:
+ if (!pressed)
+ break;
+ if (action->mode == pad->modes.current) {
+ warnx("switching to base mode");
+ pad->modes.current = &pad->modes.base;
+ } else {
+ warnx("switching to mode %s", action->mode->name);
+ pad->modes.current = action->mode;
+ }
+ break;
+ default:
+ todo();
+ break;
+ }
+}
+
+void handle_axis_event(struct action *action, int input) {
+ int value = input - action->old_value;
+ action->old_value = input;
+
+ if (input > 1500 || input < -1500)
+ return;
+
+ switch (action->type) {
+ case ACTION_NONE:
+ return;
+ case ACTION_MOUSE:
+ switch (action->mouse) {
+ case MOUSE_X:
+ uinput_mouse_rel_move(value / 100, 0);
+ break;
+ case MOUSE_Y:
+ uinput_mouse_rel_move(0, -(value / 100));
+ break;
+ }
+ return;
+ default:
+ todo();
+ return;
+ }
+}
+
+void handle_event(struct gamepad *pad, struct input_event ev) {
+ struct action *action;
+
+ switch (ev.type) {
+ case EV_KEY:
+ action = &pad->modes.current->buttons[pad->map.button[ev.code - BTN_MISC]];
+ handle_key_event(pad, action, ev.value);
+ return;
+ case EV_ABS:
+ action = &pad->modes.current->buttons[pad->map.axis[ev.code]];
+ handle_axis_event(action, ev.value);
+ return;
+ }
+}
+
+int main(int argc, char **argv) {
+ if (argc < 2)
+ errx(EXIT_FAILURE, "usage: %s <config>", argv[0]);
+ if (!load_gamepads())
+ errx(EXIT_FAILURE, "failed to load gamepads");
+ if (!load_config(argv[1]))
+ errx(EXIT_FAILURE, "failed to load configuration file");
+ if (!uinput_mouse_init())
+ errx(EXIT_FAILURE, "failed to create uinput mouse");
+ if (!uinput_keyboard_init())
+ errx(EXIT_FAILURE, "failed to create uinput keyboard");
+
+ atexit(cleanup);
+
+ struct pollfd fds[gamepads.len];
+ for (size_t i = 0; i < gamepads.len; i++) {
+ fds[i] = (struct pollfd) {
+ .fd = libevdev_get_fd(gamepads.loaded[i].dev),
+ .events = POLLIN
+ };
+ }
+
+ while (true) {
+ if (poll(fds, gamepads.len, -1) < 0)
+ continue;
+
+ for (size_t i = 0; i < gamepads.len; i++) {
+ struct input_event ev;
+ int rc;
+ if ((rc = libevdev_next_event(gamepads.loaded[i].dev, LIBEVDEV_READ_FLAG_NORMAL, &ev)) == 0)
+ handle_event(&gamepads.loaded[i], ev);
+ else if (rc != -EAGAIN)
+ warnx("libevdev next event: %s.", strerror(-rc));
+ }
+ }
+
+ return 0;
+}
diff --git a/src/waypad.h b/src/waypad.h
new file mode 100644
index 0000000..1137f9b
--- /dev/null
+++ b/src/waypad.h
@@ -0,0 +1,132 @@
+#ifndef _WAYPAD_H_
+#define _WAYPAD_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <libevdev/libevdev.h>
+
+enum gamepad_button {
+ GAMEPAD_BUTTON_INVALID = -1,
+ GAMEPAD_BUTTON_A,
+ GAMEPAD_BUTTON_B,
+ GAMEPAD_BUTTON_X,
+ GAMEPAD_BUTTON_Y,
+ GAMEPAD_BUTTON_BACK,
+ GAMEPAD_BUTTON_GUIDE,
+ GAMEPAD_BUTTON_START,
+ GAMEPAD_BUTTON_LEFTSTICK,
+ GAMEPAD_BUTTON_RIGHTSTICK,
+ GAMEPAD_BUTTON_LEFTSHOULDER,
+ GAMEPAD_BUTTON_RIGHTSHOULDER,
+ GAMEPAD_BUTTON_DPAD_UP,
+ GAMEPAD_BUTTON_DPAD_DOWN,
+ GAMEPAD_BUTTON_DPAD_LEFT,
+ GAMEPAD_BUTTON_DPAD_RIGHT,
+ GAMEPAD_BUTTON_MISC1,
+ GAMEPAD_BUTTON_PADDLE1,
+ GAMEPAD_BUTTON_PADDLE2,
+ GAMEPAD_BUTTON_PADDLE3,
+ GAMEPAD_BUTTON_PADDLE4,
+ GAMEPAD_BUTTON_TOUCHPAD,
+ GAMEPAD_BUTTON_MAX
+};
+
+enum gamepad_axis {
+ GAMEPAD_AXIS_INVALID = -1,
+ GAMEPAD_AXIS_LEFTX,
+ GAMEPAD_AXIS_LEFTY,
+ GAMEPAD_AXIS_RIGHTX,
+ GAMEPAD_AXIS_RIGHTY,
+ GAMEPAD_AXIS_TRIGGERLEFT,
+ GAMEPAD_AXIS_TRIGGERRIGHT,
+ GAMEPAD_AXIS_MAX
+};
+
+enum keyboard_modifier {
+ KEYMOD_LSHIFT = 1 << 0,
+ KEYMOD_RSHIFT = 1 << 1,
+ KEYMOD_LCTRL = 1 << 2,
+ KEYMOD_RCTRL = 1 << 3,
+ KEYMOD_LALT = 1 << 4,
+ KEYMOD_RALT = 1 << 5,
+ KEYMOD_LMETA = 1 << 6,
+ KEYMOD_RMETA = 1 << 7,
+};
+
+struct guid { uint16_t guid[8]; };
+
+struct mapping {
+ char *name;
+ struct guid guid;
+ uint8_t buttons[GAMEPAD_BUTTON_MAX];
+ uint8_t axis[GAMEPAD_AXIS_MAX];
+};
+
+struct action {
+ enum {
+ ACTION_NONE,
+ ACTION_EXEC,
+ ACTION_KEY,
+ ACTION_MOUSE,
+ ACTION_CLICK,
+ ACTION_MODE,
+ } type;
+ int old_value; /* for relative axis binds */
+ union {
+ char **exec;
+ struct {
+ enum keyboard_modifier mod;
+ int code;
+ } key;
+ int click;
+ enum {
+ MOUSE_X,
+ MOUSE_Y
+ } mouse;
+ struct mode *mode;
+ };
+};
+
+struct gamepad {
+ struct mapping *mapping;
+ struct libevdev *dev;
+ /* maps libevdev event ids to indexes in gamepad.actions */
+ struct {
+ uint8_t button[KEY_MAX - BTN_MISC];
+ uint8_t axis[ABS_MAX];
+ } map;
+ size_t nbuttons, naxis;
+ struct {
+ size_t len, cap;
+ struct mode {
+ char *name;
+ struct action *buttons, *axis;
+ } *all, *current, base;
+ } modes;
+};
+
+extern struct gamepads {
+ size_t len, cap;
+ struct gamepad *loaded;
+} gamepads;
+
+bool load_config(const char *path);
+bool load_gamepads();
+void free_gamepads();
+struct gamepad *find_gamepad(const char *name);
+struct mode *get_make_mode(struct gamepad *pad, const char *name);
+enum gamepad_axis str_to_axis(const char *str);
+enum gamepad_button str_to_button(const char *str);
+
+bool wayland_init();
+
+bool uinput_mouse_init();
+void uinput_mouse_press(int button);
+void uinput_mouse_release(int button);
+void uinput_mouse_rel_move(int x, int y);
+
+bool uinput_keyboard_init();
+void uinput_keyboard_press(enum keyboard_modifier mod, int key);
+void uinput_keyboard_release(enum keyboard_modifier mod, int key);
+#endif