1
0
Fork 0

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>.

Signed-off-by: Mingi Sung <sungmg@saltyming.net>
This commit is contained in:
Alynx Zhou 2024-05-15 00:07:41 +08:00 committed by Mingi Sung
parent 8b8f052ff9
commit f5adba2fae
Signed by: sungmg
GPG key ID: 41BAFD6FFD8036C5
10 changed files with 933 additions and 5 deletions

View file

@ -1172,6 +1172,9 @@ typedef enum
CLUTTER_INPUT_CONTENT_HINT_SENSITIVE_DATA = 1 << 7, CLUTTER_INPUT_CONTENT_HINT_SENSITIVE_DATA = 1 << 7,
CLUTTER_INPUT_CONTENT_HINT_LATIN = 1 << 8, CLUTTER_INPUT_CONTENT_HINT_LATIN = 1 << 8,
CLUTTER_INPUT_CONTENT_HINT_MULTILINE = 1 << 9, 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; } ClutterInputContentHintFlags;
typedef enum typedef enum

View file

@ -239,6 +239,7 @@ meta_display_handle_event (MetaDisplay *display,
#ifdef HAVE_WAYLAND #ifdef HAVE_WAYLAND
MetaWaylandCompositor *wayland_compositor; MetaWaylandCompositor *wayland_compositor;
MetaWaylandTextInput *wayland_text_input = NULL; MetaWaylandTextInput *wayland_text_input = NULL;
MetaWaylandTextInputV1 *wayland_text_input_v1 = NULL;
#endif #endif
#ifdef HAVE_WAYLAND #ifdef HAVE_WAYLAND
@ -247,6 +248,8 @@ meta_display_handle_event (MetaDisplay *display,
{ {
wayland_text_input = wayland_text_input =
meta_wayland_compositor_get_text_input (wayland_compositor); meta_wayland_compositor_get_text_input (wayland_compositor);
wayland_text_input_v1 =
meta_wayland_compositor_get_text_input_v1 (wayland_compositor);
} }
#endif #endif
@ -288,9 +291,11 @@ meta_display_handle_event (MetaDisplay *display,
} }
#ifdef HAVE_WAYLAND #ifdef HAVE_WAYLAND
if (wayland_text_input && if (!meta_compositor_get_current_window_drag (compositor) &&
!meta_compositor_get_current_window_drag (compositor) && ((wayland_text_input &&
meta_wayland_text_input_update (wayland_text_input, event)) 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; return CLUTTER_EVENT_STOP;
if (wayland_compositor) if (wayland_compositor)

View file

@ -696,6 +696,8 @@ if have_wayland
'wayland/meta-wayland-tablet-tool.h', 'wayland/meta-wayland-tablet-tool.h',
'wayland/meta-wayland-text-input.c', 'wayland/meta-wayland-text-input.c',
'wayland/meta-wayland-text-input.h', '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.c',
'wayland/meta-wayland-touch.h', 'wayland/meta-wayland-touch.h',
'wayland/meta-wayland-transaction.c', 'wayland/meta-wayland-transaction.c',
@ -1109,6 +1111,7 @@ if have_wayland
['single-pixel-buffer', 'staging', 'v1', ], ['single-pixel-buffer', 'staging', 'v1', ],
['tablet', 'unstable', 'v2', ], ['tablet', 'unstable', 'v2', ],
['text-input', 'unstable', 'v3', ], ['text-input', 'unstable', 'v3', ],
['text-input', 'unstable', 'v1', ],
['viewporter', 'stable', ], ['viewporter', 'stable', ],
['xdg-activation', 'staging', 'v1', ], ['xdg-activation', 'staging', 'v1', ],
['xdg-dialog', 'staging', 'v1', ], ['xdg-dialog', 'staging', 'v1', ],

View file

@ -232,6 +232,7 @@ default_focus (MetaWaylandEventHandler *handler,
surface); surface);
meta_wayland_tablet_seat_set_pad_focus (seat->tablet_seat, surface); meta_wayland_tablet_seat_set_pad_focus (seat->tablet_seat, surface);
meta_wayland_text_input_set_focus (seat->text_input, 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) if (caps & CLUTTER_INPUT_CAPABILITY_TABLET_TOOL)
@ -297,6 +298,8 @@ meta_wayland_seat_new (MetaWaylandCompositor *compositor,
NULL); NULL);
seat->text_input = meta_wayland_text_input_new (seat); 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_init (&seat->data_device, seat);
meta_wayland_data_device_primary_init (&seat->primary_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); g_object_unref (seat->touch);
meta_wayland_text_input_destroy (seat->text_input); meta_wayland_text_input_destroy (seat->text_input);
meta_wayland_text_input_v1_destroy (seat->text_input_v1);
g_free (seat); g_free (seat);
} }
@ -494,7 +498,10 @@ meta_wayland_seat_handle_event_internal (MetaWaylandSeat *seat,
if (event_type == CLUTTER_BUTTON_PRESS || if (event_type == CLUTTER_BUTTON_PRESS ||
event_type == CLUTTER_TOUCH_BEGIN) 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) switch (event_type)
@ -526,7 +533,8 @@ meta_wayland_seat_handle_event_internal (MetaWaylandSeat *seat,
case CLUTTER_IM_COMMIT: case CLUTTER_IM_COMMIT:
case CLUTTER_IM_DELETE: case CLUTTER_IM_DELETE:
case CLUTTER_IM_PREEDIT: 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; return TRUE;
break; break;

View file

@ -30,6 +30,7 @@
#include "wayland/meta-wayland-pointer.h" #include "wayland/meta-wayland-pointer.h"
#include "wayland/meta-wayland-tablet-tool.h" #include "wayland/meta-wayland-tablet-tool.h"
#include "wayland/meta-wayland-text-input.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-touch.h"
#include "wayland/meta-wayland-types.h" #include "wayland/meta-wayland-types.h"
@ -51,6 +52,7 @@ struct _MetaWaylandSeat
MetaWaylandDataDevicePrimary primary_data_device; MetaWaylandDataDevicePrimary primary_data_device;
MetaWaylandTextInput *text_input; MetaWaylandTextInput *text_input;
MetaWaylandTextInputV1 *text_input_v1;
MetaWaylandInput *input_handler; MetaWaylandInput *input_handler;
MetaWaylandEventHandler *default_handler; MetaWaylandEventHandler *default_handler;

View file

@ -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;
}

View file

@ -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);

