summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/waypad.c341
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, &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;
+}