diff options
-rw-r--r-- | .SRCINFO | 27 | ||||
-rw-r--r-- | PKGBUILD | 34 | ||||
-rw-r--r-- | ime-popup-text-input-v1.patch | 1234 |
3 files changed, 1295 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO new file mode 100644 index 000000000000..c30f7531df5a --- /dev/null +++ b/.SRCINFO @@ -0,0 +1,27 @@ +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.0 + pkgrel = 1 + url = https://github.com/labwc/labwc + arch = x86_64 + license = GPL2 + makedepends = meson + makedepends = scdoc + makedepends = wayland-protocols + depends = libpng + depends = librsvg + depends = pango + depends = seatd + depends = wlroots>=0.17 + depends = wlroots<0.18 + depends = wayland + depends = xorg-xwayland + optdepends = bemenu: default launcher via Alt+F3 + provides = labwc + conflicts = labwc + source = labwc-0.7.0.tar.gz::https://github.com/labwc/labwc/archive/0.7.0.tar.gz + source = ime-popup-text-input-v1.patch + b2sums = 18ab44981eb4c8f949707243422feebe4292e7ac8b7cada8d309af12fad55bfc742f5ad0600f1f77dfdc83497f5dd0c546363f5b711460b1a11197ce2e13e5ca + b2sums = 16d73f7715e266f123c83d0297aa78cb3bbc31e7c97408f3427cdd6b40139ce7b9b2d99c6f64c046122e9fd9220381786bd57dc0c7bb9dc826848cd757c0a9a2 + +pkgname = labwc-im diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 000000000000..bfaa818279ba --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,34 @@ +# Maintainer: Hiroaki Yamamoto <hrak1529@gmail.com> + +# Based on labwc AUR from Lex Black <autumn-wind@web.de> + +pkgname=labwc-im +pkgver=0.7.0 +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" +arch=('x86_64') +license=('GPL2') +depends=('libpng' 'librsvg' 'pango' 'seatd' 'wlroots>=0.17' 'wlroots<0.18' 'wayland' 'xorg-xwayland') +makedepends=('meson' 'scdoc' 'wayland-protocols') +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=('18ab44981eb4c8f949707243422feebe4292e7ac8b7cada8d309af12fad55bfc742f5ad0600f1f77dfdc83497f5dd0c546363f5b711460b1a11197ce2e13e5ca' + '16d73f7715e266f123c83d0297aa78cb3bbc31e7c97408f3427cdd6b40139ce7b9b2d99c6f64c046122e9fd9220381786bd57dc0c7bb9dc826848cd757c0a9a2') + +prepare() { + cd "labwc-$pkgver" + patch -Np1 -i ../ime-popup-text-input-v1.patch +} + +build() { + arch-meson -Dman-pages=enabled "labwc-$pkgver" build + meson compile -C build +} + +package() { + meson install -C build --destdir "$pkgdir" +} diff --git a/ime-popup-text-input-v1.patch b/ime-popup-text-input-v1.patch new file mode 100644 index 000000000000..153a62b81e61 --- /dev/null +++ b/ime-popup-text-input-v1.patch @@ -0,0 +1,1234 @@ +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 da92242..27f0fe9 100644 +--- a/include/labwc.h ++++ b/include/labwc.h +@@ -39,10 +39,13 @@ + #include <wlr/types/wlr_drm_lease_v1.h> + #include <wlr/types/wlr_virtual_pointer_v1.h> + #include <wlr/types/wlr_virtual_keyboard_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 +@@ -119,6 +122,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 +@@ -318,6 +323,9 @@ struct server { + struct wlr_pointer_constraints_v1 *constraints; + struct wl_listener new_constraint; + ++ 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 527f9d4..3750256 100644 +--- a/protocols/meson.build ++++ b/protocols/meson.build +@@ -18,6 +18,7 @@ server_protocols = [ + wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', + wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', + wl_protocol_dir / 'staging/drm-lease/drm-lease-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 08f8ff5..863a058 100644 +--- a/src/desktop.c ++++ b/src/desktop.c +@@ -417,6 +417,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..03e2693 +--- /dev/null ++++ b/src/input/ime.c +@@ -0,0 +1,915 @@ ++// 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_v1seat_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_v1seat_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 b9d01ac..31012b4 100644 +--- a/src/input/keyboard.c ++++ b/src/input/keyboard.c +@@ -91,7 +91,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 * +@@ -505,7 +509,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 cfdd3cd..793c409 100644 +--- a/src/input/meson.build ++++ b/src/input/meson.build +@@ -5,4 +5,5 @@ labwc_sources += files( + 'keyboard.c', + 'key-state.c', + 'touch.c', ++ 'ime.c', + ) +diff --git a/src/layers.c b/src/layers.c +index abf9c71..706a26c 100644 +--- a/src/layers.c ++++ b/src/layers.c +@@ -278,6 +278,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); + +@@ -403,6 +405,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 eaec44b..892b1a7 100644 +--- a/src/output.c ++++ b/src/output.c +@@ -304,6 +304,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 +@@ -318,6 +319,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 64447df..acc8d9a 100644 +--- a/src/seat.c ++++ b/src/seat.c +@@ -414,6 +414,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"); +@@ -437,6 +439,7 @@ seat_finish(struct server *server) + } + + input_handlers_finish(seat); ++ input_method_relay_finish(seat->input_method_relay); + } + + static void +@@ -481,6 +484,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; + } + +@@ -503,6 +507,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 e7a58be..8797aaa 100644 +--- a/src/server.c ++++ b/src/server.c +@@ -379,6 +379,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 713eccd..770a92f 100644 +--- a/src/xdg.c ++++ b/src/xdg.c +@@ -671,7 +671,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); |