// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2026 Jason A. Donenfeld . All Rights Reserved. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #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); }