#include #include #include #include #include #include #include #include #include #include #include #include #include #include #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, " = %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 {[ %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; }