summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authortokyo4j2024-05-11 16:55:19 +0900
committertokyo4j2024-05-11 16:55:29 +0900
commitab3f16a1f0af8db52d88b6b69695e2a2b548fc14 (patch)
treef9d099d2a7e2881dd8c9eec4255dfaf02ac83eb5
parent45677cfeb3f2cff92291ec5173247c2d977cb410 (diff)
downloadaur-ab3f16a1f0af8db52d88b6b69695e2a2b548fc14.tar.gz
Update to 0.7.2
-rw-r--r--.SRCINFO10
-rw-r--r--.gitignore4
-rw-r--r--0001-IME-support-text-input-v1.patch433
-rw-r--r--PKGBUILD10
-rw-r--r--ime-popup-text-input-v1.patch1236
5 files changed, 447 insertions, 1246 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 78d9c041ea2f..4d68dd819e2b 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,6 +1,6 @@
pkgbase = labwc-im
pkgdesc = stacking wayland compositor with look and feel from openbox (with IME, IME popups and minimal text-input-v1 support)
- pkgver = 0.7.1
+ pkgver = 0.7.2
pkgrel = 1
url = https://github.com/labwc/labwc
arch = x86_64
@@ -19,9 +19,9 @@ pkgbase = labwc-im
optdepends = bemenu: default launcher via Alt+F3
provides = labwc
conflicts = labwc
- source = labwc-0.7.1.tar.gz::https://github.com/labwc/labwc/archive/0.7.1.tar.gz
- source = ime-popup-text-input-v1.patch
- b2sums = 55e5539953edaccbb8b3c991b7cbc362c1783c0eb49ba92f3b135b95f2462226073d61a4122171662f63419b21141088da9a742a9b320e55e868fb6e2049d00a
- b2sums = 997f5d03e9e82178b47415a480bdf1ac171b6ccaf9e8c582b77c03bfa1b5fe1cace4b5515f9ae88e4fe062c0e35ed525cfc4495f8226a866ba439ab1fdf8a19f
+ source = labwc-0.7.2.tar.gz::https://github.com/labwc/labwc/archive/0.7.2.tar.gz
+ source = 0001-IME-support-text-input-v1.patch
+ b2sums = 68d47d316f5dd19252db4ea11c313a9f8da560b25ff16623ee63f8ce88eccbea779e6a9f37bbf343a4926afecf40e3c9b1038833a171fe4acd90cf8b6b9035c0
+ b2sums = 574c56a3518dfd2aef5efa7cbb1b00a2c4bbaa6697a25a39127d5505a33b7c3baed9cb77b1556df1fd448b556be0c088fe145beec690d9446d781c19de8c76ed
pkgname = labwc-im
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000000..9ddb7842226e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+pkg
+src
+*.tar.gz
+*pkg.tar.zst \ No newline at end of file
diff --git a/0001-IME-support-text-input-v1.patch b/0001-IME-support-text-input-v1.patch
new file mode 100644
index 000000000000..0d7b7e1f2164
--- /dev/null
+++ b/0001-IME-support-text-input-v1.patch
@@ -0,0 +1,433 @@
+From ba5683c721db68c81d50bd7d2dd550d5ea5b1592 Mon Sep 17 00:00:00 2001
+From: tokyo4j <hrak1529@gmail.com>
+Date: Fri, 23 Feb 2024 03:57:01 +0900
+Subject: [PATCH] IME: support text-input-v1
+
+---
+ include/input/ime.h | 1 +
+ protocols/meson.build | 1 +
+ src/input/ime.c | 342 +++++++++++++++++++++++++++++++++++++++++-
+ 3 files changed, 343 insertions(+), 1 deletion(-)
+
+diff --git a/include/input/ime.h b/include/input/ime.h
+index 06dd16ff..24638094 100644
+--- a/include/input/ime.h
++++ b/include/input/ime.h
+@@ -50,6 +50,7 @@ struct input_method_relay {
+ struct text_input {
+ struct input_method_relay *relay;
+ struct wlr_text_input_v3 *input;
++ bool v1;
+ struct wl_list link;
+
+ struct wl_listener enable;
+diff --git a/protocols/meson.build b/protocols/meson.build
+index acaf0215..ffb371ac 100644
+--- a/protocols/meson.build
++++ b/protocols/meson.build
+@@ -20,6 +20,7 @@ server_protocols = [
+ wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml',
+ wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml',
+ wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml',
++ wl_protocol_dir / 'unstable/text-input/text-input-unstable-v1.xml',
+ 'wlr-layer-shell-unstable-v1.xml',
+ 'wlr-input-inhibitor-unstable-v1.xml',
+ 'wlr-output-power-management-unstable-v1.xml',
+diff --git a/src/input/ime.c b/src/input/ime.c
+index b3d6410d..9272c559 100644
+--- a/src/input/ime.c
++++ b/src/input/ime.c
+@@ -6,6 +6,7 @@
+ #include "input/ime.h"
+ #include "node.h"
+ #include "view.h"
++#include "text-input-unstable-v1-protocol.h"
+
+ #define SAME_CLIENT(wlr_obj1, wlr_obj2) \
+ (wl_resource_get_client((wlr_obj1)->resource) \
+@@ -25,6 +26,276 @@ is_keyboard_emulated_by_input_method(struct wlr_keyboard *keyboard,
+ return virtual_keyboard && SAME_CLIENT(virtual_keyboard, input_method);
+ }
+
++/*
++ * ----------------------------------------------------------------------------
++ * text-input-unstable-v1 protocol support
++ * ----------------------------------------------------------------------------
++ * To reuse code for text-input-unstable-v3, zwp_text_input_v1 interface is
++ * wrapped as wlr_text_input_v3, by converting zwp_text_input_v1-specific
++ * events and emitting them as wlr_text_input_v3's events. When
++ * zwp_text_input_manager_v1.create_text_input event is received, a wrapped
++ * wlr_text_input_v3 is created and added to
++ * wlr_text_input_manager_v3.text_inputs.
++ * ----------------------------------------------------------------------------
++ */
++
++struct text_input_manager_v1 {
++ struct wl_global *global;
++ struct wlr_text_input_manager_v3 *wlr_manager_v3;
++ struct wl_listener display_destroy;
++};
++
++static void
++text_input_v1_clear_focused_surface(struct wlr_text_input_v3 *text_input)
++{
++ if (text_input->focused_surface) {
++ wl_list_remove(&text_input->surface_destroy.link);
++ }
++ text_input->focused_surface = NULL;
++}
++
++static void
++text_input_v1_clear_seat(struct wlr_text_input_v3 *text_input)
++{
++ if (text_input->seat) {
++ wl_list_remove(&text_input->seat_destroy.link);
++ }
++ text_input->seat = NULL;
++}
++
++static void
++text_input_v1_destroy(struct wlr_text_input_v3 *text_input)
++{
++ wl_signal_emit_mutable(&text_input->events.destroy, NULL);
++
++ text_input_v1_clear_focused_surface(text_input);
++ text_input_v1_clear_seat(text_input);
++
++ wl_list_remove(&text_input->link);
++ free(text_input->current.surrounding.text);
++ free(text_input);
++}
++
++static void
++handle_text_input_v1_focused_surface_destroy(struct wl_listener *listener,
++ void *data)
++{
++ struct wlr_text_input_v3 *text_input =
++ wl_container_of(listener, text_input, surface_destroy);
++ text_input_v1_clear_focused_surface(text_input);
++}
++
++static void
++handle_text_input_v1_activate(struct wl_client *client,
++ struct wl_resource *resource, struct wl_resource *seat,
++ struct wl_resource *surface)
++{
++ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
++ struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat);
++
++ text_input->seat = seat_client->seat;
++ wl_signal_add(&seat_client->events.destroy, &text_input->seat_destroy);
++
++ text_input->current_enabled = true;
++ text_input->active_features = text_input->current.features;
++ wl_signal_emit_mutable(&text_input->events.enable, NULL);
++}
++
++static void
++handle_text_input_v1_deactivate(struct wl_client *client,
++ struct wl_resource *resource, struct wl_resource *seat)
++{
++ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
++
++ text_input_v1_clear_seat(text_input);
++
++ text_input->current_enabled = false;
++ text_input->active_features = 0;
++ wl_signal_emit_mutable(&text_input->events.disable, NULL);
++}
++
++static void
++handle_text_input_v1_reset(struct wl_client *client, struct wl_resource *resource)
++{
++ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
++ /* Preserve cursor rectangle to prevent flickering */
++ text_input->current = (struct wlr_text_input_v3_state){
++ .cursor_rectangle = text_input->current.cursor_rectangle,
++ .features = text_input->current.features
++ & WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE,
++ };
++ free(text_input->current.surrounding.text);
++ wl_signal_emit_mutable(&text_input->events.commit, NULL);
++}
++
++static void
++handle_text_input_v1_set_surrounding_text(struct wl_client *client,
++ struct wl_resource *resource, const char *text,
++ uint32_t cursor, uint32_t anchor)
++{
++ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
++
++ free(text_input->current.surrounding.text);
++ text_input->current.surrounding.text = xstrdup(text);
++ text_input->current.surrounding.cursor = cursor;
++ text_input->current.surrounding.anchor = anchor;
++ text_input->current.features |= WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT;
++ text_input->active_features = text_input->current.features;
++ wl_signal_emit_mutable(&text_input->events.commit, NULL);
++}
++
++static void
++handle_text_input_v1_set_cursor_rectangle(struct wl_client *client,
++ struct wl_resource *resource, int32_t x, int32_t y,
++ int32_t width, int32_t height)
++{
++ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
++ text_input->current.cursor_rectangle.x = x;
++ text_input->current.cursor_rectangle.y = y;
++ text_input->current.cursor_rectangle.width = width;
++ text_input->current.cursor_rectangle.height = height;
++ text_input->current.features |= WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE;
++ text_input->active_features = text_input->current.features;
++ wl_signal_emit_mutable(&text_input->events.commit, NULL);
++}
++
++static void
++handle_text_input_v1_commit_state(struct wl_client *client,
++ struct wl_resource *resource, uint32_t serial)
++{
++ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
++ text_input->current_serial = serial;
++}
++
++static void noop(void){}
++static const struct zwp_text_input_v1_interface text_input_impl = {
++ .activate = handle_text_input_v1_activate,
++ .deactivate = handle_text_input_v1_deactivate,
++ .show_input_panel = (__typeof__(text_input_impl.show_input_panel))noop,
++ .hide_input_panel = (__typeof__(text_input_impl.hide_input_panel))noop,
++ .reset = handle_text_input_v1_reset,
++ .set_surrounding_text = handle_text_input_v1_set_surrounding_text,
++ .set_content_type = (__typeof__(text_input_impl.set_content_type))noop,
++ .set_cursor_rectangle = handle_text_input_v1_set_cursor_rectangle,
++ .set_preferred_language = (__typeof__(text_input_impl.set_preferred_language))noop,
++ .commit_state = handle_text_input_v1_commit_state,
++ .invoke_action = (__typeof__(text_input_impl.invoke_action))noop,
++};
++
++static void
++handle_text_input_v1_resource_destroy(struct wl_resource *resource)
++{
++ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
++ if (!text_input) {
++ return;
++ }
++ text_input_v1_destroy(text_input);
++ wl_resource_set_user_data(resource, NULL);
++}
++
++static void
++handle_text_input_v1_seat_destroy(struct wl_listener *listener, void *data)
++{
++ struct wlr_text_input_v3 *text_input =
++ wl_container_of(listener, text_input, seat_destroy);
++ if (!text_input) {
++ return;
++ }
++ struct wl_resource *resource = text_input->resource;
++ text_input_v1_destroy(text_input);
++ wl_resource_set_user_data(resource, NULL);
++}
++
++static void
++handle_manager_v1_create_text_input(struct wl_client *client,
++ struct wl_resource *resource, uint32_t id)
++{
++ int version = wl_resource_get_version(resource);
++ struct wl_resource *text_input_resource = wl_resource_create(client,
++ &zwp_text_input_v1_interface, version, id);
++ struct text_input_manager_v1 *manager_v1 = wl_resource_get_user_data(resource);
++ struct wlr_text_input_manager_v3 *wlr_manager_v3 = manager_v1->wlr_manager_v3;
++
++ struct wlr_text_input_v3 *text_input = znew(*text_input);
++ text_input->resource = text_input_resource;
++
++ wl_signal_init(&text_input->events.commit);
++ wl_signal_init(&text_input->events.enable);
++ wl_signal_init(&text_input->events.disable);
++ wl_signal_init(&text_input->events.destroy);
++
++ wl_resource_set_implementation(text_input_resource, &text_input_impl,
++ text_input, handle_text_input_v1_resource_destroy);
++
++ text_input->seat_destroy.notify =
++ handle_text_input_v1_seat_destroy;
++ text_input->surface_destroy.notify =
++ handle_text_input_v1_focused_surface_destroy;
++
++ wl_list_insert(&wlr_manager_v3->text_inputs, &text_input->link);
++
++ wl_signal_emit_mutable(&wlr_manager_v3->events.text_input, text_input);
++}
++
++static const struct zwp_text_input_manager_v1_interface text_input_manager_v1_impl = {
++ .create_text_input = handle_manager_v1_create_text_input,
++};
++
++static void
++handle_manager_v1_bind(struct wl_client *wl_client, void *data,
++ uint32_t version, uint32_t id)
++{
++ struct text_input_manager_v1 *manager_v1 = data;
++
++ struct wl_resource *resource = wl_resource_create(wl_client,
++ &zwp_text_input_manager_v1_interface, version, id);
++ wl_resource_set_implementation(resource, &text_input_manager_v1_impl,
++ manager_v1, NULL);
++}
++
++static void
++handle_display_destroy(struct wl_listener *listener, void *data)
++{
++ struct text_input_manager_v1 *manager_v1 =
++ wl_container_of(listener, manager_v1, display_destroy);
++ wl_list_remove(&manager_v1->display_destroy.link);
++ wl_global_destroy(manager_v1->global);
++ free(manager_v1);
++}
++
++static void
++text_input_manager_v1_init(struct wl_display *display,
++ struct wlr_text_input_manager_v3 *wlr_manager_v3)
++{
++ struct text_input_manager_v1 *manager_v1 = znew(*manager_v1);
++ manager_v1->wlr_manager_v3 = wlr_manager_v3;
++ manager_v1->global = wl_global_create(display,
++ &zwp_text_input_manager_v1_interface, 1, manager_v1,
++ handle_manager_v1_bind);
++ manager_v1->display_destroy.notify = handle_display_destroy;
++ wl_display_add_destroy_listener(display, &manager_v1->display_destroy);
++}
++
++static void
++text_input_v1_send_enter(struct wlr_text_input_v3 *text_input,
++ struct wlr_surface *surface)
++{
++ assert(!text_input->focused_surface);
++ text_input->focused_surface = surface;
++ wl_signal_add(&text_input->focused_surface->events.destroy,
++ &text_input->surface_destroy);
++ zwp_text_input_v1_send_enter(text_input->resource,
++ surface->resource);
++}
++
++static void
++text_input_v1_send_leave(struct wlr_text_input_v3 *text_input)
++{
++ assert(text_input->focused_surface);
++ text_input_v1_clear_focused_surface(text_input);
++ zwp_text_input_v1_send_leave(text_input->resource);
++}
++
+ /*
+ * Get keyboard grab of the seat from keyboard if we should forward events
+ * to it.
+@@ -155,9 +426,16 @@ update_text_inputs_focused_surface(struct input_method_relay *relay)
+ continue;
+ }
+ if (input->focused_surface) {
++ if (text_input->v1)
++ text_input_v1_send_leave(input);
++ else
+ wlr_text_input_v3_send_leave(input);
+ }
+ if (new_focused_surface) {
++ if (text_input->v1)
++ text_input_v1_send_enter(input,
++ new_focused_surface);
++ else
+ wlr_text_input_v3_send_enter(input, new_focused_surface);
+ }
+ }
+@@ -264,6 +542,62 @@ handle_input_method_commit(struct wl_listener *listener, void *data)
+ return;
+ }
+
++ if (text_input->v1) {
++ /*
++ * For some reason, if I send commit_string after preedit_string,
++ * the text is doubled in the integrated terminal of VSCode.
++ */
++ if (input_method->current.commit_text) {
++ zwp_text_input_v1_send_commit_string(
++ text_input->input->resource,
++ text_input->input->current_serial,
++ input_method->current.commit_text);
++ }
++ if (input_method->current.preedit.text) {
++ int preedit_len = strlen(input_method->current.preedit.text);
++ zwp_text_input_v1_send_preedit_cursor(
++ text_input->input->resource,
++ input_method->current.preedit.cursor_begin
++ );
++ if (0 < input_method->current.preedit.cursor_begin) {
++ zwp_text_input_v1_send_preedit_styling(
++ text_input->input->resource, 0,
++ input_method->current.preedit.cursor_begin,
++ ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE);
++ }
++ if (input_method->current.preedit.cursor_begin
++ < input_method->current.preedit.cursor_end) {
++ zwp_text_input_v1_send_preedit_styling(
++ text_input->input->resource,
++ input_method->current.preedit.cursor_begin,
++ input_method->current.preedit.cursor_end
++ - input_method->current.preedit.cursor_begin,
++ ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT);
++ }
++ if (input_method->current.preedit.cursor_end < preedit_len) {
++ zwp_text_input_v1_send_preedit_styling(
++ text_input->input->resource,
++ input_method->current.preedit.cursor_end,
++ preedit_len - input_method->current.preedit.cursor_end,
++ ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE);
++ }
++ zwp_text_input_v1_send_preedit_string(
++ text_input->input->resource,
++ text_input->input->current_serial,
++ input_method->current.preedit.text,
++ input_method->current.preedit.text);
++ } else {
++ zwp_text_input_v1_send_preedit_cursor(
++ text_input->input->resource, 0);
++ zwp_text_input_v1_send_preedit_styling(
++ text_input->input->resource, 0, 0,
++ ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_NONE);
++ zwp_text_input_v1_send_preedit_string(
++ text_input->input->resource,
++ text_input->input->current_serial, "", "");
++ }
++ return;
++ }
+ if (input_method->current.preedit.text) {
+ wlr_text_input_v3_send_preedit_string(
+ text_input->input,
+@@ -520,11 +854,14 @@ handle_new_text_input(struct wl_listener *listener, void *data)
+ struct input_method_relay *relay =
+ wl_container_of(listener, relay, new_text_input);
+ struct wlr_text_input_v3 *wlr_text_input = data;
+- if (relay->seat->seat != wlr_text_input->seat) {
++ bool is_v1 = !strcmp("zwp_text_input_v1",
++ wl_resource_get_class(wlr_text_input->resource));
++ if (!is_v1 && relay->seat->seat != wlr_text_input->seat) {
+ return;
+ }
+
+ struct text_input *text_input = znew(*text_input);
++ text_input->v1 = is_v1;
+ text_input->input = wlr_text_input;
+ text_input->relay = relay;
+ wl_list_insert(&relay->text_inputs, &text_input->link);
+@@ -578,6 +915,9 @@ input_method_relay_create(struct seat *seat)
+ relay->popup_surface_destroy.notify = handle_popup_surface_destroy;
+ relay->popup_surface_commit.notify = handle_popup_surface_commit;
+
++ text_input_manager_v1_init(seat->server->wl_display,
++ seat->server->text_input_manager);
++
+ return relay;
+ }
+
+--
+2.45.0
+
diff --git a/PKGBUILD b/PKGBUILD
index aaea1246bd4a..dfe10970ba0f 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -3,7 +3,7 @@
# Based on labwc AUR from Lex Black <autumn-wind@web.de>
pkgname=labwc-im
-pkgver=0.7.1
+pkgver=0.7.2
pkgrel=1
pkgdesc='stacking wayland compositor with look and feel from openbox (with IME, IME popups and minimal text-input-v1 support)'
url="https://github.com/labwc/labwc"
@@ -15,13 +15,13 @@ optdepends=("bemenu: default launcher via Alt+F3")
conflicts=(labwc)
provides=(labwc)
source=(labwc-${pkgver}.tar.gz::"https://github.com/labwc/labwc/archive/${pkgver}.tar.gz"
- 'ime-popup-text-input-v1.patch')
-b2sums=('55e5539953edaccbb8b3c991b7cbc362c1783c0eb49ba92f3b135b95f2462226073d61a4122171662f63419b21141088da9a742a9b320e55e868fb6e2049d00a'
- '997f5d03e9e82178b47415a480bdf1ac171b6ccaf9e8c582b77c03bfa1b5fe1cace4b5515f9ae88e4fe062c0e35ed525cfc4495f8226a866ba439ab1fdf8a19f')
+ '0001-IME-support-text-input-v1.patch')
+b2sums=('68d47d316f5dd19252db4ea11c313a9f8da560b25ff16623ee63f8ce88eccbea779e6a9f37bbf343a4926afecf40e3c9b1038833a171fe4acd90cf8b6b9035c0'
+ '574c56a3518dfd2aef5efa7cbb1b00a2c4bbaa6697a25a39127d5505a33b7c3baed9cb77b1556df1fd448b556be0c088fe145beec690d9446d781c19de8c76ed')
prepare() {
cd "labwc-$pkgver"
- patch -Np1 -i ../ime-popup-text-input-v1.patch
+ patch -Np1 -i ../0001-IME-support-text-input-v1.patch
}
build() {
diff --git a/ime-popup-text-input-v1.patch b/ime-popup-text-input-v1.patch
deleted file mode 100644
index 97891abfe0aa..000000000000
--- a/ime-popup-text-input-v1.patch
+++ /dev/null
@@ -1,1236 +0,0 @@
-diff --git a/include/input/ime.h b/include/input/ime.h
-new file mode 100644
-index 0000000..0e13f38
---- /dev/null
-+++ b/include/input/ime.h
-@@ -0,0 +1,85 @@
-+/* SPDX-License-Identifier: GPL-2.0-only */
-+
-+#ifndef LABWC_IME_H
-+#define LABWC_IME_H
-+
-+#include <wlr/types/wlr_text_input_v3.h>
-+#include <wlr/types/wlr_input_method_v2.h>
-+#include "labwc.h"
-+
-+struct keyboard;
-+
-+/*
-+ * The relay structure manages the relationship between text-inputs and
-+ * input-method on a given seat. Multiple text-inputs may be bound to a relay,
-+ * but at most one will be "active" (commucating with input-method) at a time.
-+ * At most one input-method may be bound to the seat. When an input-method and
-+ * an active text-input is present, the relay passes messages between them.
-+ */
-+struct input_method_relay {
-+ struct seat *seat;
-+ struct wl_list text_inputs; /* struct text_input.link */
-+ struct wlr_input_method_v2 *input_method;
-+ struct wlr_surface *focused_surface;
-+ /*
-+ * Text-input which is enabled by the client and communicating with
-+ * input-method.
-+ * This must be NULL if input-method is not present.
-+ * Its client must be the same as that of focused_surface.
-+ */
-+ struct text_input *active_text_input;
-+
-+ struct wlr_input_popup_surface_v2 *popup_surface;
-+ struct wlr_scene_tree *popup_tree;
-+
-+ struct wl_listener new_text_input;
-+ struct wl_listener new_input_method;
-+
-+ struct wl_listener input_method_commit;
-+ struct wl_listener input_method_grab_keyboard;
-+ struct wl_listener input_method_destroy;
-+ struct wl_listener input_method_new_popup_surface;
-+
-+ struct wl_listener popup_surface_destroy;
-+ struct wl_listener popup_surface_commit;
-+
-+ struct wl_listener keyboard_grab_destroy;
-+ struct wl_listener focused_surface_destroy;
-+};
-+
-+struct text_input {
-+ struct input_method_relay *relay;
-+ struct wlr_text_input_v3 *input;
-+ bool v1;
-+ struct wl_list link;
-+
-+ struct wl_listener enable;
-+ struct wl_listener commit;
-+ struct wl_listener disable;
-+ struct wl_listener destroy;
-+};
-+
-+/*
-+ * Forward key event to keyboard grab of the seat from the keyboard
-+ * if the keyboard grab exists.
-+ * Returns true if the key event was forwarded.
-+ */
-+bool input_method_keyboard_grab_forward_key(struct keyboard *keyboard,
-+ struct wlr_keyboard_key_event *event);
-+
-+/*
-+ * Forward modifier state to keyboard grab of the seat from the keyboard
-+ * if the keyboard grab exists.
-+ * Returns true if the modifier state was forwarded.
-+ */
-+bool input_method_keyboard_grab_forward_modifiers(struct keyboard *keyboard);
-+
-+struct input_method_relay *input_method_relay_create(struct seat *seat);
-+
-+void input_method_relay_finish(struct input_method_relay *relay);
-+
-+/* Updates currently focused surface. Surface must belong to the same seat. */
-+void input_method_relay_set_focus(struct input_method_relay *relay,
-+ struct wlr_surface *surface);
-+
-+#endif
-diff --git a/include/labwc.h b/include/labwc.h
-index 70cef94..abe32a6 100644
---- a/include/labwc.h
-+++ b/include/labwc.h
-@@ -40,10 +40,13 @@
- #include <wlr/types/wlr_virtual_pointer_v1.h>
- #include <wlr/types/wlr_virtual_keyboard_v1.h>
- #include <wlr/types/wlr_tearing_control_v1.h>
-+#include <wlr/types/wlr_text_input_v3.h>
-+#include <wlr/types/wlr_input_method_v2.h>
- #include <wlr/util/log.h>
- #include "config/keybind.h"
- #include "config/rcxml.h"
- #include "input/cursor.h"
-+#include "input/ime.h"
- #include "regions.h"
- #include "session-lock.h"
- #if HAVE_NLS
-@@ -120,6 +123,8 @@ struct seat {
- /* if set, views cannot receive focus */
- struct wlr_layer_surface_v1 *focused_layer;
-
-+ struct input_method_relay *input_method_relay;
-+
- /**
- * pressed view/surface/node will usually be NULL and is only set on
- * button press while the mouse is over a view or surface, and reset
-@@ -324,6 +329,9 @@ struct server {
- struct wlr_tearing_control_manager_v1 *tearing_control;
- struct wl_listener tearing_new_object;
-
-+ struct wlr_input_method_manager_v2 *input_method_manager;
-+ struct wlr_text_input_manager_v3 *text_input_manager;
-+
- /* Set when in cycle (alt-tab) mode */
- struct osd_state {
- struct view *cycle_view;
-diff --git a/include/node.h b/include/node.h
-index 419aa99..6d44664 100644
---- a/include/node.h
-+++ b/include/node.h
-@@ -15,6 +15,7 @@ enum node_descriptor_type {
- LAB_NODE_DESC_XDG_POPUP,
- LAB_NODE_DESC_LAYER_SURFACE,
- LAB_NODE_DESC_LAYER_POPUP,
-+ LAB_NODE_DESC_INPUT_METHOD_POPUP,
- LAB_NODE_DESC_MENUITEM,
- LAB_NODE_DESC_TREE,
- LAB_NODE_DESC_SSD_BUTTON,
-diff --git a/protocols/meson.build b/protocols/meson.build
-index 0ec9154..60df3d4 100644
---- a/protocols/meson.build
-+++ b/protocols/meson.build
-@@ -19,6 +19,7 @@ server_protocols = [
- wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml',
- wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml',
- wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml',
-+ wl_protocol_dir / 'unstable/text-input/text-input-unstable-v1.xml',
- 'wlr-layer-shell-unstable-v1.xml',
- 'wlr-input-inhibitor-unstable-v1.xml',
- 'wlr-output-power-management-unstable-v1.xml',
-diff --git a/src/desktop.c b/src/desktop.c
-index 35d56b8..aac7f73 100644
---- a/src/desktop.c
-+++ b/src/desktop.c
-@@ -416,6 +416,11 @@ get_cursor_context(struct server *server)
- ret.type = LAB_SSD_CLIENT;
- ret.surface = get_surface_from_layer_node(node);
- return ret;
-+ case LAB_NODE_DESC_INPUT_METHOD_POPUP:
-+ ret.node = node;
-+ ret.type = LAB_SSD_CLIENT;
-+ ret.surface = lab_wlr_surface_from_node(node);
-+ return ret;
- case LAB_NODE_DESC_MENUITEM:
- /* Always return the top scene node for menu items */
- ret.node = node;
-diff --git a/src/input/ime.c b/src/input/ime.c
-new file mode 100644
-index 0000000..4e35400
---- /dev/null
-+++ b/src/input/ime.c
-@@ -0,0 +1,917 @@
-+// SPDX-License-Identifier: GPL-2.0-only
-+/* Based on Sway (https://github.com/swaywm/sway) */
-+
-+#include <assert.h>
-+#include "common/mem.h"
-+#include "input/ime.h"
-+#include "node.h"
-+#include "view.h"
-+#include "text-input-unstable-v1-protocol.h"
-+
-+#define SAME_CLIENT(wlr_obj1, wlr_obj2) \
-+ (wl_resource_get_client((wlr_obj1)->resource) \
-+ == wl_resource_get_client((wlr_obj2)->resource))
-+
-+/* ----------------------------------------------------------------------------
-+ * text-input-unstable-v1 protocol support
-+ * ----------------------------------------------------------------------------
-+ * To reuse code for text-input-unstable-v3, zwp_text_input_v1 interface is
-+ * wrapped as wlr_text_input_v3, by converting zwp_text_input_v1-specific
-+ * events and emitting them as wlr_text_input_v3's events. When
-+ * zwp_text_input_manager_v1.create_text_input event is received, a wrapped
-+ * wlr_text_input_v3 is created and added to
-+ * wlr_text_input_manager_v3.text_inputs.
-+ * ----------------------------------------------------------------------------
-+ */
-+
-+struct text_input_manager_v1 {
-+ struct wl_global *global;
-+ struct wlr_text_input_manager_v3 *wlr_manager_v3;
-+ struct wl_listener display_destroy;
-+};
-+
-+static void
-+text_input_v1_clear_focused_surface(struct wlr_text_input_v3 *text_input)
-+{
-+ if (text_input->focused_surface) {
-+ wl_list_remove(&text_input->surface_destroy.link);
-+ }
-+ text_input->focused_surface = NULL;
-+}
-+
-+static void
-+text_input_v1_clear_seat(struct wlr_text_input_v3 *text_input)
-+{
-+ if (text_input->seat) {
-+ wl_list_remove(&text_input->seat_destroy.link);
-+ }
-+ text_input->seat = NULL;
-+}
-+
-+static void
-+text_input_v1_destroy(struct wlr_text_input_v3 *text_input)
-+{
-+ wl_signal_emit_mutable(&text_input->events.destroy, NULL);
-+
-+ text_input_v1_clear_focused_surface(text_input);
-+ text_input_v1_clear_seat(text_input);
-+
-+ wl_list_remove(&text_input->link);
-+ free(text_input->current.surrounding.text);
-+ free(text_input);
-+}
-+
-+static void
-+handle_text_input_v1_focused_surface_destroy(struct wl_listener *listener,
-+ void *data)
-+{
-+ struct wlr_text_input_v3 *text_input =
-+ wl_container_of(listener, text_input, surface_destroy);
-+ text_input_v1_clear_focused_surface(text_input);
-+}
-+
-+static void
-+handle_text_input_v1_activate(struct wl_client *client,
-+ struct wl_resource *resource, struct wl_resource *seat,
-+ struct wl_resource *surface)
-+{
-+ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
-+ struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat);
-+
-+ text_input->seat = seat_client->seat;
-+ wl_signal_add(&seat_client->events.destroy, &text_input->seat_destroy);
-+
-+ text_input->current_enabled = true;
-+ text_input->active_features = text_input->current.features;
-+ wl_signal_emit_mutable(&text_input->events.enable, NULL);
-+}
-+
-+static void
-+handle_text_input_v1_deactivate(struct wl_client *client,
-+ struct wl_resource *resource, struct wl_resource *seat)
-+{
-+ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
-+
-+ text_input_v1_clear_seat(text_input);
-+
-+ text_input->current_enabled = false;
-+ text_input->active_features = 0;
-+ wl_signal_emit_mutable(&text_input->events.disable, NULL);
-+}
-+
-+static void
-+handle_text_input_v1_reset(struct wl_client *client, struct wl_resource *resource)
-+{
-+ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
-+ text_input->current = (struct wlr_text_input_v3_state){0};
-+ free(text_input->current.surrounding.text);
-+ wl_signal_emit_mutable(&text_input->events.commit, NULL);
-+}
-+
-+static void
-+handle_text_input_v1_set_surrounding_text(struct wl_client *client,
-+ struct wl_resource *resource, const char *text,
-+ uint32_t cursor, uint32_t anchor)
-+{
-+ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
-+
-+ free(text_input->current.surrounding.text);
-+ text_input->current.surrounding.text = xstrdup(text);
-+ text_input->current.surrounding.cursor = cursor;
-+ text_input->current.surrounding.anchor = anchor;
-+ text_input->current.features |= WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT;
-+ text_input->active_features = text_input->current.features;
-+ wl_signal_emit_mutable(&text_input->events.commit, NULL);
-+}
-+
-+static void
-+handle_text_input_v1_set_cursor_rectangle(struct wl_client *client,
-+ struct wl_resource *resource, int32_t x, int32_t y,
-+ int32_t width, int32_t height)
-+{
-+ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
-+ text_input->current.cursor_rectangle.x = x;
-+ text_input->current.cursor_rectangle.y = y;
-+ text_input->current.cursor_rectangle.width = width;
-+ text_input->current.cursor_rectangle.height = height;
-+ text_input->current.features |= WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE;
-+ text_input->active_features = text_input->current.features;
-+ wl_signal_emit_mutable(&text_input->events.commit, NULL);
-+}
-+
-+static void
-+handle_text_input_v1_commit_state(struct wl_client *client,
-+ struct wl_resource *resource, uint32_t serial)
-+{
-+ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
-+ text_input->current_serial = serial;
-+}
-+
-+static void noop(void){}
-+static const struct zwp_text_input_v1_interface text_input_impl = {
-+ .activate = handle_text_input_v1_activate,
-+ .deactivate = handle_text_input_v1_deactivate,
-+ .show_input_panel = (__typeof__(text_input_impl.show_input_panel))noop,
-+ .hide_input_panel = (__typeof__(text_input_impl.hide_input_panel))noop,
-+ .reset = handle_text_input_v1_reset,
-+ .set_surrounding_text = handle_text_input_v1_set_surrounding_text,
-+ .set_content_type = (__typeof__(text_input_impl.set_content_type))noop,
-+ .set_cursor_rectangle = handle_text_input_v1_set_cursor_rectangle,
-+ .set_preferred_language = (__typeof__(text_input_impl.set_preferred_language))noop,
-+ .commit_state = handle_text_input_v1_commit_state,
-+ .invoke_action = (__typeof__(text_input_impl.invoke_action))noop,
-+};
-+
-+static void
-+handle_text_input_v1_resource_destroy(struct wl_resource *resource)
-+{
-+ struct wlr_text_input_v3 *text_input = wl_resource_get_user_data(resource);
-+ if (!text_input) {
-+ return;
-+ }
-+ text_input_v1_destroy(text_input);
-+ wl_resource_set_user_data(resource, NULL);
-+}
-+
-+static void
-+handle_text_input_v1_seat_destroy(struct wl_listener *listener, void *data)
-+{
-+ struct wlr_text_input_v3 *text_input =
-+ wl_container_of(listener, text_input, seat_destroy);
-+ if (!text_input) {
-+ return;
-+ }
-+ struct wl_resource *resource = text_input->resource;
-+ text_input_v1_destroy(text_input);
-+ wl_resource_set_user_data(resource, NULL);
-+}
-+
-+static void
-+handle_manager_v1_create_text_input(struct wl_client *client,
-+ struct wl_resource *resource, uint32_t id)
-+{
-+ int version = wl_resource_get_version(resource);
-+ struct wl_resource *text_input_resource = wl_resource_create(client,
-+ &zwp_text_input_v1_interface, version, id);
-+ struct text_input_manager_v1 *manager_v1 = wl_resource_get_user_data(resource);
-+ struct wlr_text_input_manager_v3 *wlr_manager_v3 = manager_v1->wlr_manager_v3;
-+
-+ struct wlr_text_input_v3 *text_input = znew(*text_input);
-+ text_input->resource = text_input_resource;
-+
-+ wl_signal_init(&text_input->events.commit);
-+ wl_signal_init(&text_input->events.enable);
-+ wl_signal_init(&text_input->events.disable);
-+ wl_signal_init(&text_input->events.destroy);
-+
-+ wl_resource_set_implementation(text_input_resource, &text_input_impl,
-+ text_input, handle_text_input_v1_resource_destroy);
-+
-+ text_input->seat_destroy.notify =
-+ handle_text_input_v1_seat_destroy;
-+ text_input->surface_destroy.notify =
-+ handle_text_input_v1_focused_surface_destroy;
-+
-+ wl_list_insert(&wlr_manager_v3->text_inputs, &text_input->link);
-+
-+ wl_signal_emit_mutable(&wlr_manager_v3->events.text_input, text_input);
-+}
-+
-+static const struct zwp_text_input_manager_v1_interface text_input_manager_v1_impl = {
-+ .create_text_input = handle_manager_v1_create_text_input,
-+};
-+
-+static void
-+handle_manager_v1_bind(struct wl_client *wl_client, void *data,
-+ uint32_t version, uint32_t id)
-+{
-+ struct text_input_manager_v1 *manager_v1 = data;
-+
-+ struct wl_resource *resource = wl_resource_create(wl_client,
-+ &zwp_text_input_manager_v1_interface, version, id);
-+ wl_resource_set_implementation(resource, &text_input_manager_v1_impl,
-+ manager_v1, NULL);
-+}
-+
-+static void handle_display_destroy(struct wl_listener *listener, void *data)
-+{
-+ struct text_input_manager_v1 *manager_v1 =
-+ wl_container_of(listener, manager_v1, display_destroy);
-+ wl_list_remove(&manager_v1->display_destroy.link);
-+ wl_global_destroy(manager_v1->global);
-+ free(manager_v1);
-+}
-+
-+static void
-+text_input_manager_v1_init(struct wl_display *display,
-+ struct wlr_text_input_manager_v3 *wlr_manager_v3)
-+{
-+ struct text_input_manager_v1 *manager_v1 = znew(*manager_v1);
-+ manager_v1->wlr_manager_v3 = wlr_manager_v3;
-+ manager_v1->global = wl_global_create(display,
-+ &zwp_text_input_manager_v1_interface, 1, manager_v1,
-+ handle_manager_v1_bind);
-+ manager_v1->display_destroy.notify = handle_display_destroy;
-+ wl_display_add_destroy_listener(display, &manager_v1->display_destroy);
-+}
-+
-+static void
-+text_input_v1_send_enter(struct wlr_text_input_v3 *text_input,
-+ struct wlr_surface *surface)
-+{
-+ assert(!text_input->focused_surface);
-+ text_input->focused_surface = surface;
-+ wl_signal_add(&text_input->focused_surface->events.destroy,
-+ &text_input->surface_destroy);
-+ zwp_text_input_v1_send_enter(text_input->resource,
-+ surface->resource);
-+}
-+
-+static void
-+text_input_v1_send_leave(struct wlr_text_input_v3 *text_input)
-+{
-+ assert(text_input->focused_surface);
-+ text_input_v1_clear_focused_surface(text_input);
-+ zwp_text_input_v1_send_leave(text_input->resource);
-+}
-+
-+/*
-+ * Get keyboard grab of the seat from keyboard if we should forward events
-+ * to it.
-+ */
-+static struct wlr_input_method_keyboard_grab_v2 *
-+get_keyboard_grab(struct keyboard *keyboard)
-+{
-+ struct wlr_input_method_v2 *input_method =
-+ keyboard->base.seat->input_method_relay->input_method;
-+ if (!input_method || !input_method->keyboard_grab) {
-+ return NULL;
-+ }
-+
-+ /*
-+ * Input-methods often use virtual keyboard to send raw key signals
-+ * instead of sending encoded text via set_preedit_string and
-+ * commit_string. We should not forward those key events back to the
-+ * input-method so key events don't loop between the compositor and
-+ * the input-method.
-+ */
-+ struct wlr_virtual_keyboard_v1 *virtual_keyboard =
-+ wlr_input_device_get_virtual_keyboard(
-+ keyboard->base.wlr_input_device);
-+ if (virtual_keyboard && SAME_CLIENT(virtual_keyboard,
-+ input_method->keyboard_grab)) {
-+ return NULL;
-+ }
-+
-+ return input_method->keyboard_grab;
-+}
-+
-+bool
-+input_method_keyboard_grab_forward_modifiers(struct keyboard *keyboard)
-+{
-+ struct wlr_input_method_keyboard_grab_v2 *keyboard_grab =
-+ get_keyboard_grab(keyboard);
-+ if (keyboard_grab) {
-+ wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab,
-+ keyboard->wlr_keyboard);
-+ wlr_input_method_keyboard_grab_v2_send_modifiers(keyboard_grab,
-+ &keyboard->wlr_keyboard->modifiers);
-+ return true;
-+ } else {
-+ return false;
-+ }
-+}
-+
-+bool
-+input_method_keyboard_grab_forward_key(struct keyboard *keyboard,
-+ struct wlr_keyboard_key_event *event)
-+{
-+ struct wlr_input_method_keyboard_grab_v2 *keyboard_grab =
-+ get_keyboard_grab(keyboard);
-+ if (keyboard_grab) {
-+ wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab,
-+ keyboard->wlr_keyboard);
-+ wlr_input_method_keyboard_grab_v2_send_key(keyboard_grab,
-+ event->time_msec, event->keycode, event->state);
-+ return true;
-+ } else {
-+ return false;
-+ }
-+}
-+
-+/*
-+ * update_text_inputs_focused_surface() should be called beforehand to set
-+ * right text-inputs to choose from.
-+ */
-+static struct text_input *
-+get_active_text_input(struct input_method_relay *relay)
-+{
-+ if (!relay->input_method) {
-+ return NULL;
-+ }
-+ struct text_input *text_input;
-+ wl_list_for_each(text_input, &relay->text_inputs, link) {
-+ if (text_input->input->focused_surface
-+ && text_input->input->current_enabled) {
-+ return text_input;
-+ }
-+ }
-+ return NULL;
-+}
-+
-+/*
-+ * Updates active text-input and activates/deactivates the input-method if the
-+ * value is changed.
-+ */
-+static void
-+update_active_text_input(struct input_method_relay *relay)
-+{
-+ struct text_input *active_text_input = get_active_text_input(relay);
-+
-+ if (relay->input_method && relay->active_text_input
-+ != active_text_input) {
-+ if (active_text_input) {
-+ wlr_input_method_v2_send_activate(relay->input_method);
-+ } else {
-+ wlr_input_method_v2_send_deactivate(relay->input_method);
-+ }
-+ wlr_input_method_v2_send_done(relay->input_method);
-+ }
-+
-+ relay->active_text_input = active_text_input;
-+}
-+
-+/*
-+ * Updates focused surface of text-inputs and sends enter/leave events to
-+ * the text-inputs whose focused surface is changed.
-+ * When input-method is present, text-inputs whose client is the same as the
-+ * relay's focused surface also have that focused surface. Clients can then
-+ * send enable request on a text-input which has the focused surface to make
-+ * the text-input active and start communicating with input-method.
-+ */
-+static void
-+update_text_inputs_focused_surface(struct input_method_relay *relay)
-+{
-+ struct text_input *text_input;
-+ wl_list_for_each(text_input, &relay->text_inputs, link) {
-+ struct wlr_text_input_v3 *input = text_input->input;
-+
-+ struct wlr_surface *new_focused_surface;
-+ if (relay->input_method && relay->focused_surface
-+ && SAME_CLIENT(input, relay->focused_surface)) {
-+ new_focused_surface = relay->focused_surface;
-+ } else {
-+ new_focused_surface = NULL;
-+ }
-+
-+ if (input->focused_surface == new_focused_surface) {
-+ continue;
-+ }
-+ if (input->focused_surface) {
-+ if (text_input->v1)
-+ text_input_v1_send_leave(input);
-+ else
-+ wlr_text_input_v3_send_leave(input);
-+ }
-+ if (new_focused_surface) {
-+ if (text_input->v1)
-+ text_input_v1_send_enter(input,
-+ relay->focused_surface);
-+ else
-+ wlr_text_input_v3_send_enter(input, new_focused_surface);
-+ }
-+ }
-+}
-+
-+static void
-+update_popup_position(struct input_method_relay *relay)
-+{
-+ struct server *server = relay->seat->server;
-+ struct text_input *text_input = relay->active_text_input;
-+
-+ if (!text_input || !relay->focused_surface || !relay->popup_surface
-+ || !relay->popup_surface->surface->mapped) {
-+ return;
-+ }
-+
-+ struct wlr_box cursor_rect;
-+ struct wlr_xdg_surface *xdg_surface =
-+ wlr_xdg_surface_try_from_wlr_surface(relay->focused_surface);
-+ struct wlr_layer_surface_v1 *layer_surface =
-+ wlr_layer_surface_v1_try_from_wlr_surface(relay->focused_surface);
-+
-+ if ((text_input->input->current.features
-+ & WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE)
-+ && (xdg_surface || layer_surface)) {
-+ cursor_rect = text_input->input->current.cursor_rectangle;
-+
-+ struct wlr_scene_tree *tree = relay->focused_surface->data;
-+ int lx, ly;
-+ wlr_scene_node_coords(&tree->node, &lx, &ly);
-+ cursor_rect.x += lx;
-+ cursor_rect.y += ly;
-+
-+ if (xdg_surface) {
-+ /*
-+ * The position of cursor rectangle set by client
-+ * is in wl_surface-coordinate so we need to convert
-+ * it to offset relative to top-left corner of the
-+ * view (set by xdg_surface.set_window_geometry).
-+ */
-+ cursor_rect.x -= xdg_surface->current.geometry.x;
-+ cursor_rect.y -= xdg_surface->current.geometry.y;
-+ }
-+ } else {
-+ cursor_rect = (struct wlr_box){0};
-+ }
-+
-+ struct output *output =
-+ output_nearest_to(server, cursor_rect.x, cursor_rect.y);
-+ if (!output_is_usable(output)) {
-+ wlr_log(WLR_ERROR,
-+ "Cannot position IME popup (invalid output)");
-+ return;
-+ }
-+ struct wlr_box output_box;
-+ wlr_output_layout_get_box(server->output_layout, output->wlr_output,
-+ &output_box);
-+
-+ /* Use xdg-positioner utilities to position popup */
-+ struct wlr_xdg_positioner_rules rules = {
-+ .anchor_rect = cursor_rect,
-+ .anchor = XDG_POSITIONER_ANCHOR_BOTTOM_LEFT,
-+ .gravity = XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
-+ .size = {
-+ .width = relay->popup_surface->surface->current.width,
-+ .height = relay->popup_surface->surface->current.height,
-+ },
-+ .constraint_adjustment =
-+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y
-+ | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X,
-+ };
-+
-+ /* Calculate the popup position */
-+ struct wlr_box popup_box;
-+ wlr_xdg_positioner_rules_get_geometry(&rules, &popup_box);
-+ wlr_xdg_positioner_rules_unconstrain_box(&rules, &output_box, &popup_box);
-+
-+ wlr_scene_node_set_position(&relay->popup_tree->node,
-+ popup_box.x, popup_box.y);
-+
-+ wlr_input_popup_surface_v2_send_text_input_rectangle(
-+ relay->popup_surface, &(struct wlr_box){
-+ .x = cursor_rect.x - popup_box.x,
-+ .y = cursor_rect.y - popup_box.y,
-+ .width = cursor_rect.width,
-+ .height = cursor_rect.height,
-+ });
-+}
-+
-+static void
-+handle_input_method_commit(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay =
-+ wl_container_of(listener, relay, input_method_commit);
-+ struct wlr_input_method_v2 *input_method = data;
-+ assert(relay->input_method == input_method);
-+
-+ struct text_input *text_input = relay->active_text_input;
-+ if (!text_input) {
-+ return;
-+ }
-+
-+ if (text_input->v1) {
-+ /*
-+ * For some reason, if I send commit_string after preedit_string,
-+ * the text is doubled in the integrated terminal of VSCode.
-+ */
-+ if (input_method->current.commit_text) {
-+ zwp_text_input_v1_send_commit_string(
-+ text_input->input->resource,
-+ text_input->input->current_serial,
-+ input_method->current.commit_text);
-+ }
-+ if (input_method->current.preedit.text) {
-+ int preedit_len = strlen(input_method->current.preedit.text);
-+ zwp_text_input_v1_send_preedit_cursor(
-+ text_input->input->resource,
-+ input_method->current.preedit.cursor_begin
-+ );
-+ if (0 < input_method->current.preedit.cursor_begin) {
-+ zwp_text_input_v1_send_preedit_styling(
-+ text_input->input->resource, 0,
-+ input_method->current.preedit.cursor_begin,
-+ ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE);
-+ }
-+ if (input_method->current.preedit.cursor_begin
-+ < input_method->current.preedit.cursor_end) {
-+ zwp_text_input_v1_send_preedit_styling(
-+ text_input->input->resource,
-+ input_method->current.preedit.cursor_begin,
-+ input_method->current.preedit.cursor_end
-+ - input_method->current.preedit.cursor_begin,
-+ ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT);
-+ }
-+ if (input_method->current.preedit.cursor_end < preedit_len) {
-+ zwp_text_input_v1_send_preedit_styling(
-+ text_input->input->resource,
-+ input_method->current.preedit.cursor_end,
-+ preedit_len - input_method->current.preedit.cursor_end,
-+ ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE);
-+ }
-+ zwp_text_input_v1_send_preedit_string(
-+ text_input->input->resource,
-+ text_input->input->current_serial,
-+ input_method->current.preedit.text,
-+ input_method->current.preedit.text);
-+ } else {
-+ zwp_text_input_v1_send_preedit_cursor(
-+ text_input->input->resource, 0);
-+ zwp_text_input_v1_send_preedit_styling(
-+ text_input->input->resource, 0, 0,
-+ ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_NONE);
-+ zwp_text_input_v1_send_preedit_string(
-+ text_input->input->resource,
-+ text_input->input->current_serial, "", "");
-+ }
-+ return;
-+ }
-+ if (input_method->current.preedit.text) {
-+ wlr_text_input_v3_send_preedit_string(
-+ text_input->input,
-+ input_method->current.preedit.text,
-+ input_method->current.preedit.cursor_begin,
-+ input_method->current.preedit.cursor_end);
-+ }
-+ if (input_method->current.commit_text) {
-+ wlr_text_input_v3_send_commit_string(
-+ text_input->input,
-+ input_method->current.commit_text);
-+ }
-+ if (input_method->current.delete.before_length
-+ || input_method->current.delete.after_length) {
-+ wlr_text_input_v3_send_delete_surrounding_text(
-+ text_input->input,
-+ input_method->current.delete.before_length,
-+ input_method->current.delete.after_length);
-+ }
-+ wlr_text_input_v3_send_done(text_input->input);
-+}
-+
-+static void
-+handle_keyboard_grab_destroy(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay =
-+ wl_container_of(listener, relay, keyboard_grab_destroy);
-+ struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data;
-+ wl_list_remove(&relay->keyboard_grab_destroy.link);
-+
-+ if (keyboard_grab->keyboard) {
-+ /* Send modifier state to original client */
-+ wlr_seat_keyboard_notify_modifiers(
-+ keyboard_grab->input_method->seat,
-+ &keyboard_grab->keyboard->modifiers);
-+ }
-+}
-+
-+static void
-+handle_input_method_grab_keyboard(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay = wl_container_of(listener, relay,
-+ input_method_grab_keyboard);
-+ struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data;
-+
-+ /* Send modifier state to grab */
-+ struct wlr_keyboard *active_keyboard =
-+ wlr_seat_get_keyboard(relay->seat->seat);
-+ wlr_input_method_keyboard_grab_v2_set_keyboard(
-+ keyboard_grab, active_keyboard);
-+
-+ relay->keyboard_grab_destroy.notify = handle_keyboard_grab_destroy;
-+ wl_signal_add(&keyboard_grab->events.destroy,
-+ &relay->keyboard_grab_destroy);
-+}
-+
-+static void
-+handle_input_method_destroy(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay =
-+ wl_container_of(listener, relay, input_method_destroy);
-+ assert(relay->input_method == data);
-+ wl_list_remove(&relay->input_method_commit.link);
-+ wl_list_remove(&relay->input_method_grab_keyboard.link);
-+ wl_list_remove(&relay->input_method_destroy.link);
-+ relay->input_method = NULL;
-+
-+ update_text_inputs_focused_surface(relay);
-+ update_active_text_input(relay);
-+}
-+
-+static void
-+handle_popup_surface_destroy(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay = wl_container_of(listener, relay,
-+ popup_surface_destroy);
-+ wl_list_remove(&relay->popup_surface_destroy.link);
-+ wl_list_remove(&relay->popup_surface_commit.link);
-+ relay->popup_surface = NULL;
-+}
-+
-+static void
-+handle_popup_surface_commit(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay = wl_container_of(listener, relay,
-+ popup_surface_commit);
-+ update_popup_position(relay);
-+}
-+
-+static void
-+handle_input_method_new_popup_surface(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay = wl_container_of(listener, relay,
-+ input_method_new_popup_surface);
-+
-+ if (relay->popup_surface) {
-+ wlr_log(WLR_INFO, "Multiple IME popups are not supported");
-+ return;
-+ }
-+
-+ relay->popup_surface = data;
-+
-+ wl_signal_add(&relay->popup_surface->events.destroy,
-+ &relay->popup_surface_destroy);
-+ wl_signal_add(&relay->popup_surface->surface->events.commit,
-+ &relay->popup_surface_commit);
-+
-+ struct wlr_scene_node *scene_node = &wlr_scene_surface_create(
-+ relay->popup_tree, relay->popup_surface->surface)->buffer->node;
-+ node_descriptor_create(scene_node, LAB_NODE_DESC_INPUT_METHOD_POPUP, NULL);
-+
-+ update_popup_position(relay);
-+}
-+
-+static void
-+handle_new_input_method(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay =
-+ wl_container_of(listener, relay, new_input_method);
-+ struct wlr_input_method_v2 *input_method = data;
-+ if (relay->seat->seat != input_method->seat) {
-+ return;
-+ }
-+
-+ if (relay->input_method) {
-+ wlr_log(WLR_INFO,
-+ "Attempted to connect second input method to a seat");
-+ wlr_input_method_v2_send_unavailable(input_method);
-+ return;
-+ }
-+
-+ relay->input_method = input_method;
-+
-+ relay->input_method_commit.notify = handle_input_method_commit;
-+ wl_signal_add(&relay->input_method->events.commit,
-+ &relay->input_method_commit);
-+
-+ relay->input_method_grab_keyboard.notify =
-+ handle_input_method_grab_keyboard;
-+ wl_signal_add(&relay->input_method->events.grab_keyboard,
-+ &relay->input_method_grab_keyboard);
-+
-+ relay->input_method_destroy.notify = handle_input_method_destroy;
-+ wl_signal_add(&relay->input_method->events.destroy,
-+ &relay->input_method_destroy);
-+
-+ relay->input_method_new_popup_surface.notify =
-+ handle_input_method_new_popup_surface;
-+ wl_signal_add(&relay->input_method->events.new_popup_surface,
-+ &relay->input_method_new_popup_surface);
-+
-+ update_text_inputs_focused_surface(relay);
-+ update_active_text_input(relay);
-+}
-+
-+/* Conveys state from active text-input to input-method */
-+static void
-+send_state_to_input_method(struct input_method_relay *relay)
-+{
-+ assert(relay->active_text_input && relay->input_method);
-+
-+ struct wlr_input_method_v2 *input_method = relay->input_method;
-+ struct wlr_text_input_v3 *input = relay->active_text_input->input;
-+
-+ /* TODO: only send each of those if they were modified */
-+ if (input->active_features
-+ & WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT) {
-+ wlr_input_method_v2_send_surrounding_text(input_method,
-+ input->current.surrounding.text,
-+ input->current.surrounding.cursor,
-+ input->current.surrounding.anchor);
-+ }
-+ wlr_input_method_v2_send_text_change_cause(input_method,
-+ input->current.text_change_cause);
-+ if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE) {
-+ wlr_input_method_v2_send_content_type(input_method,
-+ input->current.content_type.hint,
-+ input->current.content_type.purpose);
-+ }
-+ wlr_input_method_v2_send_done(input_method);
-+}
-+
-+static void
-+handle_text_input_enable(struct wl_listener *listener, void *data)
-+{
-+ struct text_input *text_input =
-+ wl_container_of(listener, text_input, enable);
-+ struct input_method_relay *relay = text_input->relay;
-+
-+ update_active_text_input(relay);
-+ if (relay->active_text_input == text_input) {
-+ send_state_to_input_method(relay);
-+ }
-+}
-+
-+static void
-+handle_text_input_disable(struct wl_listener *listener, void *data)
-+{
-+ struct text_input *text_input =
-+ wl_container_of(listener, text_input, disable);
-+ /*
-+ * When the focus is moved between surfaces from different clients and
-+ * then the old client sends "disable" event, the relay ignores it and
-+ * doesn't deactivate the input-method.
-+ */
-+ update_active_text_input(text_input->relay);
-+}
-+
-+static void
-+handle_text_input_commit(struct wl_listener *listener, void *data)
-+{
-+ struct text_input *text_input =
-+ wl_container_of(listener, text_input, commit);
-+ struct input_method_relay *relay = text_input->relay;
-+
-+ if (relay->active_text_input == text_input) {
-+ update_popup_position(relay);
-+ send_state_to_input_method(relay);
-+ }
-+}
-+
-+static void
-+handle_text_input_destroy(struct wl_listener *listener, void *data)
-+{
-+ struct text_input *text_input =
-+ wl_container_of(listener, text_input, destroy);
-+ wl_list_remove(&text_input->enable.link);
-+ wl_list_remove(&text_input->disable.link);
-+ wl_list_remove(&text_input->commit.link);
-+ wl_list_remove(&text_input->destroy.link);
-+ wl_list_remove(&text_input->link);
-+ update_active_text_input(text_input->relay);
-+ free(text_input);
-+}
-+
-+static void
-+handle_new_text_input(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay =
-+ wl_container_of(listener, relay, new_text_input);
-+ struct wlr_text_input_v3 *wlr_text_input = data;
-+ bool is_v1 = !strcmp("zwp_text_input_v1",
-+ wl_resource_get_class(wlr_text_input->resource));
-+ if (!is_v1 && relay->seat->seat != wlr_text_input->seat) {
-+ return;
-+ }
-+
-+ struct text_input *text_input = znew(*text_input);
-+ text_input->v1 = is_v1;
-+ text_input->input = wlr_text_input;
-+ text_input->relay = relay;
-+ wl_list_insert(&relay->text_inputs, &text_input->link);
-+
-+ text_input->enable.notify = handle_text_input_enable;
-+ wl_signal_add(&text_input->input->events.enable, &text_input->enable);
-+
-+ text_input->disable.notify = handle_text_input_disable;
-+ wl_signal_add(&text_input->input->events.disable, &text_input->disable);
-+
-+ text_input->commit.notify = handle_text_input_commit;
-+ wl_signal_add(&text_input->input->events.commit, &text_input->commit);
-+
-+ text_input->destroy.notify = handle_text_input_destroy;
-+ wl_signal_add(&text_input->input->events.destroy, &text_input->destroy);
-+
-+ update_text_inputs_focused_surface(relay);
-+}
-+
-+/*
-+ * Usually this function is not called because the client destroys the surface
-+ * role (like xdg_toplevel) first and input_method_relay_set_focus() is called
-+ * before wl_surface is destroyed.
-+ */
-+static void
-+handle_focused_surface_destroy(struct wl_listener *listener, void *data)
-+{
-+ struct input_method_relay *relay =
-+ wl_container_of(listener, relay, focused_surface_destroy);
-+ assert(relay->focused_surface == data);
-+
-+ input_method_relay_set_focus(relay, NULL);
-+}
-+
-+struct input_method_relay *
-+input_method_relay_create(struct seat *seat)
-+{
-+ struct input_method_relay *relay = znew(*relay);
-+ relay->seat = seat;
-+ wl_list_init(&relay->text_inputs);
-+
-+ relay->new_text_input.notify = handle_new_text_input;
-+ wl_signal_add(&seat->server->text_input_manager->events.text_input,
-+ &relay->new_text_input);
-+
-+ relay->new_input_method.notify = handle_new_input_method;
-+ wl_signal_add(&seat->server->input_method_manager->events.input_method,
-+ &relay->new_input_method);
-+
-+ relay->focused_surface_destroy.notify = handle_focused_surface_destroy;
-+ relay->popup_surface_destroy.notify = handle_popup_surface_destroy;
-+ relay->popup_surface_commit.notify = handle_popup_surface_commit;
-+
-+ relay->popup_tree = wlr_scene_tree_create(&seat->server->scene->tree);
-+
-+ text_input_manager_v1_init(seat->server->wl_display,
-+ seat->server->text_input_manager);
-+
-+ return relay;
-+}
-+
-+void
-+input_method_relay_finish(struct input_method_relay *relay)
-+{
-+ wl_list_remove(&relay->new_text_input.link);
-+ wl_list_remove(&relay->new_input_method.link);
-+ free(relay);
-+}
-+
-+void
-+input_method_relay_set_focus(struct input_method_relay *relay,
-+ struct wlr_surface *surface)
-+{
-+ if (relay->focused_surface == surface) {
-+ wlr_log(WLR_INFO, "The surface is already focused");
-+ return;
-+ }
-+
-+ if (relay->focused_surface) {
-+ wl_list_remove(&relay->focused_surface_destroy.link);
-+ }
-+ relay->focused_surface = surface;
-+ if (surface) {
-+ wl_signal_add(&surface->events.destroy,
-+ &relay->focused_surface_destroy);
-+ }
-+
-+ update_text_inputs_focused_surface(relay);
-+ update_active_text_input(relay);
-+}
-diff --git a/src/input/keyboard.c b/src/input/keyboard.c
-index 7379a54..3b0b6d9 100644
---- a/src/input/keyboard.c
-+++ b/src/input/keyboard.c
-@@ -97,7 +97,11 @@ keyboard_modifiers_notify(struct wl_listener *listener, void *data)
- }
- }
- }
-- wlr_seat_keyboard_notify_modifiers(seat->seat, &wlr_keyboard->modifiers);
-+
-+ if (!input_method_keyboard_grab_forward_modifiers(keyboard)) {
-+ wlr_seat_keyboard_notify_modifiers(seat->seat,
-+ &wlr_keyboard->modifiers);
-+ }
- }
-
- static struct keybind *
-@@ -523,7 +527,7 @@ keyboard_key_notify(struct wl_listener *listener, void *data)
- if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
- start_keybind_repeat(seat->server, keyboard, event);
- }
-- } else {
-+ } else if (!input_method_keyboard_grab_forward_key(keyboard, event)) {
- wlr_seat_set_keyboard(wlr_seat, keyboard->wlr_keyboard);
- wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
- event->keycode, event->state);
-diff --git a/src/input/meson.build b/src/input/meson.build
-index c8b7717..c333184 100644
---- a/src/input/meson.build
-+++ b/src/input/meson.build
-@@ -7,4 +7,5 @@ labwc_sources += files(
- 'keyboard.c',
- 'key-state.c',
- 'touch.c',
-+ 'ime.c',
- )
-diff --git a/src/layers.c b/src/layers.c
-index 20e9e68..f84c2fb 100644
---- a/src/layers.c
-+++ b/src/layers.c
-@@ -298,6 +298,8 @@ create_popup(struct wlr_xdg_popup *wlr_popup, struct wlr_scene_tree *parent)
- free(popup);
- return NULL;
- }
-+ /* In support of IME popups */
-+ popup->wlr_popup->base->surface->data = popup->scene_tree;
- node_descriptor_create(&popup->scene_tree->node,
- LAB_NODE_DESC_LAYER_POPUP, popup);
-
-@@ -424,6 +426,9 @@ handle_new_layer_surface(struct wl_listener *listener, void *data)
- return;
- }
-
-+ /* In support of IME popups */
-+ layer_surface->surface->data = surface->scene_layer_surface->tree;
-+
- node_descriptor_create(&surface->scene_layer_surface->tree->node,
- LAB_NODE_DESC_LAYER_SURFACE, surface);
-
-diff --git a/src/output.c b/src/output.c
-index e1c615e..5eca379 100644
---- a/src/output.c
-+++ b/src/output.c
-@@ -320,6 +320,7 @@ new_output_notify(struct wl_listener *listener, void *data)
- /*
- * Set the z-positions to achieve the following order (from top to
- * bottom):
-+ * - IME popups
- * - session lock layer
- * - layer-shell popups
- * - overlay layer
-@@ -334,6 +335,7 @@ new_output_notify(struct wl_listener *listener, void *data)
- wlr_scene_node_raise_to_top(&output->layer_tree[3]->node);
- wlr_scene_node_raise_to_top(&output->layer_popup_tree->node);
- wlr_scene_node_raise_to_top(&output->session_lock_tree->node);
-+ wlr_scene_node_raise_to_top(&server->seat.input_method_relay->popup_tree->node);
-
- /*
- * Wait until wlr_output_layout_add_auto() returns before
-diff --git a/src/seat.c b/src/seat.c
-index e4e69ef..27d5d57 100644
---- a/src/seat.c
-+++ b/src/seat.c
-@@ -536,6 +536,8 @@ seat_init(struct server *server)
- &seat->virtual_keyboard_new);
- seat->virtual_keyboard_new.notify = new_virtual_keyboard;
-
-+ seat->input_method_relay = input_method_relay_create(seat);
-+
- seat->cursor = wlr_cursor_create();
- if (!seat->cursor) {
- wlr_log(WLR_ERROR, "unable to create cursor");
-@@ -559,6 +561,7 @@ seat_finish(struct server *server)
- }
-
- input_handlers_finish(seat);
-+ input_method_relay_finish(seat->input_method_relay);
- }
-
- static void
-@@ -614,6 +617,7 @@ seat_focus(struct seat *seat, struct wlr_surface *surface, bool is_lock_surface)
-
- if (!surface) {
- wlr_seat_keyboard_notify_clear_focus(seat->seat);
-+ input_method_relay_set_focus(seat->input_method_relay, NULL);
- return;
- }
-
-@@ -636,6 +640,8 @@ seat_focus(struct seat *seat, struct wlr_surface *surface, bool is_lock_surface)
- wlr_seat_keyboard_notify_enter(seat->seat, surface,
- pressed_sent_keycodes, nr_pressed_sent_keycodes, &kb->modifiers);
-
-+ input_method_relay_set_focus(seat->input_method_relay, surface);
-+
- struct wlr_pointer_constraint_v1 *constraint =
- wlr_pointer_constraints_v1_constraint_for_surface(server->constraints,
- surface, seat->seat);
-diff --git a/src/server.c b/src/server.c
-index c0ee768..e88b25e 100644
---- a/src/server.c
-+++ b/src/server.c
-@@ -378,6 +378,10 @@ server_init(struct server *server)
- */
- wlr_primary_selection_v1_device_manager_create(server->wl_display);
-
-+ server->input_method_manager = wlr_input_method_manager_v2_create(
-+ server->wl_display);
-+ server->text_input_manager = wlr_text_input_manager_v3_create(
-+ server->wl_display);
- seat_init(server);
- xdg_shell_init(server);
- kde_server_decoration_init(server);
-diff --git a/src/xdg.c b/src/xdg.c
-index dc9acc9..8a96807 100644
---- a/src/xdg.c
-+++ b/src/xdg.c
-@@ -771,7 +771,7 @@ xdg_surface_new(struct wl_listener *listener, void *data)
- */
- kde_server_decoration_set_view(view, xdg_surface->surface);
-
-- /* In support of xdg popups */
-+ /* In support of xdg popups and IME popups */
- xdg_surface->surface->data = tree;
-
- view_connect_map(view, xdg_surface->surface);