summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnna (navi) Figueiredo Gomes <navi@vlhl.dev>2024-06-06 21:20:38 +0200
committerAnna (navi) Figueiredo Gomes <navi@vlhl.dev>2024-06-06 21:20:38 +0200
commitaf49f3e693dfd80430a9606b2c4c836702b36af0 (patch)
tree498adf3d7eeec0c9e5910ed251f516b3797784af
intial prototypeHEADmain
-rw-r--r--config_file6
-rw-r--r--meson.build21
-rw-r--r--protocols/virtual-keyboard-unstable-v1.xml113
-rw-r--r--protocols/wlr-virtual-pointer-unstable-v1.xml152
-rw-r--r--src/waypad.c341
-rw-r--r--todo4
6 files changed, 637 insertions, 0 deletions
diff --git a/config_file b/config_file
new file mode 100644
index 0000000..f24f576
--- /dev/null
+++ b/config_file
@@ -0,0 +1,6 @@
+/dev/input/event9 button 5 key h
+/dev/input/event9 button 3 key j
+/dev/input/event9 button 6 key k
+/dev/input/event9 button 4 key l
+
+/dev/input/event9 button 8 key q
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..e349b81
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,21 @@
+project('waypad', 'c',
+ version : '0.1',
+ default_options : ['warning_level=3'])
+
+wl = import('unstable-wayland')
+
+files = [
+ 'src/waypad.c',
+ wl.scan_xml(files('protocols/virtual-keyboard-unstable-v1.xml')),
+ wl.scan_xml(files('protocols/wlr-virtual-pointer-unstable-v1.xml'))
+]
+
+deps = [
+ dependency('libevdev'),
+ dependency('wayland-client'),
+ dependency('xkbcommon')
+]
+
+exe = executable('waypad', files,
+ dependencies: deps,
+ install : true)
diff --git a/protocols/virtual-keyboard-unstable-v1.xml b/protocols/virtual-keyboard-unstable-v1.xml
new file mode 100644
index 0000000..5095c91
--- /dev/null
+++ b/protocols/virtual-keyboard-unstable-v1.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="virtual_keyboard_unstable_v1">
+ <copyright>
+ Copyright © 2008-2011 Kristian Høgsberg
+ Copyright © 2010-2013 Intel Corporation
+ Copyright © 2012-2013 Collabora, Ltd.
+ Copyright © 2018 Purism SPC
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="zwp_virtual_keyboard_v1" version="1">
+ <description summary="virtual keyboard">
+ The virtual keyboard provides an application with requests which emulate
+ the behaviour of a physical keyboard.
+
+ This interface can be used by clients on its own to provide raw input
+ events, or it can accompany the input method protocol.
+ </description>
+
+ <request name="keymap">
+ <description summary="keyboard mapping">
+ Provide a file descriptor to the compositor which can be
+ memory-mapped to provide a keyboard mapping description.
+
+ Format carries a value from the keymap_format enumeration.
+ </description>
+ <arg name="format" type="uint" summary="keymap format"/>
+ <arg name="fd" type="fd" summary="keymap file descriptor"/>
+ <arg name="size" type="uint" summary="keymap size, in bytes"/>
+ </request>
+
+ <enum name="error">
+ <entry name="no_keymap" value="0" summary="No keymap was set"/>
+ </enum>
+
+ <request name="key">
+ <description summary="key event">
+ A key was pressed or released.
+ The time argument is a timestamp with millisecond granularity, with an
+ undefined base. All requests regarding a single object must share the
+ same clock.
+
+ Keymap must be set before issuing this request.
+
+ State carries a value from the key_state enumeration.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="key" type="uint" summary="key that produced the event"/>
+ <arg name="state" type="uint" summary="physical state of the key"/>
+ </request>
+
+ <request name="modifiers">
+ <description summary="modifier and group state">
+ Notifies the compositor that the modifier and/or group state has
+ changed, and it should update state.
+
+ The client should use wl_keyboard.modifiers event to synchronize its
+ internal state with seat state.
+
+ Keymap must be set before issuing this request.
+ </description>
+ <arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
+ <arg name="mods_latched" type="uint" summary="latched modifiers"/>
+ <arg name="mods_locked" type="uint" summary="locked modifiers"/>
+ <arg name="group" type="uint" summary="keyboard layout"/>
+ </request>
+
+ <request name="destroy" type="destructor" since="1">
+ <description summary="destroy the virtual keyboard keyboard object"/>
+ </request>
+ </interface>
+
+ <interface name="zwp_virtual_keyboard_manager_v1" version="1">
+ <description summary="virtual keyboard manager">
+ A virtual keyboard manager allows an application to provide keyboard
+ input events as if they came from a physical keyboard.
+ </description>
+
+ <enum name="error">
+ <entry name="unauthorized" value="0" summary="client not authorized to use the interface"/>
+ </enum>
+
+ <request name="create_virtual_keyboard">
+ <description summary="Create a new virtual keyboard">
+ Creates a new virtual keyboard associated to a seat.
+
+ If the compositor enables a keyboard to perform arbitrary actions, it
+ should present an error when an untrusted client requests a new
+ keyboard.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ <arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/>
+ </request>
+ </interface>
+</protocol>
diff --git a/protocols/wlr-virtual-pointer-unstable-v1.xml b/protocols/wlr-virtual-pointer-unstable-v1.xml
new file mode 100644
index 0000000..ea243e7
--- /dev/null
+++ b/protocols/wlr-virtual-pointer-unstable-v1.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_virtual_pointer_unstable_v1">
+ <copyright>
+ Copyright © 2019 Josef Gajdusek
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="zwlr_virtual_pointer_v1" version="2">
+ <description summary="virtual pointer">
+ This protocol allows clients to emulate a physical pointer device. The
+ requests are mostly mirror opposites of those specified in wl_pointer.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_axis" value="0"
+ summary="client sent invalid axis enumeration value" />
+ <entry name="invalid_axis_source" value="1"
+ summary="client sent invalid axis source enumeration value" />
+ </enum>
+
+ <request name="motion">
+ <description summary="pointer relative motion event">
+ The pointer has moved by a relative amount to the previous request.
+
+ Values are in the global compositor space.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="dx" type="fixed" summary="displacement on the x-axis"/>
+ <arg name="dy" type="fixed" summary="displacement on the y-axis"/>
+ </request>
+
+ <request name="motion_absolute">
+ <description summary="pointer absolute motion event">
+ The pointer has moved in an absolute coordinate frame.
+
+ Value of x can range from 0 to x_extent, value of y can range from 0
+ to y_extent.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="x" type="uint" summary="position on the x-axis"/>
+ <arg name="y" type="uint" summary="position on the y-axis"/>
+ <arg name="x_extent" type="uint" summary="extent of the x-axis"/>
+ <arg name="y_extent" type="uint" summary="extent of the y-axis"/>
+ </request>
+
+ <request name="button">
+ <description summary="button event">
+ A button was pressed or released.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="button" type="uint" summary="button that produced the event"/>
+ <arg name="state" type="uint" enum="wl_pointer.button_state" summary="physical state of the button"/>
+ </request>
+
+ <request name="axis">
+ <description summary="axis event">
+ Scroll and other axis requests.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
+ <arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
+ </request>
+
+ <request name="frame">
+ <description summary="end of a pointer event sequence">
+ Indicates the set of events that logically belong together.
+ </description>
+ </request>
+
+ <request name="axis_source">
+ <description summary="axis source event">
+ Source information for scroll and other axis.
+ </description>
+ <arg name="axis_source" type="uint" enum="wl_pointer.axis_source" summary="source of the axis event"/>
+ </request>
+
+ <request name="axis_stop">
+ <description summary="axis stop event">
+ Stop notification for scroll and other axes.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="axis" type="uint" enum="wl_pointer.axis" summary="the axis stopped with this event"/>
+ </request>
+
+ <request name="axis_discrete">
+ <description summary="axis click event">
+ Discrete step information for scroll and other axes.
+
+ This event allows the client to extend data normally sent using the axis
+ event with discrete value.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
+ <arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
+ <arg name="discrete" type="int" summary="number of steps"/>
+ </request>
+
+ <request name="destroy" type="destructor" since="1">
+ <description summary="destroy the virtual pointer object"/>
+ </request>
+ </interface>
+
+ <interface name="zwlr_virtual_pointer_manager_v1" version="2">
+ <description summary="virtual pointer manager">
+ This object allows clients to create individual virtual pointer objects.
+ </description>
+
+ <request name="create_virtual_pointer">
+ <description summary="Create a new virtual pointer">
+ Creates a new virtual pointer. The optional seat is a suggestion to the
+ compositor.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
+ <arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
+ </request>
+
+ <request name="destroy" type="destructor" since="1">
+ <description summary="destroy the virtual pointer manager"/>
+ </request>
+
+ <!-- Version 2 additions -->
+ <request name="create_virtual_pointer_with_output" since="2">
+ <description summary="Create a new virtual pointer">
+ Creates a new virtual pointer. The seat and the output arguments are
+ optional. If the seat argument is set, the compositor should assign the
+ input device to the requested seat. If the output argument is set, the
+ compositor should map the input device to the requested output.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ <arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
+ </request>
+ </interface>
+</protocol>
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;
+}
diff --git a/todo b/todo
new file mode 100644
index 0000000..c1fbf9f
--- /dev/null
+++ b/todo
@@ -0,0 +1,4 @@
+configuration via scfg
+proper stick support
+exec as well as key and mouse movement
+additional bindings depending on appid and fullscreen status