From af49f3e693dfd80430a9606b2c4c836702b36af0 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Thu, 6 Jun 2024 21:20:38 +0200 Subject: intial prototype --- config_file | 6 + meson.build | 21 ++ protocols/virtual-keyboard-unstable-v1.xml | 113 +++++++++ protocols/wlr-virtual-pointer-unstable-v1.xml | 152 ++++++++++++ src/waypad.c | 341 ++++++++++++++++++++++++++ todo | 4 + 6 files changed, 637 insertions(+) create mode 100644 config_file create mode 100644 meson.build create mode 100644 protocols/virtual-keyboard-unstable-v1.xml create mode 100644 protocols/wlr-virtual-pointer-unstable-v1.xml create mode 100644 src/waypad.c create mode 100644 todo 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 @@ + + + + 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. + + + + + 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. + + + + + 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. + + + + + + + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + + + + + + A virtual keyboard manager allows an application to provide keyboard + input events as if they came from a physical 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. + + + + + + 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 @@ + + + + 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. + + + + + This protocol allows clients to emulate a physical pointer device. The + requests are mostly mirror opposites of those specified in wl_pointer. + + + + + + + + + + The pointer has moved by a relative amount to the previous request. + + Values are in the global compositor space. + + + + + + + + + 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. + + + + + + + + + + + A button was pressed or released. + + + + + + + + + Scroll and other axis requests. + + + + + + + + + Indicates the set of events that logically belong together. + + + + + + Source information for scroll and other axis. + + + + + + + Stop notification for scroll and other axes. + + + + + + + + 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. + + + + + + + + + + + + + + + This object allows clients to create individual virtual pointer objects. + + + + + Creates a new virtual pointer. The optional seat is a suggestion to the + compositor. + + + + + + + + + + + + + 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. + + + + + + + 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 +#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; +} 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 -- cgit v1.2.3