diff options
author | tokyo4j | 2024-05-11 16:55:19 +0900 |
---|---|---|
committer | tokyo4j | 2024-05-11 16:55:29 +0900 |
commit | ab3f16a1f0af8db52d88b6b69695e2a2b548fc14 (patch) | |
tree | f9d099d2a7e2881dd8c9eec4255dfaf02ac83eb5 | |
parent | 45677cfeb3f2cff92291ec5173247c2d977cb410 (diff) | |
download | aur-ab3f16a1f0af8db52d88b6b69695e2a2b548fc14.tar.gz |
Update to 0.7.2
-rw-r--r-- | .SRCINFO | 10 | ||||
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | 0001-IME-support-text-input-v1.patch | 433 | ||||
-rw-r--r-- | PKGBUILD | 10 | ||||
-rw-r--r-- | ime-popup-text-input-v1.patch | 1236 |
5 files changed, 447 insertions, 1246 deletions
@@ -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 + @@ -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); |