#define _XOPEN_SOURCE #include #include #include #include #include #include #include #include #include #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--; } }