summaryrefslogtreecommitdiff
path: root/src/gamepad.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gamepad.c')
-rw-r--r--src/gamepad.c380
1 files changed, 380 insertions, 0 deletions
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--;
+ }
+}