diff options
Diffstat (limited to 'src/gamepad.c')
| -rw-r--r-- | src/gamepad.c | 380 |
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--; + } +} |