View file

@ -49,6 +49,7 @@
#define META_ZXDG_OUTPUT_V1_VERSION 3 #define META_ZXDG_OUTPUT_V1_VERSION 3
#define META_ZWP_XWAYLAND_KEYBOARD_GRAB_V1_VERSION 1 #define META_ZWP_XWAYLAND_KEYBOARD_GRAB_V1_VERSION 1
#define META_ZWP_TEXT_INPUT_V3_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_WP_VIEWPORTER_VERSION 1
#define META_ZWP_PRIMARY_SELECTION_V1_VERSION 1 #define META_ZWP_PRIMARY_SELECTION_V1_VERSION 1
#define META_WP_PRESENTATION_VERSION 1 #define META_WP_PRESENTATION_VERSION 1

View file

@ -887,6 +887,7 @@ meta_wayland_compositor_new (MetaContext *context)
meta_wayland_keyboard_shortcuts_inhibit_init (compositor); meta_wayland_keyboard_shortcuts_inhibit_init (compositor);
meta_wayland_surface_inhibit_shortcuts_dialog_init (); meta_wayland_surface_inhibit_shortcuts_dialog_init ();
meta_wayland_text_input_init (compositor); meta_wayland_text_input_init (compositor);
meta_wayland_text_input_v1_init (compositor);
meta_wayland_init_presentation_time (compositor); meta_wayland_init_presentation_time (compositor);
meta_wayland_activation_init (compositor); meta_wayland_activation_init (compositor);
meta_wayland_transaction_init (compositor); meta_wayland_transaction_init (compositor);
@ -1153,6 +1154,12 @@ meta_wayland_compositor_get_text_input (MetaWaylandCompositor *compositor)
return compositor->seat->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 static void
meta_wayland_compositor_update_focus (MetaWaylandCompositor *compositor, meta_wayland_compositor_update_focus (MetaWaylandCompositor *compositor,
MetaWindow *window) MetaWindow *window)

View file

@ -26,6 +26,7 @@
#include "meta/types.h" #include "meta/types.h"
#include "meta/meta-wayland-compositor.h" #include "meta/meta-wayland-compositor.h"
#include "wayland/meta-wayland-text-input.h" #include "wayland/meta-wayland-text-input.h"
#include "wayland/meta-wayland-text-input-v1.h"
#include "wayland/meta-wayland-types.h" #include "wayland/meta-wayland-types.h"
META_EXPORT_TEST META_EXPORT_TEST
@ -88,6 +89,7 @@ void meta_wayland_compositor_schedule_surface_association (Me
MetaWindow *window); MetaWindow *window);
MetaWaylandTextInput * meta_wayland_compositor_get_text_input (MetaWaylandCompositor *compositor); MetaWaylandTextInput * meta_wayland_compositor_get_text_input (MetaWaylandCompositor *compositor);
MetaWaylandTextInputV1 * meta_wayland_compositor_get_text_input_v1 (MetaWaylandCompositor *compositor);
#ifdef HAVE_XWAYLAND #ifdef HAVE_XWAYLAND
void meta_wayland_compositor_notify_surface_id (MetaWaylandCompositor *compositor, void meta_wayland_compositor_notify_surface_id (MetaWaylandCompositor *compositor,