From f5adba2fae5da369338d142880f6d8481c2a5601 Mon Sep 17 00:00:00 2001 From: Alynx Zhou Date: Wed, 15 May 2024 00:07:41 +0800 Subject: [PATCH] wayland/text-input-v1: Implement basic text-input-v1 support This commit makes input methods work in text-input-v1 only clients (mostly Chromium/Electron based apps with Ozone Wayland), which is needed by users who needs IME to input their languages, like Chinese, Japanese or Korean. Closes . Signed-off-by: Mingi Sung --- clutter/clutter/clutter-enums.h | 3 + src/core/events.c | 11 +- src/meson.build | 3 + src/wayland/meta-wayland-seat.c | 12 +- src/wayland/meta-wayland-seat.h | 2 + src/wayland/meta-wayland-text-input-v1.c | 859 +++++++++++++++++++++++ src/wayland/meta-wayland-text-input-v1.h | 38 + src/wayland/meta-wayland-versions.h | 1 + src/wayland/meta-wayland.c | 7 + src/wayland/meta-wayland.h | 2 + 10 files changed, 933 insertions(+), 5 deletions(-) create mode 100644 src/wayland/meta-wayland-text-input-v1.c create mode 100644 src/wayland/meta-wayland-text-input-v1.h diff --git a/clutter/clutter/clutter-enums.h b/clutter/clutter/clutter-enums.h index 9bd508fb6..8f174087e 100644 --- a/clutter/clutter/clutter-enums.h +++ b/clutter/clutter/clutter-enums.h @@ -1172,6 +1172,9 @@ typedef enum CLUTTER_INPUT_CONTENT_HINT_SENSITIVE_DATA = 1 << 7, CLUTTER_INPUT_CONTENT_HINT_LATIN = 1 << 8, CLUTTER_INPUT_CONTENT_HINT_MULTILINE = 1 << 9, + CLUTTER_INPUT_CONTENT_HINT_DEFAULT = 1 << 10, + CLUTTER_INPUT_CONTENT_HINT_PASSWORD = 1 << 11, + CLUTTER_INPUT_CONTENT_HINT_AUTO_CORRECTION = 1 << 12, } ClutterInputContentHintFlags; typedef enum diff --git a/src/core/events.c b/src/core/events.c index fb4c7574c..31f5af705 100644 --- a/src/core/events.c +++ b/src/core/events.c @@ -239,6 +239,7 @@ meta_display_handle_event (MetaDisplay *display, #ifdef HAVE_WAYLAND MetaWaylandCompositor *wayland_compositor; MetaWaylandTextInput *wayland_text_input = NULL; + MetaWaylandTextInputV1 *wayland_text_input_v1 = NULL; #endif #ifdef HAVE_WAYLAND @@ -247,6 +248,8 @@ meta_display_handle_event (MetaDisplay *display, { wayland_text_input = meta_wayland_compositor_get_text_input (wayland_compositor); + wayland_text_input_v1 = + meta_wayland_compositor_get_text_input_v1 (wayland_compositor); } #endif @@ -288,9 +291,11 @@ meta_display_handle_event (MetaDisplay *display, } #ifdef HAVE_WAYLAND - if (wayland_text_input && - !meta_compositor_get_current_window_drag (compositor) && - meta_wayland_text_input_update (wayland_text_input, event)) + if (!meta_compositor_get_current_window_drag (compositor) && + ((wayland_text_input && + meta_wayland_text_input_update (wayland_text_input, event)) || + (wayland_text_input_v1 && + meta_wayland_text_input_v1_update (wayland_text_input_v1, event)))) return CLUTTER_EVENT_STOP; if (wayland_compositor) diff --git a/src/meson.build b/src/meson.build index bfa8306d2..396d58eb4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -696,6 +696,8 @@ if have_wayland 'wayland/meta-wayland-tablet-tool.h', 'wayland/meta-wayland-text-input.c', 'wayland/meta-wayland-text-input.h', + 'wayland/meta-wayland-text-input-v1.c', + 'wayland/meta-wayland-text-input-v1.h', 'wayland/meta-wayland-touch.c', 'wayland/meta-wayland-touch.h', 'wayland/meta-wayland-transaction.c', @@ -1109,6 +1111,7 @@ if have_wayland ['single-pixel-buffer', 'staging', 'v1', ], ['tablet', 'unstable', 'v2', ], ['text-input', 'unstable', 'v3', ], + ['text-input', 'unstable', 'v1', ], ['viewporter', 'stable', ], ['xdg-activation', 'staging', 'v1', ], ['xdg-dialog', 'staging', 'v1', ], diff --git a/src/wayland/meta-wayland-seat.c b/src/wayland/meta-wayland-seat.c index 8e592d6c8..e72a7a1f4 100644 --- a/src/wayland/meta-wayland-seat.c +++ b/src/wayland/meta-wayland-seat.c @@ -232,6 +232,7 @@ default_focus (MetaWaylandEventHandler *handler, surface); meta_wayland_tablet_seat_set_pad_focus (seat->tablet_seat, surface); meta_wayland_text_input_set_focus (seat->text_input, surface); + /* text-input-v1 will set focused surface on activate. */ } if (caps & CLUTTER_INPUT_CAPABILITY_TABLET_TOOL) @@ -297,6 +298,8 @@ meta_wayland_seat_new (MetaWaylandCompositor *compositor, NULL); seat->text_input = meta_wayland_text_input_new (seat); + /* Chromium/Electron-based apps only support text-input-v1. */ + seat->text_input_v1 = meta_wayland_text_input_v1_new (seat); meta_wayland_data_device_init (&seat->data_device, seat); meta_wayland_data_device_primary_init (&seat->primary_data_device, seat); @@ -342,6 +345,7 @@ meta_wayland_seat_free (MetaWaylandSeat *seat) g_object_unref (seat->touch); meta_wayland_text_input_destroy (seat->text_input); + meta_wayland_text_input_v1_destroy (seat->text_input_v1); g_free (seat); } @@ -494,7 +498,10 @@ meta_wayland_seat_handle_event_internal (MetaWaylandSeat *seat, if (event_type == CLUTTER_BUTTON_PRESS || event_type == CLUTTER_TOUCH_BEGIN) { - meta_wayland_text_input_handle_event (seat->text_input, event); + gboolean handled = FALSE; + handled = meta_wayland_text_input_handle_event (seat->text_input, event); + if (!handled) + handled = meta_wayland_text_input_v1_handle_event (seat->text_input_v1, event); } switch (event_type) @@ -526,7 +533,8 @@ meta_wayland_seat_handle_event_internal (MetaWaylandSeat *seat, case CLUTTER_IM_COMMIT: case CLUTTER_IM_DELETE: case CLUTTER_IM_PREEDIT: - if (meta_wayland_text_input_handle_event (seat->text_input, event)) + if (meta_wayland_text_input_handle_event (seat->text_input, event) || + meta_wayland_text_input_v1_handle_event (seat->text_input_v1, event)) return TRUE; break; diff --git a/src/wayland/meta-wayland-seat.h b/src/wayland/meta-wayland-seat.h index cfca96238..fd5772aa5 100644 --- a/src/wayland/meta-wayland-seat.h +++ b/src/wayland/meta-wayland-seat.h @@ -30,6 +30,7 @@ #include "wayland/meta-wayland-pointer.h" #include "wayland/meta-wayland-tablet-tool.h" #include "wayland/meta-wayland-text-input.h" +#include "wayland/meta-wayland-text-input-v1.h" #include "wayland/meta-wayland-touch.h" #include "wayland/meta-wayland-types.h" @@ -51,6 +52,7 @@ struct _MetaWaylandSeat MetaWaylandDataDevicePrimary primary_data_device; MetaWaylandTextInput *text_input; + MetaWaylandTextInputV1 *text_input_v1; MetaWaylandInput *input_handler; MetaWaylandEventHandler *default_handler; diff --git a/src/wayland/meta-wayland-text-input-v1.c b/src/wayland/meta-wayland-text-input-v1.c new file mode 100644 index 000000000..1826a4ff4 --- /dev/null +++ b/src/wayland/meta-wayland-text-input-v1.c @@ -0,0 +1,859 @@ +/* + * Copyright (C) 2024 SUSE LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Author: Alynx Zhou + */ + +#include "config.h" +#include "wayland/meta-wayland-text-input-v1.h" + +#include + +#include "compositor/meta-surface-actor-wayland.h" +#include "wayland/meta-wayland-private.h" +#include "wayland/meta-wayland-seat.h" +#include "wayland/meta-wayland-versions.h" + +#include "text-input-unstable-v1-server-protocol.h" + +/* + * Main difference between text-input-v1 and text-input-v3: + * text-input-v1 is not required to be double-buffered, we are expected to send + * response immediately after we receive requests, while text-input-v3 requires + * us to hold pending state and apply on commit, and all responses are applied + * after we send done. + * + * This implementation is incomplete, but it do make IME work. + * + * Things won't be implemented (Reminders for myself): + * - set_preferred_language (We don't have equivalence in ClutterInputMethod.) + * - invoke_action (No description about what button and index are.) + * - input_panel_state (We don't set this from ClutterInputFocus to text_input, + * we only set this from text_input to ClutterInputFocus.) + * - cursor_position (We don't have equivalence in ClutterInputMethod.) + * - language (We don't have equivalence in ClutterInputMethod.) + * - text_direction (We don't have equivalence in ClutterInputMethod.) + * - keysym (This matches keysym request in input-method-v1, but we only have + * forward_key in ClutterInputMethod, which is more like key request in + * input-method-v1 and will finally become a keyboard key event, we don't have + * equivalence for this in ClutterInputMethod.) + * - modifiers_map (This is used by keysym and we don't support keysym.) + */ + +struct _MetaWaylandTextInputV1 +{ + MetaWaylandSeat *seat; + ClutterInputFocus *input_focus; + + struct wl_list resource_list; + struct wl_list focus_resource_list; + MetaWaylandSurface *surface; + struct wl_listener surface_listener; + + GHashTable *resource_serials; + + struct + { + char *text; + uint32_t cursor; + uint32_t anchor; + } surrounding; +}; + +#define META_TYPE_WAYLAND_TEXT_INPUT_V1_FOCUS (meta_wayland_text_input_v1_focus_get_type ()) +G_DECLARE_FINAL_TYPE (MetaWaylandTextInputV1Focus, meta_wayland_text_input_v1_focus, + META, WAYLAND_TEXT_INPUT_V1_FOCUS, ClutterInputFocus) + +struct _MetaWaylandTextInputV1Focus +{ + ClutterInputFocus parent_instance; + MetaWaylandTextInputV1 *text_input; +}; +G_DEFINE_TYPE (MetaWaylandTextInputV1Focus, meta_wayland_text_input_v1_focus, + CLUTTER_TYPE_INPUT_FOCUS) + +static MetaBackend * +backend_from_text_input_v1 (MetaWaylandTextInputV1 *text_input) +{ + MetaWaylandSeat *seat = text_input->seat; + MetaWaylandCompositor *compositor = meta_wayland_seat_get_compositor (seat); + MetaContext *context = meta_wayland_compositor_get_context (compositor); + + return meta_context_get_backend (context); +} + +static uint32_t +get_serial (MetaWaylandTextInputV1 *text_input, + struct wl_resource *resource) +{ + return GPOINTER_TO_UINT (g_hash_table_lookup (text_input->resource_serials, + resource)); +} + +static void +set_serial (MetaWaylandTextInputV1 *text_input, + struct wl_resource *resource, + uint32_t serial) +{ + g_hash_table_insert (text_input->resource_serials, resource, + GUINT_TO_POINTER (serial)); +} + +static void +text_input_v1_send_preedit_string (struct wl_resource *resource, + uint32_t serial, + const char *text, + unsigned int cursor) +{ + gsize pos = 0; + + /* Chromium does not accept NULL as preedit/commit string... */ + text = text ? text : ""; + pos = g_utf8_offset_to_pointer (text, cursor) - text; + + /* We really don't need so much styles... */ + zwp_text_input_v1_send_preedit_styling (resource, 0, strlen (text), + ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE); + zwp_text_input_v1_send_preedit_cursor (resource, pos); + zwp_text_input_v1_send_preedit_string (resource, serial, text, text); +} + +static void +meta_wayland_text_input_v1_focus_set_preedit_text (ClutterInputFocus *focus, + const gchar *text, + unsigned int cursor, + unsigned int anchor) +{ + MetaWaylandTextInputV1 *text_input; + struct wl_resource *resource; + + text_input = META_WAYLAND_TEXT_INPUT_V1_FOCUS (focus)->text_input; + + wl_resource_for_each (resource, &text_input->focus_resource_list) + { + text_input_v1_send_preedit_string (resource, + get_serial (text_input, resource), + text, + cursor); + } +} + +static void +meta_wayland_text_input_v1_focus_request_surrounding (ClutterInputFocus *focus) +{ + MetaWaylandTextInputV1 *text_input; + long cursor, anchor; + + /* Clutter uses char offsets but text-input-v1 uses byte offsets. */ + text_input = META_WAYLAND_TEXT_INPUT_V1_FOCUS (focus)->text_input; + cursor = g_utf8_strlen (text_input->surrounding.text, + text_input->surrounding.cursor); + anchor = g_utf8_strlen (text_input->surrounding.text, + text_input->surrounding.anchor); + clutter_input_focus_set_surrounding (focus, + text_input->surrounding.text, + cursor, + anchor); +} + +static void +text_input_v1_send_commit_string (struct wl_resource *resource, + uint32_t serial, + const char *text) +{ + /* Chromium does not accept NULL as preedit/commit string... */ + text = text ? text : ""; + + zwp_text_input_v1_send_commit_string (resource, serial, text); +} + +static void +meta_wayland_text_input_v1_focus_delete_surrounding (ClutterInputFocus *focus, + int offset, + guint len) +{ + MetaWaylandTextInputV1 *text_input; + struct wl_resource *resource; + const char *start, *end; + const char *before, *after; + const char *cursor; + + /* + * offset and len are counted by UTF-8 chars, but text-input-v1's lengths are + * counted by bytes, so we convert UTF-8 char offsets to pointers here, this + * needs the surrounding text + */ + text_input = META_WAYLAND_TEXT_INPUT_V1_FOCUS (focus)->text_input; + offset = MIN (offset, 0); + + start = text_input->surrounding.text; + end = start + strlen (text_input->surrounding.text); + cursor = start + text_input->surrounding.cursor; + + before = g_utf8_offset_to_pointer (cursor, offset); + g_assert (before >= start); + + after = g_utf8_offset_to_pointer (cursor, offset + len); + g_assert (after <= end); + + wl_resource_for_each (resource, &text_input->focus_resource_list) + { + zwp_text_input_v1_send_delete_surrounding_text (resource, + before - cursor, + after - before); + /* + * text-input-v1 says delete_surrounding belongs to next commit, so an + * empty commit is required. + */ + text_input_v1_send_commit_string (resource, + get_serial (text_input, resource), + NULL); + } +} + +static void +meta_wayland_text_input_v1_focus_commit_text (ClutterInputFocus *focus, + const gchar *text) +{ + MetaWaylandTextInputV1 *text_input; + struct wl_resource *resource; + + text_input = META_WAYLAND_TEXT_INPUT_V1_FOCUS (focus)->text_input; + + wl_resource_for_each (resource, &text_input->focus_resource_list) + { + /* + * You have to clear preedit string after committing string, otherwise + * some apps (I reproduced with Code OSS) will send you empty surrounding + * text and breaks delete_surrounding_text. + */ + text_input_v1_send_commit_string (resource, + get_serial (text_input, resource), + text); + /* Clear preedit string because we already committed. */ + text_input_v1_send_preedit_string (resource, + get_serial (text_input, resource), + NULL, + 0); + } +} + +static void +meta_wayland_text_input_v1_focus_class_init (MetaWaylandTextInputV1FocusClass *klass) +{ + ClutterInputFocusClass *focus_class = CLUTTER_INPUT_FOCUS_CLASS (klass); + + focus_class->request_surrounding = meta_wayland_text_input_v1_focus_request_surrounding; + focus_class->delete_surrounding = meta_wayland_text_input_v1_focus_delete_surrounding; + focus_class->commit_text = meta_wayland_text_input_v1_focus_commit_text; + focus_class->set_preedit_text = meta_wayland_text_input_v1_focus_set_preedit_text; +} + +static void +meta_wayland_text_input_v1_focus_init (MetaWaylandTextInputV1Focus *focus) +{ +} + +static ClutterInputFocus * +meta_wayland_text_input_focus_new (MetaWaylandTextInputV1 *text_input) +{ + MetaWaylandTextInputV1Focus *focus; + + focus = g_object_new (META_TYPE_WAYLAND_TEXT_INPUT_V1_FOCUS, NULL); + focus->text_input = text_input; + + return CLUTTER_INPUT_FOCUS (focus); +} + +static void +move_resources (struct wl_list *destination, struct wl_list *source) +{ + wl_list_insert_list (destination, source); + wl_list_init (source); +} + +static void +move_resources_for_client (struct wl_list *destination, + struct wl_list *source, + struct wl_client *client) +{ + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe (resource, tmp, source) + { + if (wl_resource_get_client (resource) == client) + { + wl_list_remove (wl_resource_get_link (resource)); + wl_list_insert (destination, wl_resource_get_link (resource)); + } + } +} + +static void +meta_wayland_text_input_v1_set_focus (MetaWaylandTextInputV1 *text_input, + MetaWaylandSurface *surface) +{ + if (text_input->surface == surface) + return; + + if (text_input->surface) + { + if (!wl_list_empty (&text_input->focus_resource_list)) + { + ClutterInputFocus *focus = text_input->input_focus; + ClutterInputMethod *input_method; + struct wl_resource *resource; + + if (clutter_input_focus_is_focused (focus)) + { + input_method = clutter_backend_get_input_method (clutter_get_default_backend ()); + clutter_input_focus_reset (focus); + clutter_input_method_focus_out (input_method); + } + + wl_resource_for_each (resource, &text_input->focus_resource_list) + { + zwp_text_input_v1_send_leave (resource); + } + + move_resources (&text_input->resource_list, + &text_input->focus_resource_list); + } + + wl_list_remove (&text_input->surface_listener.link); + text_input->surface = NULL; + } + + if (surface && surface->resource) + { + struct wl_resource *focus_surface_resource; + + text_input->surface = surface; + focus_surface_resource = text_input->surface->resource; + wl_resource_add_destroy_listener (focus_surface_resource, + &text_input->surface_listener); + + move_resources_for_client (&text_input->focus_resource_list, + &text_input->resource_list, + wl_resource_get_client (focus_surface_resource)); + + if (!wl_list_empty (&text_input->focus_resource_list)) + { + struct wl_resource *resource; + + wl_resource_for_each (resource, &text_input->focus_resource_list) + { + zwp_text_input_v1_send_enter (resource, surface->resource); + } + } + } +} + +static void +text_input_v1_handle_focus_surface_destroy (struct wl_listener *listener, + void *data) +{ + MetaWaylandTextInputV1 *text_input = wl_container_of (listener, text_input, surface_listener); + + meta_wayland_text_input_v1_set_focus (text_input, NULL); +} + +static void +text_input_v1_destructor (struct wl_resource *resource) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + + g_hash_table_remove (text_input->resource_serials, resource); + wl_list_remove (wl_resource_get_link (resource)); +} + +static gboolean +client_matches_focus (MetaWaylandTextInputV1 *text_input, + struct wl_client *client) +{ + if (!text_input->surface) + return FALSE; + + return client == wl_resource_get_client (text_input->surface->resource); +} + +static void +text_input_v1_activate (struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + struct wl_resource *surface_resource) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + MetaWaylandSurface *surface; + ClutterInputFocus *focus = text_input->input_focus; + ClutterInputMethod *input_method; + + /* + * Don't use client_matches_focus() here because we have no focused surface if + * not activated in text-input-v1. + */ + + surface = wl_resource_get_user_data (surface_resource); + meta_wayland_text_input_v1_set_focus (text_input, surface); + + input_method = clutter_backend_get_input_method (clutter_get_default_backend ()); + + if (input_method) + { + if (!clutter_input_focus_is_focused (focus)) + clutter_input_method_focus_in (input_method, focus); + + clutter_input_focus_set_can_show_preedit (focus, TRUE); + } +} + +static void +text_input_v1_deactivate (struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat_resource) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + ClutterInputFocus *focus = text_input->input_focus; + ClutterInputMethod *input_method; + + if (!client_matches_focus (text_input, client)) + return; + + meta_wayland_text_input_v1_set_focus (text_input, NULL); + + input_method = clutter_backend_get_input_method (clutter_get_default_backend ()); + if (input_method && clutter_input_focus_is_focused (focus)) + { + clutter_input_focus_reset (focus); + clutter_input_method_focus_out (input_method); + } +} + +static void +text_input_v1_show_input_panel (struct wl_client *client, + struct wl_resource *resource) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + ClutterInputFocus *focus = text_input->input_focus; + + if (!client_matches_focus (text_input, client)) + return; + + clutter_input_focus_set_input_panel_state (focus, + CLUTTER_INPUT_PANEL_STATE_ON); +} + +static void +text_input_v1_hide_input_panel (struct wl_client *client, + struct wl_resource *resource) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + ClutterInputFocus *focus = text_input->input_focus; + + if (!client_matches_focus (text_input, client)) + return; + + clutter_input_focus_set_input_panel_state (focus, + CLUTTER_INPUT_PANEL_STATE_OFF); +} + +static void +text_input_v1_set_surrounding_text (struct wl_client *client, + struct wl_resource *resource, + const char *text, + uint32_t cursor, + uint32_t anchor) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + ClutterInputFocus *focus = text_input->input_focus; + long char_cursor, char_anchor; + + if (!client_matches_focus (text_input, client)) + return; + + /* Save the surrounding text for `delete_surrounding_text`. */ + g_free (text_input->surrounding.text); + text_input->surrounding.text = g_strdup (text); + text_input->surrounding.cursor = cursor; + text_input->surrounding.anchor = anchor; + + /* Pass the surrounding text to Clutter to handle it with input method. */ + /* Clutter uses char offsets but text-input-v1 uses byte offsets. */ + char_cursor = g_utf8_strlen (text_input->surrounding.text, + text_input->surrounding.cursor); + char_anchor = g_utf8_strlen (text_input->surrounding.text, + text_input->surrounding.anchor); + clutter_input_focus_set_surrounding (focus, + text_input->surrounding.text, + char_cursor, + char_anchor); +} + +static void +text_input_v1_reset (struct wl_client *client, + struct wl_resource *resource) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + ClutterInputFocus *focus = text_input->input_focus; + + if (!client_matches_focus (text_input, client)) + return; + + /* + * This means text was changed outside of normal input method flow, but we are + * still focusing the same text entry, so we only reset states, but don't + * reset focus, cursor position and panel visibility. + */ + g_clear_pointer (&text_input->surrounding.text, g_free); + clutter_input_focus_set_surrounding (focus, NULL, 0, 0); + clutter_input_focus_set_content_hints (focus, 0); + clutter_input_focus_set_content_purpose (focus, + CLUTTER_INPUT_CONTENT_PURPOSE_NORMAL); +} + +static ClutterInputContentHintFlags +translate_hints (uint32_t hints) +{ + ClutterInputContentHintFlags clutter_hints = 0; + + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_DEFAULT) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_DEFAULT; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_PASSWORD) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_PASSWORD; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_COMPLETION) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_COMPLETION; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_CORRECTION) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_AUTO_CORRECTION; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_CAPITALIZATION) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_AUTO_CAPITALIZATION; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_LOWERCASE) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_LOWERCASE; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_UPPERCASE) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_UPPERCASE; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_TITLECASE) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_TITLECASE; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_HIDDEN_TEXT) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_HIDDEN_TEXT; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_SENSITIVE_DATA) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_SENSITIVE_DATA; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_LATIN) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_LATIN; + if (hints & ZWP_TEXT_INPUT_V1_CONTENT_HINT_MULTILINE) + clutter_hints |= CLUTTER_INPUT_CONTENT_HINT_MULTILINE; + + return clutter_hints; +} + +static ClutterInputContentPurpose +translate_purpose (uint32_t purpose) +{ + switch (purpose) + { + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NORMAL: + return CLUTTER_INPUT_CONTENT_PURPOSE_NORMAL; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_ALPHA: + return CLUTTER_INPUT_CONTENT_PURPOSE_ALPHA; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DIGITS: + return CLUTTER_INPUT_CONTENT_PURPOSE_DIGITS; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NUMBER: + return CLUTTER_INPUT_CONTENT_PURPOSE_NUMBER; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PHONE: + return CLUTTER_INPUT_CONTENT_PURPOSE_PHONE; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_URL: + return CLUTTER_INPUT_CONTENT_PURPOSE_URL; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_EMAIL: + return CLUTTER_INPUT_CONTENT_PURPOSE_EMAIL; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NAME: + return CLUTTER_INPUT_CONTENT_PURPOSE_NAME; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PASSWORD: + return CLUTTER_INPUT_CONTENT_PURPOSE_PASSWORD; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DATE: + return CLUTTER_INPUT_CONTENT_PURPOSE_DATE; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_TIME: + return CLUTTER_INPUT_CONTENT_PURPOSE_TIME; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DATETIME: + return CLUTTER_INPUT_CONTENT_PURPOSE_DATETIME; + case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_TERMINAL: + return CLUTTER_INPUT_CONTENT_PURPOSE_TERMINAL; + } + + g_warn_if_reached (); + return CLUTTER_INPUT_CONTENT_PURPOSE_NORMAL; +} + +static void +text_input_v1_set_content_type (struct wl_client *client, + struct wl_resource *resource, + uint32_t hint, + uint32_t purpose) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + ClutterInputFocus *focus = text_input->input_focus; + + if (!client_matches_focus (text_input, client)) + return; + + clutter_input_focus_set_content_hints (focus, translate_hints (hint)); + clutter_input_focus_set_content_purpose (focus, translate_purpose (purpose)); +} + +static void +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) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + ClutterInputFocus *focus = text_input->input_focus; + MtkRectangle rect = (MtkRectangle) { x, y, width, height }; + graphene_rect_t cursor_rect; + float x1, y1, x2, y2; + + if (!client_matches_focus (text_input, client)) + return; + + meta_wayland_surface_get_absolute_coordinates (text_input->surface, + rect.x, rect.y, &x1, &y1); + meta_wayland_surface_get_absolute_coordinates (text_input->surface, + rect.x + rect.width, + rect.y + rect.height, + &x2, &y2); + + graphene_rect_init (&cursor_rect, x1, y1, x2 - x1, y2 - y1); + clutter_input_focus_set_cursor_location (focus, &cursor_rect); +} + +static void +text_input_v1_set_preferred_lanaguage (struct wl_client *client, + struct wl_resource *resource, + const char *language) +{ + /* ClutterInputMethod does not support this so this is useless. */ +} + +/* + * text-input-v1 is not required to be double-buffered!!!!!!!!!!!!!!!!!!!!!!!!!! + * commit_state just means "I am giving you a new serial and you should use + * this". It can work without commit_state, chromium does not send this. + */ +static void +text_input_v1_commit_state (struct wl_client *client, + struct wl_resource *resource, + uint32_t serial) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + + if (!client_matches_focus (text_input, client)) + return; + + set_serial (text_input, resource, serial); +} + +static void +text_input_v1_invoke_action (struct wl_client *client, + struct wl_resource *resource, + uint32_t button, + uint32_t index) +{ + /* There is no doc about what button and index are, I am not an invoker. */ +} + +static struct zwp_text_input_v1_interface meta_text_input_v1_interface = { + text_input_v1_activate, + text_input_v1_deactivate, + text_input_v1_show_input_panel, + text_input_v1_hide_input_panel, + text_input_v1_reset, + text_input_v1_set_surrounding_text, + text_input_v1_set_content_type, + text_input_v1_set_cursor_rectangle, + text_input_v1_set_preferred_lanaguage, + text_input_v1_commit_state, + text_input_v1_invoke_action +}; + +void +meta_wayland_text_input_v1_destroy (MetaWaylandTextInputV1 *text_input) +{ + meta_wayland_text_input_v1_set_focus (text_input, NULL); + g_object_unref (text_input->input_focus); + g_hash_table_destroy (text_input->resource_serials); + g_clear_pointer (&text_input->surrounding.text, g_free); + g_free (text_input); +} + +static void +meta_wayland_text_input_v1_create_new_resource (MetaWaylandTextInputV1 *text_input, + struct wl_client *client, + uint32_t id) +{ + struct wl_resource *text_input_resource; + + text_input_resource = wl_resource_create (client, + &zwp_text_input_v1_interface, + META_ZWP_TEXT_INPUT_V1_VERSION, + id); + + wl_resource_set_implementation (text_input_resource, + &meta_text_input_v1_interface, + text_input, text_input_v1_destructor); + + if (text_input->surface && + wl_resource_get_client (text_input->surface->resource) == client) + { + wl_list_insert (&text_input->focus_resource_list, + wl_resource_get_link (text_input_resource)); + + zwp_text_input_v1_send_enter (text_input_resource, + text_input->surface->resource); + } + else + { + wl_list_insert (&text_input->resource_list, + wl_resource_get_link (text_input_resource)); + } +} + +static void +text_input_manager_v1_get_text_input (struct wl_client *client, + struct wl_resource *resource, + uint32_t id) +{ + MetaWaylandTextInputV1 *text_input = wl_resource_get_user_data (resource); + + meta_wayland_text_input_v1_create_new_resource (text_input, client, id); +} + +static struct zwp_text_input_manager_v1_interface meta_text_input_manager_v1_interface = { + text_input_manager_v1_get_text_input +}; + +static void +bind_text_input_v1 (struct wl_client *client, + void *data, + uint32_t version, + uint32_t id) +{ + MetaWaylandTextInputV1 *text_input = data; + struct wl_resource *resource; + + resource = wl_resource_create (client, + &zwp_text_input_manager_v1_interface, + META_ZWP_TEXT_INPUT_V1_VERSION, + id); + wl_resource_set_implementation (resource, + &meta_text_input_manager_v1_interface, + text_input, NULL); +} + +gboolean +meta_wayland_text_input_v1_init (MetaWaylandCompositor *compositor) +{ + return (wl_global_create (compositor->wayland_display, + &zwp_text_input_manager_v1_interface, + META_ZWP_TEXT_INPUT_V1_VERSION, + compositor->seat->text_input_v1, + bind_text_input_v1) != NULL); +} + +MetaWaylandTextInputV1 * +meta_wayland_text_input_v1_new (MetaWaylandSeat *seat) +{ + MetaWaylandTextInputV1 *text_input; + + text_input = g_new0 (MetaWaylandTextInputV1, 1); + text_input->input_focus = meta_wayland_text_input_focus_new (text_input); + text_input->seat = seat; + + wl_list_init (&text_input->resource_list); + wl_list_init (&text_input->focus_resource_list); + text_input->surface_listener.notify = text_input_v1_handle_focus_surface_destroy; + + text_input->resource_serials = g_hash_table_new (NULL, NULL); + + return text_input; +} + +/* This function eats key events and will send them to input method. */ +gboolean +meta_wayland_text_input_v1_update (MetaWaylandTextInputV1 *text_input, + const ClutterEvent *event) +{ + ClutterInputFocus *focus = text_input->input_focus; + ClutterEventType event_type; + + if (!text_input->surface || !clutter_input_focus_is_focused (focus)) + return FALSE; + + event_type = clutter_event_type (event); + + if (event_type == CLUTTER_KEY_PRESS || + event_type == CLUTTER_KEY_RELEASE) + { + gboolean filtered = FALSE; + + filtered = clutter_input_focus_filter_event (focus, event); + + return filtered; + } + + return FALSE; +} + +gboolean +meta_wayland_text_input_v1_handle_event (MetaWaylandTextInputV1 *text_input, + const ClutterEvent *event) +{ + ClutterInputFocus *focus = text_input->input_focus; + ClutterEventType event_type; + gboolean retval; + + if (!text_input->surface || !clutter_input_focus_is_focused (focus)) + return FALSE; + + event_type = clutter_event_type (event); + + retval = clutter_input_focus_process_event (focus, event); + + if (event_type == CLUTTER_BUTTON_PRESS || event_type == CLUTTER_TOUCH_BEGIN) + { + MetaWaylandSurface *surface = NULL; + MetaBackend *backend; + ClutterStage *stage; + ClutterActor *actor; + + backend = backend_from_text_input_v1 (text_input); + stage = CLUTTER_STAGE (meta_backend_get_stage (backend)); + + actor = clutter_stage_get_device_actor (stage, + clutter_event_get_device (event), + clutter_event_get_event_sequence (event)); + + if (META_IS_SURFACE_ACTOR_WAYLAND (actor)) + { + MetaSurfaceActorWayland *actor_wayland = + META_SURFACE_ACTOR_WAYLAND (actor); + + surface = meta_surface_actor_wayland_get_surface (actor_wayland); + + if (surface == text_input->surface) + clutter_input_focus_reset (focus); + } + } + + return retval; +} diff --git a/src/wayland/meta-wayland-text-input-v1.h b/src/wayland/meta-wayland-text-input-v1.h new file mode 100644 index 000000000..79b1c0a54 --- /dev/null +++ b/src/wayland/meta-wayland-text-input-v1.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 SUSE LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Author: Alynx Zhou + */ + +#pragma once + +#include + +#include "meta/window.h" +#include "wayland/meta-wayland-types.h" + +typedef struct _MetaWaylandTextInputV1 MetaWaylandTextInputV1; + +MetaWaylandTextInputV1 * meta_wayland_text_input_v1_new (MetaWaylandSeat *seat); +void meta_wayland_text_input_v1_destroy (MetaWaylandTextInputV1 *text_input); + +gboolean meta_wayland_text_input_v1_init (MetaWaylandCompositor *compositor); + +gboolean meta_wayland_text_input_v1_update (MetaWaylandTextInputV1 *text_input, + const ClutterEvent *event); + +gboolean meta_wayland_text_input_v1_handle_event (MetaWaylandTextInputV1 *text_input, + const ClutterEvent *event); diff --git a/src/wayland/meta-wayland-versions.h b/src/wayland/meta-wayland-versions.h index f0d035506..21d14b489 100644 --- a/src/wayland/meta-wayland-versions.h +++ b/src/wayland/meta-wayland-versions.h @@ -49,6 +49,7 @@ #define META_ZXDG_OUTPUT_V1_VERSION 3 #define META_ZWP_XWAYLAND_KEYBOARD_GRAB_V1_VERSION 1 #define META_ZWP_TEXT_INPUT_V3_VERSION 1 +#define META_ZWP_TEXT_INPUT_V1_VERSION 1 #define META_WP_VIEWPORTER_VERSION 1 #define META_ZWP_PRIMARY_SELECTION_V1_VERSION 1 #define META_WP_PRESENTATION_VERSION 1 diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c index fd7b6e473..c42d8a9c3 100644 --- a/src/wayland/meta-wayland.c +++ b/src/wayland/meta-wayland.c @@ -887,6 +887,7 @@ meta_wayland_compositor_new (MetaContext *context) meta_wayland_keyboard_shortcuts_inhibit_init (compositor); meta_wayland_surface_inhibit_shortcuts_dialog_init (); meta_wayland_text_input_init (compositor); + meta_wayland_text_input_v1_init (compositor); meta_wayland_init_presentation_time (compositor); meta_wayland_activation_init (compositor); meta_wayland_transaction_init (compositor); @@ -1153,6 +1154,12 @@ meta_wayland_compositor_get_text_input (MetaWaylandCompositor *compositor) return compositor->seat->text_input; } +MetaWaylandTextInputV1 * +meta_wayland_compositor_get_text_input_v1 (MetaWaylandCompositor *compositor) +{ + return compositor->seat->text_input_v1; +} + static void meta_wayland_compositor_update_focus (MetaWaylandCompositor *compositor, MetaWindow *window) diff --git a/src/wayland/meta-wayland.h b/src/wayland/meta-wayland.h index 0a0476eba..c23e82cdc 100644 --- a/src/wayland/meta-wayland.h +++ b/src/wayland/meta-wayland.h @@ -26,6 +26,7 @@ #include "meta/types.h" #include "meta/meta-wayland-compositor.h" #include "wayland/meta-wayland-text-input.h" +#include "wayland/meta-wayland-text-input-v1.h" #include "wayland/meta-wayland-types.h" META_EXPORT_TEST @@ -88,6 +89,7 @@ void meta_wayland_compositor_schedule_surface_association (Me MetaWindow *window); MetaWaylandTextInput * meta_wayland_compositor_get_text_input (MetaWaylandCompositor *compositor); +MetaWaylandTextInputV1 * meta_wayland_compositor_get_text_input_v1 (MetaWaylandCompositor *compositor); #ifdef HAVE_XWAYLAND void meta_wayland_compositor_notify_surface_id (MetaWaylandCompositor *compositor,