aboutsummaryrefslogtreecommitdiffstats
path: root/kwin-type.c
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2026-10-26 05:38:41 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2026-10-26 14:39:00 +0100
commit1df38bfc2cb16df5e252730b8f95ecd184356e7d (patch)
treef290afafc613de042b03f8da73afe30b2379814f /kwin-type.c
downloadkwin-type-master.tar.xz
kwin-type-master.zip
Initial commitHEADv0.1master
Diffstat (limited to 'kwin-type.c')
-rw-r--r--kwin-type.c188
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);
+}