File mutter-implement-text-input-v1.patch of Package mutter

From 2a94e19b00434fe4d7ab858a6cdcff6364f6e408 Mon Sep 17 00:00:00 2001
From: Alynx Zhou <alynx.zhou@gmail.com>
Date: Wed, 15 May 2024 00:07:41 +0800
Subject: [PATCH 2/2] 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 <https://gitlab.gnome.org/GNOME/mutter/-/issues/3200>.
---
 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

Index: mutter-48.0/clutter/clutter/clutter-enums.h
===================================================================
--- mutter-48.0.orig/clutter/clutter/clutter-enums.h
+++ mutter-48.0/clutter/clutter/clutter-enums.h
@@ -1128,6 +1128,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
Index: mutter-48.0/src/core/events.c
===================================================================
--- mutter-48.0.orig/src/core/events.c
+++ mutter-48.0/src/core/events.c
@@ -244,6 +244,7 @@ meta_display_handle_event (MetaDisplay
 #ifdef HAVE_WAYLAND
   MetaWaylandCompositor *wayland_compositor;
   MetaWaylandTextInput *wayland_text_input = NULL;
+  MetaWaylandTextInputV1 *wayland_text_input_v1 = NULL;
 #endif
 
 #ifdef HAVE_WAYLAND
@@ -252,6 +253,8 @@ meta_display_handle_event (MetaDisplay
     {
       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
 
@@ -300,9 +303,11 @@ meta_display_handle_event (MetaDisplay
     }
 
 #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)
Index: mutter-48.0/src/meson.build
===================================================================
--- mutter-48.0.orig/src/meson.build
+++ mutter-48.0/src/meson.build
@@ -694,6 +694,8 @@ if have_wayland
     'wayland/meta-wayland-tablet-pad.c',
     'wayland/meta-wayland-tablet-pad-group.c',
     'wayland/meta-wayland-tablet-pad-group.h',
+    'wayland/meta-wayland-text-input-v1.c',
+    'wayland/meta-wayland-text-input-v1.h',
     'wayland/meta-wayland-tablet-pad.h',
     'wayland/meta-wayland-tablet-pad-ring.c',
     'wayland/meta-wayland-tablet-pad-ring.h',
@@ -1146,6 +1148,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', ],
Index: mutter-48.0/src/wayland/meta-wayland-seat.c
===================================================================
--- mutter-48.0.orig/src/wayland/meta-wayland-seat.c
+++ mutter-48.0/src/wayland/meta-wayland-seat.c
@@ -236,6 +236,7 @@ default_focus (MetaWaylandEventHandler *
                                                   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)
@@ -301,6 +302,8 @@ meta_wayland_seat_new (MetaWaylandCompos
                               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);
@@ -346,6 +349,7 @@ meta_wayland_seat_free (MetaWaylandSeat
   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);
 }
@@ -498,7 +502,10 @@ meta_wayland_seat_handle_event_internal
   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)
@@ -530,7 +537,8 @@ meta_wayland_seat_handle_event_internal
     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;
Index: mutter-48.0/src/wayland/meta-wayland-seat.h
===================================================================
--- mutter-48.0.orig/src/wayland/meta-wayland-seat.h
+++ mutter-48.0/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;
Index: mutter-48.0/src/wayland/meta-wayland-text-input-v1.c
===================================================================
--- /dev/null
+++ mutter-48.0/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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alynx Zhou <alynx.zhou@gmail.com>
+ */
+
+#include "config.h"
+#include "wayland/meta-wayland-text-input-v1.h"
+
+#include <wayland-server.h>
+
+#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;
+}
Index: mutter-48.0/src/wayland/meta-wayland-text-input-v1.h
===================================================================
--- /dev/null
+++ mutter-48.0/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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alynx Zhou <alynx.zhou@gmail.com>
+ */
+
+#pragma once
+
+#include <wayland-server.h>
+
+#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);
Index: mutter-48.0/src/wayland/meta-wayland-versions.h
===================================================================
--- mutter-48.0.orig/src/wayland/meta-wayland-versions.h
+++ mutter-48.0/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        2
Index: mutter-48.0/src/wayland/meta-wayland.c
===================================================================
--- mutter-48.0.orig/src/wayland/meta-wayland.c
+++ mutter-48.0/src/wayland/meta-wayland.c
@@ -983,6 +983,7 @@ meta_wayland_compositor_new (MetaContext
   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);
@@ -1252,6 +1253,12 @@ meta_wayland_compositor_get_text_input (
   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)
Index: mutter-48.0/src/wayland/meta-wayland.h
===================================================================
--- mutter-48.0.orig/src/wayland/meta-wayland.h
+++ mutter-48.0/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
@@ -99,6 +100,7 @@ void                    meta_wayland_com
                                                                               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,
openSUSE Build Service is sponsored by