diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.c | 198 | ||||
| -rw-r--r-- | src/gamepad.c | 380 | ||||
| -rw-r--r-- | src/uinput.c | 80 | ||||
| -rw-r--r-- | src/util.h | 7 | ||||
| -rw-r--r-- | src/waypad.c | 141 | ||||
| -rw-r--r-- | src/waypad.h | 132 |
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, §ion->children); + else if (strcmp(section->name, "axis") == 0) + handle_axis(pad, §ion->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, §ion->children); + else if (strcmp(section->name, "axis") == 0) + handle_axis(pad, §ion->children); + else if (strcmp(section->name, "mode") == 0) + handle_mode(pad, section->params_len >= 1 ? section->params[0] : NULL, §ion->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 |
