diff options
Diffstat (limited to 'kwin-type.c')
| -rw-r--r-- | kwin-type.c | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/kwin-type.c b/kwin-type.c new file mode 100644 index 0000000..00f2369 --- /dev/null +++ b/kwin-type.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#define _GNU_SOURCE +#include <err.h> +#include <locale.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <wchar.h> +#include <wayland-client.h> +#include <xkbcommon/xkbcommon.h> + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +/* https://invent.kde.org/libraries/plasma-wayland-protocols/-/blob/master/src/protocols/fake-input.xml */ +static const struct wl_message input_methods[] = { + { "authenticate", "ss", NULL }, + { "pointer_motion", "ff", NULL }, + { "button", "uu", NULL }, + { "axis", "uf", NULL }, + { "touch_down", "2uff", NULL }, + { "touch_motion", "2uff", NULL }, + { "touch_up", "2u", NULL }, + { "touch_cancel", "2", NULL }, + { "touch_frame", "2", NULL }, + { "pointer_motion_absolute", "3ff", NULL }, + { "keyboard_key", "4uu", NULL }, + { "destroy", "5", NULL }, + { "keyboard_keysym", "6uu", NULL }, +}; +static const struct wl_interface input_interface = { "org_kde_kwin_fake_input", 6, ARRAY_SIZE(input_methods), input_methods, 0, NULL }; + +/* https://invent.kde.org/libraries/plasma-wayland-protocols/-/blob/master/src/protocols/keystate.xml */ +static const struct wl_message state_methods[] = { { "fetchStates", "", NULL } }; +static const struct wl_message state_events[] = { { "stateChanged", "uu", NULL } }; +static const struct wl_interface state_interface = { "org_kde_kwin_keystate", 1, ARRAY_SIZE(state_methods), state_methods, ARRAY_SIZE(state_events), state_events }; +enum { AUTHENTICATE = 0, KEY = 10, DESTROY = 11, KEYSYM = 12 }; +enum { RELEASED = 0, PRESSED = 1, CAPS_LOCK_KEY = 58 }; + +static volatile sig_atomic_t interrupted; + +static void on_signal(int signo) +{ + interrupted = signo; +} + +static void state_changed(void *data, struct wl_proxy *, uint32_t key, uint32_t state) +{ + bool **caps = data; + if (key == 0) { + *caps[0] = true; + *caps[1] = state != 0; + } +} + +static void registry_global(void *data, struct wl_registry *, uint32_t name, const char *interface, uint32_t version) +{ + uint32_t **names = data; + if (!strcmp(interface, input_interface.name) && version >= (uint32_t)input_interface.version) + *names[0] = name; + else if (!strcmp(interface, state_interface.name)) + *names[1] = name; +} + +static void registry_global_remove(void *, struct wl_registry *, uint32_t) +{ +} + +static void type_key(struct wl_proxy *input, uint32_t request, uint32_t key) +{ + wl_proxy_marshal_flags(input, request, NULL, input_interface.version, 0, key, PRESSED); + wl_proxy_marshal_flags(input, request, NULL, input_interface.version, 0, key, RELEASED); +} + +int main(int argc, char **argv) +{ + bool caps_known = false, caps_locked = false, restore_caps = false, ret = false; + uint32_t input_name = 0, keystate_name = 0; + struct wl_proxy *input, *keystate; + struct wl_registry *registry; + struct wl_display *display; + xkb_keysym_t keysym; + wint_t cp; + + if (argc != 1) + errx(1, "usage: %s < text.txt", argv[0]); + if (!setlocale(LC_CTYPE, "C.UTF-8")) + errx(1, "cannot select C.UTF-8 locale"); + if (sigaction(SIGINT, &(struct sigaction){ .sa_handler = on_signal }, NULL) < 0 || + sigaction(SIGTERM, &(struct sigaction){ .sa_handler = on_signal }, NULL) < 0 || + sigaction(SIGHUP, &(struct sigaction){ .sa_handler = on_signal }, NULL) < 0) { + warn("installing signal handlers"); + goto err; + } + + display = wl_display_connect(NULL); + if (!display) { + warn("cannot connect to display"); + goto err; + } + registry = wl_display_get_registry(display); + if (!registry) { + warn("getting registry"); + goto err_display; + } + wl_registry_add_listener(registry, + &(const struct wl_registry_listener){ registry_global, registry_global_remove }, + (uint32_t *[]){ &input_name, &keystate_name }); + if (wl_display_roundtrip(display) < 0) { + warn("synchronizing display"); + goto err_registry; + } + if (!input_name || !keystate_name) { + warnx("%s-v%d and %s-v%d protocols are required", + input_interface.name, input_interface.version, state_interface.name, state_interface.version); + goto err_registry; + } + keystate = wl_registry_bind(registry, keystate_name, &state_interface, state_interface.version); + if (!keystate) { + warn("binding key-state interface"); + goto err_registry; + } + wl_proxy_add_listener(keystate, + (void (*[])(void)){ (void (*)(void))state_changed }, + (bool *[]){ &caps_known, &caps_locked }); + input = wl_registry_bind(registry, input_name, &input_interface, input_interface.version); + if (!input) { + warn("binding fake-input interface"); + goto err_keystate; + } + wl_proxy_marshal_flags(input, AUTHENTICATE, NULL, input_interface.version, 0, "kwin-type", "Type text from stdin"); + wl_proxy_marshal_flags(keystate, 0, NULL, state_interface.version, 0); + if (wl_display_roundtrip(display) < 0) { + warn("synchronizing display"); + goto err_input; + } + if (!caps_known) { + warnx("cannot read caps lock state"); + goto err_input; + } + + /* Chrome has some complicated keyboard refreshing logic. We could patch upstream kwin to + * hack around it, or fix Chrome itself, but it's probably easier to just delay briefly + * here. Probably other things will benefit from this hack too. Oh well. */ + nanosleep(&(struct timespec){ .tv_nsec = 100000000 }, NULL); + + if (interrupted) + goto err_input; + if (caps_locked) { + restore_caps = true; + type_key(input, KEY, CAPS_LOCK_KEY); + } + + while (!interrupted && (cp = fgetwc(stdin)) != WEOF) { + if (interrupted) + break; + keysym = xkb_utf32_to_keysym(cp); + if (keysym == XKB_KEY_Linefeed) + keysym = XKB_KEY_Return; + type_key(input, KEYSYM, keysym); + } + if (!interrupted && ferror(stdin)) + warn("reading stdin"); + else + ret = true; + +err_input: + if (restore_caps) + type_key(input, KEY, CAPS_LOCK_KEY); + wl_proxy_marshal_flags(input, DESTROY, NULL, input_interface.version, WL_MARSHAL_FLAG_DESTROY); + if (wl_display_roundtrip(display) < 0) { + warn("synchronizing display"); + ret = false; + } +err_keystate: + wl_proxy_destroy(keystate); +err_registry: + wl_registry_destroy(registry); +err_display: + wl_display_disconnect(display); +err: + return interrupted ? (128 + interrupted) : (ret ? 0 : 1); +} |
