diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/waypad.c | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/src/waypad.c b/src/waypad.c new file mode 100644 index 0000000..dcfd7ef --- /dev/null +++ b/src/waypad.c @@ -0,0 +1,341 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <libevdev/libevdev.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <time.h> +#include <sys/mman.h> + +#include <xkbcommon/xkbcommon.h> + +#include <wayland-client.h> +#include "virtual-keyboard-unstable-v1-client-protocol.h" +#include "wlr-virtual-pointer-unstable-v1-client-protocol.h" + +struct wl_display *dpy; +struct wl_registry *reg; +struct wl_seat *seat; +struct zwlr_virtual_pointer_manager_v1 *ptr_manager; +struct zwlr_virtual_pointer_v1 *ptr; +struct zwp_virtual_keyboard_manager_v1 *kb_manager; +struct zwp_virtual_keyboard_v1 *kb; + +void reg_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) { + (void) data; + + if (strcmp(interface, zwlr_virtual_pointer_manager_v1_interface.name) == 0) { + ptr_manager = wl_registry_bind(wl_registry, name, &zwlr_virtual_pointer_manager_v1_interface, version); + } else if (strcmp(interface, zwp_virtual_keyboard_manager_v1_interface.name) == 0) { + kb_manager = wl_registry_bind(wl_registry, name, &zwp_virtual_keyboard_manager_v1_interface, version); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat = wl_registry_bind(wl_registry, name, &wl_seat_interface, version); + } +} + +void global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) { + (void) data; + (void) wl_registry; + (void) name; +} + +struct wl_registry_listener reg_listener = { + .global = reg_global, + .global_remove = global_remove +}; + +struct gamepad { + char *name; + struct libevdev *dev; + + uint8_t button_map[KEY_MAX]; + size_t nbuttons; + struct button { + bool set; + uint32_t keycode; + } *buttons; + + uint8_t axis_map[ABS_MAX]; + size_t naxis; + struct axis { + int old_value; + struct { + int min, max; + } deadzone; + } *axis; + struct gamepad *next; +}; + +struct config { + size_t npads, max_pads; + struct gamepad *pads; + + size_t nkeys, max_keys; + xkb_keysym_t *keys; +}; + +bool load_controller(struct gamepad *pad, const char *path) { + int fd = open(path, O_RDONLY | O_NONBLOCK); + if (fd == -1) + return false; + + struct libevdev *dev; + if (libevdev_new_from_fd(fd, &dev) < 0) + return false; + + pad->name = strdup(path); + pad->dev = dev; + + for (size_t i = BTN_JOYSTICK; i < KEY_MAX; i++) { + if (!libevdev_has_event_code(dev, EV_KEY, i)) + continue; + printf("button %ld: %s\n", pad->nbuttons, libevdev_event_code_get_name(EV_KEY, i)); + pad->button_map[i] = pad->nbuttons++; + } + pad->buttons = calloc(pad->nbuttons, sizeof(*pad->buttons)); + + for (size_t i = 0; i < ABS_MAX; i++) { + if (!libevdev_has_event_code(dev, EV_ABS, i)) + continue; + printf("axis %ld: %s\n", pad->naxis, libevdev_event_code_get_name(EV_ABS, i)); + pad->axis_map[i] = pad->naxis; + pad->naxis++; + } + pad->axis = calloc(pad->naxis, sizeof(*pad->axis)); + + return true; +} + +struct gamepad *get_gamepad(struct config *config, const char *path) { + for (size_t i = 0; i < config->npads; i++) + if (strcmp(config->pads[i].name, path) == 0) + return &config->pads[i]; + + if (config->npads == config->max_pads) + config->pads = realloc(config->pads, (config->max_pads *= 2) * sizeof(*config->pads)); + + if (!load_controller(&config->pads[config->npads], path)) + return NULL; + + return &config->pads[config->npads++]; +} + +xkb_keycode_t add_key(struct config *config, xkb_keysym_t sym) { + if (config->nkeys == config->max_keys) + config->keys = realloc(config->keys, (config->max_keys *= 2) * sizeof(*config->keys)); + config->keys[config->nkeys] = sym; + return config->nkeys++; +} + +void upload_keymap(struct config *config) { + char file[] = "/tmp/waypad-XXXXXX"; + int fd = mkstemp(file); + if (fd == -1) + abort(); + unlink(file); + FILE *f = fdopen(fd, "w"); + + fprintf(f, "xkb_keymap { "); + fprintf(f, "xkb_keycodes \"(unnamed)\" { minimum = 8; maximum = %ld; ", config->nkeys + 8 + 1); + for (size_t i = 0; i < config->nkeys; i++) + fprintf(f, "<K%ld> = %ld; ", i, i + 8); + fprintf(f, "}; "); + fprintf(f, "xkb_types \"(unnamed)\" { include \"complete\" }; "); + fprintf(f, "xkb_compatibility \"(unnamed)\" { include \"complete\" }; "); + + fprintf(f, "xkb_symbols \"(unnamed)\" { "); + for (size_t i = 0; i < config->nkeys; i++) { + char sym[256]; + if (xkb_keysym_get_name(config->keys[i], sym, sizeof(sym)) <= 0) + continue; + fprintf(f, "key <K%ld> {[ %s ]}; ", i, sym); + } + fprintf(f, "}; };"); + fflush(f); + + size_t size = ftell(f); + + zwp_virtual_keyboard_v1_keymap(kb, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, fileno(f), size); + wl_display_roundtrip(dpy); + + fclose(f); +} + +struct config *load_config(FILE *config_file) { + struct config *config = calloc(1, sizeof(*config)); + config->keys = calloc((config->max_keys = 5), sizeof(*config->keys)); + config->pads = calloc((config->max_pads = 5), sizeof(*config->pads)); + char *buf = NULL; + size_t len; + + while (getline(&buf, &len, config_file) != -1) { + buf[strlen(buf) - 1] = '\0'; /* pesky newline */ + char *save = buf + strspn(buf, " \t"); + if (*buf == '#') + continue; + char *path = strtok_r(buf, " ", &save); + if (!path || *path != '/') + continue; + + struct gamepad *pad = get_gamepad(config, path); + + char *event = strtok_r(NULL, " ", &save); + if (!event) + continue; + + if (strcmp(event, "button") == 0) { + char *idx_str = strtok_r(NULL, " ", &save); + if (!idx_str) + continue; + + char *end; + int idx = strtod(idx_str, &end); + if (idx_str == end || idx < 0 || (size_t)idx > pad->nbuttons) + continue; + + char *action = strtok_r(NULL, " ", &save); + if (!action) + continue; + + if (strcmp(action, "key") != 0) + continue; + + char *keysym = strtok_r(NULL, " ", &save); + if (!keysym) + keysym = save; + + xkb_keysym_t sym = xkb_keysym_from_name(keysym, XKB_KEYSYM_NO_FLAGS); + if (sym == XKB_KEY_NoSymbol) { + fprintf(stderr, "%s is not a valid keysym\n", keysym); + continue; + } + + pad->buttons[idx].keycode = add_key(config, sym); + pad->buttons[idx].set = true; + } else if (strcmp(event, "stick")) { + } + } + + free(buf); + + upload_keymap(config); + return config; +} + +void print_dev(struct libevdev *dev) { + printf("input id: bus %#x vendor %#x production %#x\n" + "evdev version: %x\n" + "name: %s\n" + "phys location: %s\n" + "uniq id: %s\n", + libevdev_get_id_bustype(dev), + libevdev_get_id_vendor(dev), + libevdev_get_id_product(dev), + libevdev_get_driver_version(dev), + libevdev_get_name(dev), + libevdev_get_phys(dev), + libevdev_get_uniq(dev)); + printf("props:\n"); + for (size_t i = 0 ; i < INPUT_PROP_MAX; i++) + if (libevdev_has_property(dev, i)) + printf("\tprop %ld: %s\n", i, libevdev_property_get_name(i)); + printf("events:\n"); + for (size_t i = 0; i < EV_MAX; i++) { + if (libevdev_has_event_type(dev, i)) + printf("\tevent type %ld: %s\n", i, libevdev_event_type_get_name(i)); + } +} + +void handle_event(struct input_event ev, struct gamepad *pad) { + switch (ev.type) { + case EV_KEY: + if (ev.code < BTN_MISC) + return; + printf("button %d %s\n", pad->button_map[ev.code], ev.value ? "pressed" : "released"); + struct button *b = &pad->buttons[pad->button_map[ev.code]]; + if (!b->set) + return; + zwp_virtual_keyboard_v1_key(kb, ev.time.tv_sec / 1000, b->keycode, ev.value); + break; + case EV_ABS:; + struct axis *axis = &pad->axis[pad->axis_map[ev.code]]; + if (ev.value < axis->deadzone.max && ev.value > axis->deadzone.min) + return; + int value = ev.value - axis->old_value; + if (value > 5000 || value < -5000) { + axis->old_value = ev.value; + return; + } + + switch (ev.code) { + case ABS_HAT1X: + zwlr_virtual_pointer_v1_motion(ptr, ev.time.tv_sec / 1000, value, 0); + axis->old_value = ev.value; + break; + case ABS_HAT1Y: + zwlr_virtual_pointer_v1_motion(ptr, ev.time.tv_sec / 1000, 0, -value); + axis->old_value = ev.value; + break; + } + break; + } +} + +int main(int argc, char *argv[argc]) { + if (argc < 2) + return EXIT_FAILURE; + + FILE *config_file = fopen(argv[1], "r"); + if (!config_file) + return EXIT_FAILURE; + + dpy = wl_display_connect(NULL); + if (!dpy) { + fputs("failed to connect to wayland\n", stderr); + return EXIT_FAILURE; + } + + reg = wl_display_get_registry(dpy); + wl_registry_add_listener(reg, ®_listener, NULL); + wl_display_roundtrip(dpy); + + if (!ptr_manager) { + fputs("failed to get ptr manager\n", stderr); + return EXIT_FAILURE; + } + + if (!kb_manager) { + fputs("failed to get kb manager\n", stderr); + return EXIT_FAILURE; + } + + ptr = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(ptr_manager, NULL); + if (!ptr) { + fputs("failed to get ptr\n", stderr); + return EXIT_FAILURE; + } + + kb = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(kb_manager, seat); + if (!kb) { + fputs("failed to get kb\n", stderr); + return EXIT_FAILURE; + } + + int rc; + struct input_event ev; + struct config *config = load_config(config_file); + do { + wl_display_roundtrip(dpy); + for (size_t i = 0; i < config->npads; i++) { + rc = libevdev_next_event(config->pads[i].dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + if (rc == 0) + handle_event(ev, &config->pads[i]); + } + } while (rc == 1 || rc == 0 || rc == -EAGAIN); + + return 0; +} |