From 8d77256d53500c16eaf4cf880506e5b720d8623f Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Thu, 29 Mar 2018 19:49:10 +0200 Subject: [PATCH] imwayland: Add text-input-unstable-v3 support The wayland input module now represents text-input-unstable-v3 support, while the old module supporting gtk-text-input was renamed to gtkwayland. --- gtk/gtkimmodule.c | 8 + gtk/gtksettings.c | 9 +- modules/input/Makefile.am | 27 +- modules/input/imwayland.c | 322 +++++++---- modules/input/imwaylandgtk.c | 703 +++++++++++++++++++++++ modules/input/text-input-unstable-v3.xml | 441 ++++++++++++++ po-properties/POTFILES.in | 1 + 7 files changed, 1404 insertions(+), 107 deletions(-) create mode 100644 modules/input/imwaylandgtk.c create mode 100644 modules/input/text-input-unstable-v3.xml diff --git a/gtk/gtkimmodule.c b/gtk/gtkimmodule.c index 93c1bea0ad..c31c69bf54 100644 --- a/gtk/gtkimmodule.c +++ b/gtk/gtkimmodule.c @@ -697,6 +697,14 @@ match_backend (GtkIMContextInfo *context) { #ifdef GDK_WINDOWING_WAYLAND if (g_strcmp0 (context->context_id, "wayland") == 0) + { + GdkDisplay *display = gdk_display_get_default (); + + return GDK_IS_WAYLAND_DISPLAY (display) && + gdk_wayland_display_query_registry (display, + "zwp_text_input_manager_v3"); + } + if (g_strcmp0 (context->context_id, "waylandgtk") == 0) { GdkDisplay *display = gdk_display_get_default (); diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c index 08e16f074a..571ae11cd4 100644 --- a/gtk/gtksettings.c +++ b/gtk/gtksettings.c @@ -1907,12 +1907,19 @@ gtk_settings_create_for_display (GdkDisplay *display) if (GDK_IS_WAYLAND_DISPLAY (display)) { if (gdk_wayland_display_query_registry (display, - "gtk_text_input_manager")) + "zwp_text_input_manager_v3")) { settings = g_object_new (GTK_TYPE_SETTINGS, "gtk-im-module", "wayland", NULL); } + else if (gdk_wayland_display_query_registry (display, + "gtk_text_input_manager")) + { + settings = g_object_new (GTK_TYPE_SETTINGS, + "gtk-im-module", "waylandgtk", + NULL); + } else { /* Fallback to other IM methods if the compositor does not diff --git a/modules/input/Makefile.am b/modules/input/Makefile.am index bf6c1a11bd..ce394632ee 100644 --- a/modules/input/Makefile.am +++ b/modules/input/Makefile.am @@ -175,8 +175,8 @@ endif endif protocol_built_sources = \ - gtk-text-input-protocol.c \ - gtk-text-input-client-protocol.h + text-input-unstable-v3-protocol.c \ + text-input-unstable-v3-client-protocol.h im_wayland_la_CPPFLAGS = $(AM_CPPFLAGS) im_wayland_la_LDFLAGS = -rpath $(moduledir) -avoid-version -module $(no_undefined) @@ -191,6 +191,23 @@ WAYLAND_MODULE = im-wayland.la endif endif +protocol_built_sources_gtk = \ + gtk-text-input-protocol.c \ + gtk-text-input-client-protocol.h + +im_waylandgtk_la_CPPFLAGS = $(AM_CPPFLAGS) +im_waylandgtk_la_LDFLAGS = -rpath $(moduledir) -avoid-version -module $(no_undefined) +im_waylandgtk_la_SOURCES = imwaylandgtk.c $(protocol_built_sources_gtk) +libstatic_im_waylandgtk_la_SOURCES = $(im_waylandgtk_la_SOURCES) +im_waylandgtk_la_LIBADD = $(LDADDS) +if INCLUDE_IM_WAYLAND +STATIC_WAYLANDGTK_MODULE = libstatic-im-waylandgtk.la +else +if USE_WAYLAND +WAYLANDGTK_MODULE = im-waylandgtk.la +endif +endif + multipress_defs = -DMULTIPRESS_LOCALEDIR=\""$(mplocaledir)"\" -DMULTIPRESS_CONFDIR=\""$(sysconfdir)/gtk-3.0"\" im_multipress_la_CPPFLAGS = $(AM_CPPFLAGS) $(multipress_defs) libstatic_im_multipress_la_CPPFLAGS = $(im_multipress_la_CPPFLAGS) @@ -253,6 +270,7 @@ module_LTLIBRARIES = \ $(QUARTZ_MODULE) \ $(BROADWAY_MODULE) \ $(WAYLAND_MODULE) \ + $(WAYLANDGTK_MODULE) \ $(THAI_MODULE) \ $(TI_ER_MODULE) \ $(TI_ET_MODULE) \ @@ -272,6 +290,7 @@ noinst_LTLIBRARIES = \ $(STATIC_QUARTZ_MODULE) \ $(STATIC_BROADWAY_MODULE) \ $(STATIC_WAYLAND_MODULE) \ + $(STATIC_WAYLANDGTK_MODULE) \ $(STATIC_THAI_MODULE) \ $(STATIC_TI_ER_MODULE) \ $(STATIC_TI_ET_MODULE) \ @@ -284,8 +303,8 @@ immodules.cache: Makefile.am $(module_LTLIBRARIES) $(AM_V_GEN) $(top_builddir)/gtk/gtk-query-immodules-3.0$(EXEEXT) $(module_LTLIBRARIES) > immodules.cache if USE_WAYLAND -BUILT_SOURCES = $(protocol_built_sources) -EXTRA_DIST += $(protocol_built_sources) +BUILT_SOURCES = $(protocol_built_sources) $(protocol_built_sources_gtk) +EXTRA_DIST += $(protocol_built_sources) $(protocol_built_sources_gtk) endif EXTRA_DIST += README.multipress diff --git a/modules/input/imwayland.c b/modules/input/imwayland.c index 96ffbae82c..3589c4c130 100644 --- a/modules/input/imwayland.c +++ b/modules/input/imwayland.c @@ -1,5 +1,6 @@ /* GTK - The GIMP Toolkit * Copyright (C) 2017 Red Hat, Inc. + * Copyright (C) 2018 Purism SPC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,7 +26,7 @@ #include "gtk/gtkimmodule.h" #include "gdk/wayland/gdkwayland.h" -#include "gtk-text-input-client-protocol.h" +#include "text-input-unstable-v3-client-protocol.h" typedef struct _GtkIMContextWaylandGlobal GtkIMContextWaylandGlobal; typedef struct _GtkIMContextWayland GtkIMContextWayland; @@ -36,9 +37,8 @@ struct _GtkIMContextWaylandGlobal struct wl_display *display; struct wl_registry *registry; uint32_t text_input_manager_wl_id; - struct gtk_text_input_manager *text_input_manager; - struct gtk_text_input *text_input; - uint32_t enter_serial; + struct zwp_text_input_manager_v3 *text_input_manager; + struct zwp_text_input_v3 *text_input; GtkIMContext *current; }; @@ -48,6 +48,17 @@ struct _GtkIMContextWaylandClass GtkIMContextSimpleClass parent_class; }; +struct preedit { + gchar *text; + gint cursor_begin; + gint cursor_end; +}; + +struct surrounding_delete { + guint before_length; + guint after_length; +}; + struct _GtkIMContextWayland { GtkIMContextSimple parent_instance; @@ -61,12 +72,19 @@ struct _GtkIMContextWayland struct { gchar *text; gint cursor_idx; + gint anchor_idx; } surrounding; - struct { - gchar *text; - gint cursor_idx; - } preedit; + enum zwp_text_input_v3_change_cause surrounding_change; + + guint serial; + + struct surrounding_delete pending_surrounding_delete; + + struct preedit current_preedit; + struct preedit pending_preedit; + + gchar *pending_commit; cairo_rectangle_int_t cursor_rect; guint use_preedit : 1; @@ -99,102 +117,182 @@ static const GtkIMContextInfo *info_list[] = #endif static void -reset_preedit (GtkIMContextWayland *context) +notify_external_change (GtkIMContextWayland *context) { - if (context->preedit.text == NULL) - return; + gboolean result; - g_clear_pointer (&context->preedit.text, g_free); - context->preedit.cursor_idx = 0; - g_signal_emit_by_name (context, "preedit-changed"); + context->surrounding_change = ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER; + g_signal_emit_by_name (global->current, "retrieve-surrounding", &result); } static void text_input_enter (void *data, - struct gtk_text_input *text_input, - uint32_t serial, + struct zwp_text_input_v3 *text_input, struct wl_surface *surface) -{ - GtkIMContextWaylandGlobal *global = data; - - global->enter_serial = serial; -} +{} static void text_input_leave (void *data, - struct gtk_text_input *text_input, - uint32_t serial, + struct zwp_text_input_v3 *text_input, struct wl_surface *surface) { + /* GtkIMContextWayland *context; if (!global->current) return; context = GTK_IM_CONTEXT_WAYLAND (global->current); + TODO: does this clear text input or modify text? reset_preedit (context); + */ } static void text_input_preedit (void *data, - struct gtk_text_input *text_input, + struct zwp_text_input_v3 *text_input, const char *text, - guint cursor) + gint cursor_begin, + gint cursor_end) { GtkIMContextWayland *context; - gboolean state_change; + GtkIMContextWaylandGlobal *global = data; if (!global->current) return; context = GTK_IM_CONTEXT_WAYLAND (global->current); - if (!text && !context->preedit.text) + + g_free (context->pending_preedit.text); + context->pending_preedit.text = g_strdup (text); + context->pending_preedit.cursor_begin = cursor_begin; + context->pending_preedit.cursor_end = cursor_end; +} + + +static void +text_input_preedit_apply (GtkIMContextWaylandGlobal *global) +{ + GtkIMContextWayland *context; + gboolean state_change; + struct preedit defaults = {0}; + + if (!global->current) return; - state_change = ((text == NULL) != (context->preedit.text == NULL)); + context = GTK_IM_CONTEXT_WAYLAND (global->current); - if (state_change && !context->preedit.text) + state_change = ((context->pending_preedit.text == NULL) + != (context->current_preedit.text == NULL)); + + if (state_change && !context->current_preedit.text) g_signal_emit_by_name (context, "preedit-start"); - g_free (context->preedit.text); - context->preedit.text = g_strdup (text); - context->preedit.cursor_idx = cursor; + g_free (context->current_preedit.text); + context->current_preedit = context->pending_preedit; + context->pending_preedit = defaults; g_signal_emit_by_name (context, "preedit-changed"); - if (state_change && !context->preedit.text) + if (state_change && !context->current_preedit.text) g_signal_emit_by_name (context, "preedit-end"); } static void text_input_commit (void *data, - struct gtk_text_input *text_input, + struct zwp_text_input_v3 *text_input, const char *text) { GtkIMContextWaylandGlobal *global = data; + GtkIMContextWayland *context; - if (global->current && text) - g_signal_emit_by_name (global->current, "commit", text); + if (!global->current) + return; + + context = GTK_IM_CONTEXT_WAYLAND (global->current); + + g_free (context->pending_commit); + context->pending_commit = g_strdup (text); +} + +static void +text_input_commit_apply (GtkIMContextWaylandGlobal *global, gboolean valid) +{ + GtkIMContextWayland *context; + context = GTK_IM_CONTEXT_WAYLAND (global->current); + if (context->pending_commit && valid) + g_signal_emit_by_name (global->current, "commit", context->pending_commit); + g_free (context->pending_commit); + context->pending_commit = NULL; } static void text_input_delete_surrounding_text (void *data, - struct gtk_text_input *text_input, - uint32_t offset, - uint32_t len) + struct zwp_text_input_v3 *text_input, + uint32_t before_length, + uint32_t after_length) { GtkIMContextWaylandGlobal *global = data; + GtkIMContextWayland *context; - if (global->current) - g_signal_emit_by_name (global->current, "delete-surrounding", offset, len); + if (!global->current) + return; + + context = GTK_IM_CONTEXT_WAYLAND (global->current); + + context->pending_surrounding_delete.before_length = before_length; + context->pending_surrounding_delete.after_length = after_length; } -static const struct gtk_text_input_listener text_input_listener = { +static void +text_input_delete_surrounding_text_apply (GtkIMContextWaylandGlobal *global, + gboolean valid) +{ + GtkIMContextWayland *context; + + gint len; + struct surrounding_delete defaults = {0}; + + context = GTK_IM_CONTEXT_WAYLAND (global->current); + + len = context->pending_surrounding_delete.after_length + + context->pending_surrounding_delete.before_length; + if (len > 0 && valid) + g_signal_emit_by_name (global->current, "delete-surrounding", + -context->pending_surrounding_delete.before_length, + len); + context->pending_surrounding_delete = defaults; +} + +static void +text_input_done (void *data, + struct zwp_text_input_v3 *text_input, + uint32_t serial) +{ + GtkIMContextWayland *context; + GtkIMContextWaylandGlobal *global = data; + gboolean result; + gboolean valid; + + if (!global->current) + return; + + context = GTK_IM_CONTEXT_WAYLAND (global->current); + + valid = serial == context->serial; + text_input_delete_surrounding_text_apply(global, valid); + text_input_commit_apply(global, valid); + g_signal_emit_by_name (global->current, "retrieve-surrounding", &result); + text_input_preedit_apply(global); +} + +static const struct zwp_text_input_v3_listener text_input_listener = { text_input_enter, text_input_leave, text_input_preedit, text_input_commit, - text_input_delete_surrounding_text + text_input_delete_surrounding_text, + text_input_done, }; static void @@ -207,17 +305,17 @@ registry_handle_global (void *data, GtkIMContextWaylandGlobal *global = data; GdkSeat *seat = gdk_display_get_default_seat (gdk_display_get_default ()); - if (strcmp (interface, "gtk_text_input_manager") == 0) + if (strcmp (interface, "zwp_text_input_manager_v3") == 0) { global->text_input_manager_wl_id = id; global->text_input_manager = wl_registry_bind (global->registry, global->text_input_manager_wl_id, - >k_text_input_manager_interface, 1); + &zwp_text_input_manager_v3_interface, 1); global->text_input = - gtk_text_input_manager_get_text_input (global->text_input_manager, - gdk_wayland_seat_get_wl_seat (seat)); - gtk_text_input_add_listener (global->text_input, - &text_input_listener, global); + zwp_text_input_manager_v3_get_text_input (global->text_input_manager, + gdk_wayland_seat_get_wl_seat (seat)); + zwp_text_input_v3_add_listener (global->text_input, + &text_input_listener, global); } } @@ -231,8 +329,8 @@ registry_handle_global_remove (void *data, if (id != global->text_input_manager_wl_id) return; - g_clear_pointer(&global->text_input, gtk_text_input_destroy); - g_clear_pointer(&global->text_input_manager, gtk_text_input_manager_destroy); + g_clear_pointer(&global->text_input, zwp_text_input_v3_destroy); + g_clear_pointer(&global->text_input_manager, zwp_text_input_manager_v3_destroy); } static const struct wl_registry_listener registry_listener = { @@ -262,10 +360,12 @@ notify_surrounding_text (GtkIMContextWayland *context) if (!context->surrounding.text) return; - gtk_text_input_set_surrounding_text (global->text_input, - context->surrounding.text, - context->surrounding.cursor_idx, - context->surrounding.cursor_idx); + zwp_text_input_v3_set_surrounding_text (global->text_input, + context->surrounding.text, + context->surrounding.cursor_idx, + context->surrounding.anchor_idx); + zwp_text_input_v3_set_text_change_cause (global->text_input, + context->surrounding_change); } static void @@ -284,9 +384,9 @@ notify_cursor_location (GtkIMContextWayland *context) gdk_window_get_root_coords (context->window, rect.x, rect.y, &rect.x, &rect.y); - gtk_text_input_set_cursor_rectangle (global->text_input, - rect.x, rect.y, - rect.width, rect.height); + zwp_text_input_v3_set_cursor_rectangle (global->text_input, + rect.x, rect.y, + rect.width, rect.height); } static uint32_t @@ -296,23 +396,23 @@ translate_hints (GtkInputHints input_hints, uint32_t hints = 0; if (input_hints & GTK_INPUT_HINT_SPELLCHECK) - hints |= GTK_TEXT_INPUT_CONTENT_HINT_SPELLCHECK; + hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK; if (input_hints & GTK_INPUT_HINT_WORD_COMPLETION) - hints |= GTK_TEXT_INPUT_CONTENT_HINT_COMPLETION; + hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION; if (input_hints & GTK_INPUT_HINT_LOWERCASE) - hints |= GTK_TEXT_INPUT_CONTENT_HINT_LOWERCASE; + hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE; if (input_hints & GTK_INPUT_HINT_UPPERCASE_CHARS) - hints |= GTK_TEXT_INPUT_CONTENT_HINT_UPPERCASE; + hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE; if (input_hints & GTK_INPUT_HINT_UPPERCASE_WORDS) - hints |= GTK_TEXT_INPUT_CONTENT_HINT_TITLECASE; + hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE; if (input_hints & GTK_INPUT_HINT_UPPERCASE_SENTENCES) - hints |= GTK_TEXT_INPUT_CONTENT_HINT_AUTO_CAPITALIZATION; + hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION; if (purpose == GTK_INPUT_PURPOSE_PIN || purpose == GTK_INPUT_PURPOSE_PASSWORD) { - hints |= (GTK_TEXT_INPUT_CONTENT_HINT_HIDDEN_TEXT | - GTK_TEXT_INPUT_CONTENT_HINT_SENSITIVE_DATA); + hints |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | + ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA); } return hints; @@ -324,28 +424,28 @@ translate_purpose (GtkInputPurpose purpose) switch (purpose) { case GTK_INPUT_PURPOSE_FREE_FORM: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_NORMAL; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL; case GTK_INPUT_PURPOSE_ALPHA: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_ALPHA; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA; case GTK_INPUT_PURPOSE_DIGITS: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_DIGITS; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS; case GTK_INPUT_PURPOSE_NUMBER: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_NUMBER; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER; case GTK_INPUT_PURPOSE_PHONE: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_PHONE; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE; case GTK_INPUT_PURPOSE_URL: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_URL; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL; case GTK_INPUT_PURPOSE_EMAIL: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_EMAIL; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL; case GTK_INPUT_PURPOSE_NAME: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_NAME; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME; case GTK_INPUT_PURPOSE_PASSWORD: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_PASSWORD; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD; case GTK_INPUT_PURPOSE_PIN: - return GTK_TEXT_INPUT_CONTENT_PURPOSE_PIN; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN; } - return GTK_TEXT_INPUT_CONTENT_PURPOSE_NORMAL; + return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL; } static void @@ -362,9 +462,9 @@ notify_content_type (GtkIMContextWayland *context) "input-purpose", &purpose, NULL); - gtk_text_input_set_content_type (global->text_input, - translate_hints (hints, purpose), - translate_purpose (purpose)); + zwp_text_input_v3_set_content_type (global->text_input, + translate_hints (hints, purpose), + translate_purpose (purpose)); } static void @@ -372,23 +472,15 @@ commit_state (GtkIMContextWayland *context) { if (global->current != GTK_IM_CONTEXT (context)) return; - gtk_text_input_commit (global->text_input); + context->serial++; + zwp_text_input_v3_commit (global->text_input); + context->surrounding_change = ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD; } static void -enable_text_input (GtkIMContextWayland *context, - gboolean toggle_panel) +enable_text_input (GtkIMContextWayland *context) { - guint flags = 0; - - if (context->use_preedit) - flags |= GTK_TEXT_INPUT_ENABLE_FLAGS_CAN_SHOW_PREEDIT; - if (toggle_panel) - flags |= GTK_TEXT_INPUT_ENABLE_FLAGS_TOGGLE_INPUT_PANEL; - - gtk_text_input_enable (global->text_input, - global->enter_serial, - flags); + zwp_text_input_v3_enable (global->text_input); } static void @@ -424,6 +516,7 @@ released_cb (GtkGestureMultiPress *gesture, GtkIMContextWayland *context) { GtkInputHints hints; + gboolean result; if (!global->current) return; @@ -437,7 +530,9 @@ released_cb (GtkGestureMultiPress *gesture, context->press_y, x, y)) { - enable_text_input (GTK_IM_CONTEXT_WAYLAND (context), TRUE); + enable_text_input (GTK_IM_CONTEXT_WAYLAND (context)); + g_signal_emit_by_name (global->current, "retrieve-surrounding", &result); + commit_state (context); } } @@ -491,7 +586,9 @@ gtk_im_context_wayland_get_preedit_string (GtkIMContext *context, if (attrs) *attrs = NULL; - GTK_IM_CONTEXT_CLASS (parent_class)->get_preedit_string (context, str, attrs, cursor_pos); + GTK_IM_CONTEXT_CLASS (parent_class)->get_preedit_string (context, + str, attrs, + cursor_pos); /* If the parent implementation returns a len>0 string, go with it */ if (str && *str) @@ -503,12 +600,12 @@ gtk_im_context_wayland_get_preedit_string (GtkIMContext *context, } preedit_str = - context_wayland->preedit.text ? context_wayland->preedit.text : ""; + context_wayland->current_preedit.text ? context_wayland->current_preedit.text : ""; if (str) *str = g_strdup (preedit_str); if (cursor_pos) - *cursor_pos = context_wayland->preedit.cursor_idx; + *cursor_pos = context_wayland->current_preedit.cursor_begin; if (attrs) { @@ -516,6 +613,15 @@ gtk_im_context_wayland_get_preedit_string (GtkIMContext *context, *attrs = pango_attr_list_new (); pango_attr_list_insert (*attrs, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE)); + if (context_wayland->current_preedit.cursor_begin + != context_wayland->current_preedit.cursor_end) + { + /* FIXME: Oh noes, how to highlight while taking into account user preferences? */ + PangoAttribute *cursor = pango_attr_weight_new (PANGO_WEIGHT_BOLD); + cursor->start_index = context_wayland->current_preedit.cursor_begin; + cursor->end_index = context_wayland->current_preedit.cursor_end; + pango_attr_list_insert (*attrs, cursor); + } } } @@ -531,6 +637,7 @@ static void gtk_im_context_wayland_focus_in (GtkIMContext *context) { GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + gboolean result; if (global->current == context) return; @@ -538,9 +645,9 @@ gtk_im_context_wayland_focus_in (GtkIMContext *context) return; global->current = context; - enable_text_input (context_wayland, FALSE); + enable_text_input (context_wayland); + g_signal_emit_by_name (global->current, "retrieve-surrounding", &result); notify_content_type (context_wayland); - notify_surrounding_text (context_wayland); notify_cursor_location (context_wayland); commit_state (context_wayland); } @@ -548,17 +655,22 @@ gtk_im_context_wayland_focus_in (GtkIMContext *context) static void gtk_im_context_wayland_focus_out (GtkIMContext *context) { + GtkIMContextWayland *context_wayland; + if (global->current != context) return; - gtk_text_input_disable (global->text_input); + context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + + zwp_text_input_v3_disable (global->text_input); + commit_state (context_wayland); global->current = NULL; } static void gtk_im_context_wayland_reset (GtkIMContext *context) { - reset_preedit (GTK_IM_CONTEXT_WAYLAND (context)); + notify_external_change (GTK_IM_CONTEXT_WAYLAND (context)); GTK_IM_CONTEXT_CLASS (parent_class)->reset (context); } @@ -598,9 +710,15 @@ gtk_im_context_wayland_set_surrounding (GtkIMContext *context, g_free (context_wayland->surrounding.text); context_wayland->surrounding.text = g_strdup (text); context_wayland->surrounding.cursor_idx = cursor_index; + /* Anchor is not exposed via the set_surrounding interface, emulating. */ + context_wayland->surrounding.anchor_idx = cursor_index; notify_surrounding_text (context_wayland); - commit_state (context_wayland); + /* State changes coming from reset don't have any other opportunity to get + * committed. */ + if (context_wayland->surrounding_change != + ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD) + commit_state (context_wayland); } static gboolean diff --git a/modules/input/imwaylandgtk.c b/modules/input/imwaylandgtk.c new file mode 100644 index 0000000000..1d5050496a --- /dev/null +++ b/modules/input/imwaylandgtk.c @@ -0,0 +1,703 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2017 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include "gtk/gtkintl.h" +#include "gtk/gtkimmodule.h" + +#include "gdk/wayland/gdkwayland.h" +#include "gtk-text-input-client-protocol.h" + +typedef struct _GtkIMContextWaylandGlobal GtkIMContextWaylandGlobal; +typedef struct _GtkIMContextWayland GtkIMContextWayland; +typedef struct _GtkIMContextWaylandClass GtkIMContextWaylandClass; + +struct _GtkIMContextWaylandGlobal +{ + struct wl_display *display; + struct wl_registry *registry; + uint32_t text_input_manager_wl_id; + struct gtk_text_input_manager *text_input_manager; + struct gtk_text_input *text_input; + uint32_t enter_serial; + + GtkIMContext *current; +}; + +struct _GtkIMContextWaylandClass +{ + GtkIMContextSimpleClass parent_class; +}; + +struct _GtkIMContextWayland +{ + GtkIMContextSimple parent_instance; + GdkWindow *window; + GtkWidget *widget; + + GtkGesture *gesture; + gdouble press_x; + gdouble press_y; + + struct { + gchar *text; + gint cursor_idx; + } surrounding; + + struct { + gchar *text; + gint cursor_idx; + } preedit; + + cairo_rectangle_int_t cursor_rect; + guint use_preedit : 1; +}; + +GType type_wayland = 0; +static GObjectClass *parent_class; +static GtkIMContextWaylandGlobal *global = NULL; + +static const GtkIMContextInfo imwayland_info = +{ + "waylandgtk", /* ID */ + NC_("input method menu", "Waylandgtk"), /* Human readable name */ + GETTEXT_PACKAGE, /* Translation domain */ + GTK_LOCALEDIR, /* Dir for bindtextdomain (not strictly needed for "gtk+") */ + "", /* Languages for which this module is the default */ +}; + +static const GtkIMContextInfo *info_list[] = +{ + &imwayland_info, +}; + +#define GTK_IM_CONTEXT_WAYLAND(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), type_wayland, GtkIMContextWayland)) + +#ifndef INCLUDE_IM_wayland +#define MODULE_ENTRY(type,function) G_MODULE_EXPORT type im_module_ ## function +#else +#define MODULE_ENTRY(type, function) type _gtk_immodule_wayland_ ## function +#endif + +static void +reset_preedit (GtkIMContextWayland *context) +{ + if (context->preedit.text == NULL) + return; + + g_clear_pointer (&context->preedit.text, g_free); + context->preedit.cursor_idx = 0; + g_signal_emit_by_name (context, "preedit-changed"); +} + +static void +text_input_enter (void *data, + struct gtk_text_input *text_input, + uint32_t serial, + struct wl_surface *surface) +{ + GtkIMContextWaylandGlobal *global = data; + + global->enter_serial = serial; +} + +static void +text_input_leave (void *data, + struct gtk_text_input *text_input, + uint32_t serial, + struct wl_surface *surface) +{ + GtkIMContextWayland *context; + + if (!global->current) + return; + + context = GTK_IM_CONTEXT_WAYLAND (global->current); + reset_preedit (context); +} + +static void +text_input_preedit (void *data, + struct gtk_text_input *text_input, + const char *text, + guint cursor) +{ + GtkIMContextWayland *context; + gboolean state_change; + + if (!global->current) + return; + + context = GTK_IM_CONTEXT_WAYLAND (global->current); + if (!text && !context->preedit.text) + return; + + state_change = ((text == NULL) != (context->preedit.text == NULL)); + + if (state_change && !context->preedit.text) + g_signal_emit_by_name (context, "preedit-start"); + + g_free (context->preedit.text); + context->preedit.text = g_strdup (text); + context->preedit.cursor_idx = cursor; + + g_signal_emit_by_name (context, "preedit-changed"); + + if (state_change && !context->preedit.text) + g_signal_emit_by_name (context, "preedit-end"); +} + +static void +text_input_commit (void *data, + struct gtk_text_input *text_input, + const char *text) +{ + GtkIMContextWaylandGlobal *global = data; + + if (global->current && text) + g_signal_emit_by_name (global->current, "commit", text); +} + +static void +text_input_delete_surrounding_text (void *data, + struct gtk_text_input *text_input, + uint32_t offset, + uint32_t len) +{ + GtkIMContextWaylandGlobal *global = data; + + if (global->current) + g_signal_emit_by_name (global->current, "delete-surrounding", offset, len); +} + +static const struct gtk_text_input_listener text_input_listener = { + text_input_enter, + text_input_leave, + text_input_preedit, + text_input_commit, + text_input_delete_surrounding_text +}; + +static void +registry_handle_global (void *data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + GtkIMContextWaylandGlobal *global = data; + GdkSeat *seat = gdk_display_get_default_seat (gdk_display_get_default ()); + + if (strcmp (interface, "gtk_text_input_manager") == 0) + { + global->text_input_manager_wl_id = id; + global->text_input_manager = + wl_registry_bind (global->registry, global->text_input_manager_wl_id, + >k_text_input_manager_interface, 1); + global->text_input = + gtk_text_input_manager_get_text_input (global->text_input_manager, + gdk_wayland_seat_get_wl_seat (seat)); + gtk_text_input_add_listener (global->text_input, + &text_input_listener, global); + } +} + +static void +registry_handle_global_remove (void *data, + struct wl_registry *registry, + uint32_t id) +{ + GtkIMContextWaylandGlobal *global = data; + + if (id != global->text_input_manager_wl_id) + return; + + g_clear_pointer(&global->text_input, gtk_text_input_destroy); + g_clear_pointer(&global->text_input_manager, gtk_text_input_manager_destroy); +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static void +gtk_im_context_wayland_global_init (GdkDisplay *display) +{ + g_return_if_fail (global == NULL); + + global = g_new0 (GtkIMContextWaylandGlobal, 1); + global->display = gdk_wayland_display_get_wl_display (display); + global->registry = wl_display_get_registry (global->display); + + wl_registry_add_listener (global->registry, ®istry_listener, global); +} + +static void +notify_surrounding_text (GtkIMContextWayland *context) +{ + if (!global || !global->text_input) + return; + if (global->current != GTK_IM_CONTEXT (context)) + return; + if (!context->surrounding.text) + return; + + gtk_text_input_set_surrounding_text (global->text_input, + context->surrounding.text, + context->surrounding.cursor_idx, + context->surrounding.cursor_idx); +} + +static void +notify_cursor_location (GtkIMContextWayland *context) +{ + cairo_rectangle_int_t rect; + + if (!global || !global->text_input) + return; + if (global->current != GTK_IM_CONTEXT (context)) + return; + if (!context->window) + return; + + rect = context->cursor_rect; + gdk_window_get_root_coords (context->window, rect.x, rect.y, + &rect.x, &rect.y); + + gtk_text_input_set_cursor_rectangle (global->text_input, + rect.x, rect.y, + rect.width, rect.height); +} + +static uint32_t +translate_hints (GtkInputHints input_hints, + GtkInputPurpose purpose) +{ + uint32_t hints = 0; + + if (input_hints & GTK_INPUT_HINT_SPELLCHECK) + hints |= GTK_TEXT_INPUT_CONTENT_HINT_SPELLCHECK; + if (input_hints & GTK_INPUT_HINT_WORD_COMPLETION) + hints |= GTK_TEXT_INPUT_CONTENT_HINT_COMPLETION; + if (input_hints & GTK_INPUT_HINT_LOWERCASE) + hints |= GTK_TEXT_INPUT_CONTENT_HINT_LOWERCASE; + if (input_hints & GTK_INPUT_HINT_UPPERCASE_CHARS) + hints |= GTK_TEXT_INPUT_CONTENT_HINT_UPPERCASE; + if (input_hints & GTK_INPUT_HINT_UPPERCASE_WORDS) + hints |= GTK_TEXT_INPUT_CONTENT_HINT_TITLECASE; + if (input_hints & GTK_INPUT_HINT_UPPERCASE_SENTENCES) + hints |= GTK_TEXT_INPUT_CONTENT_HINT_AUTO_CAPITALIZATION; + + if (purpose == GTK_INPUT_PURPOSE_PIN || + purpose == GTK_INPUT_PURPOSE_PASSWORD) + { + hints |= (GTK_TEXT_INPUT_CONTENT_HINT_HIDDEN_TEXT | + GTK_TEXT_INPUT_CONTENT_HINT_SENSITIVE_DATA); + } + + return hints; +} + +static uint32_t +translate_purpose (GtkInputPurpose purpose) +{ + switch (purpose) + { + case GTK_INPUT_PURPOSE_FREE_FORM: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_NORMAL; + case GTK_INPUT_PURPOSE_ALPHA: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_ALPHA; + case GTK_INPUT_PURPOSE_DIGITS: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_DIGITS; + case GTK_INPUT_PURPOSE_NUMBER: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_NUMBER; + case GTK_INPUT_PURPOSE_PHONE: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_PHONE; + case GTK_INPUT_PURPOSE_URL: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_URL; + case GTK_INPUT_PURPOSE_EMAIL: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_EMAIL; + case GTK_INPUT_PURPOSE_NAME: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_NAME; + case GTK_INPUT_PURPOSE_PASSWORD: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_PASSWORD; + case GTK_INPUT_PURPOSE_PIN: + return GTK_TEXT_INPUT_CONTENT_PURPOSE_PIN; + } + + return GTK_TEXT_INPUT_CONTENT_PURPOSE_NORMAL; +} + +static void +notify_content_type (GtkIMContextWayland *context) +{ + GtkInputHints hints; + GtkInputPurpose purpose; + + if (global->current != GTK_IM_CONTEXT (context)) + return; + + g_object_get (context, + "input-hints", &hints, + "input-purpose", &purpose, + NULL); + + gtk_text_input_set_content_type (global->text_input, + translate_hints (hints, purpose), + translate_purpose (purpose)); +} + +static void +commit_state (GtkIMContextWayland *context) +{ + if (global->current != GTK_IM_CONTEXT (context)) + return; + gtk_text_input_commit (global->text_input); +} + +static void +enable_text_input (GtkIMContextWayland *context, + gboolean toggle_panel) +{ + guint flags = 0; + + if (context->use_preedit) + flags |= GTK_TEXT_INPUT_ENABLE_FLAGS_CAN_SHOW_PREEDIT; + if (toggle_panel) + flags |= GTK_TEXT_INPUT_ENABLE_FLAGS_TOGGLE_INPUT_PANEL; + + gtk_text_input_enable (global->text_input, + global->enter_serial, + flags); +} + +static void +gtk_im_context_wayland_finalize (GObject *object) +{ + GtkIMContextWayland *context = GTK_IM_CONTEXT_WAYLAND (object); + + g_clear_object (&context->window); + g_clear_object (&context->gesture); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +pressed_cb (GtkGestureMultiPress *gesture, + gint n_press, + gdouble x, + gdouble y, + GtkIMContextWayland *context) +{ + if (n_press == 1) + { + context->press_x = x; + context->press_y = y; + } +} + +static void +released_cb (GtkGestureMultiPress *gesture, + gint n_press, + gdouble x, + gdouble y, + GtkIMContextWayland *context) +{ + GtkInputHints hints; + + if (!global->current) + return; + + g_object_get (context, "input-hints", &hints, NULL); + + if (n_press == 1 && + (hints & GTK_INPUT_HINT_INHIBIT_OSK) == 0 && + !gtk_drag_check_threshold (context->widget, + context->press_x, + context->press_y, + x, y)) + { + enable_text_input (GTK_IM_CONTEXT_WAYLAND (context), TRUE); + } +} + +static void +gtk_im_context_wayland_set_client_window (GtkIMContext *context, + GdkWindow *window) +{ + GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + GtkWidget *widget = NULL; + + if (window == context_wayland->window) + return; + + if (window) + gdk_window_get_user_data (window, (gpointer*) &widget); + + if (context_wayland->widget && context_wayland->widget != widget) + g_clear_object (&context_wayland->gesture); + + g_set_object (&context_wayland->window, window); + + if (context_wayland->widget != widget) + { + context_wayland->widget = widget; + + if (widget) + { + GtkGesture *gesture; + + gesture = gtk_gesture_multi_press_new (widget); + gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), + GTK_PHASE_CAPTURE); + g_signal_connect (gesture, "pressed", + G_CALLBACK (pressed_cb), context); + g_signal_connect (gesture, "released", + G_CALLBACK (released_cb), context); + context_wayland->gesture = gesture; + } + } +} + +static void +gtk_im_context_wayland_get_preedit_string (GtkIMContext *context, + gchar **str, + PangoAttrList **attrs, + gint *cursor_pos) +{ + GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + gchar *preedit_str; + + if (attrs) + *attrs = NULL; + + GTK_IM_CONTEXT_CLASS (parent_class)->get_preedit_string (context, str, attrs, cursor_pos); + + /* If the parent implementation returns a len>0 string, go with it */ + if (str && *str) + { + if (**str) + return; + + g_free (*str); + } + + preedit_str = + context_wayland->preedit.text ? context_wayland->preedit.text : ""; + + if (str) + *str = g_strdup (preedit_str); + if (cursor_pos) + *cursor_pos = context_wayland->preedit.cursor_idx; + + if (attrs) + { + if (!*attrs) + *attrs = pango_attr_list_new (); + pango_attr_list_insert (*attrs, + pango_attr_underline_new (PANGO_UNDERLINE_SINGLE)); + } +} + +static gboolean +gtk_im_context_wayland_filter_keypress (GtkIMContext *context, + GdkEventKey *key) +{ + /* This is done by the compositor */ + return GTK_IM_CONTEXT_CLASS (parent_class)->filter_keypress (context, key); +} + +static void +gtk_im_context_wayland_focus_in (GtkIMContext *context) +{ + GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + + if (global->current == context) + return; + if (!global->text_input) + return; + + global->current = context; + enable_text_input (context_wayland, FALSE); + notify_content_type (context_wayland); + notify_surrounding_text (context_wayland); + notify_cursor_location (context_wayland); + commit_state (context_wayland); +} + +static void +gtk_im_context_wayland_focus_out (GtkIMContext *context) +{ + if (global->current != context) + return; + + gtk_text_input_disable (global->text_input); + global->current = NULL; +} + +static void +gtk_im_context_wayland_reset (GtkIMContext *context) +{ + reset_preedit (GTK_IM_CONTEXT_WAYLAND (context)); + + GTK_IM_CONTEXT_CLASS (parent_class)->reset (context); +} + +static void +gtk_im_context_wayland_set_cursor_location (GtkIMContext *context, + GdkRectangle *rect) +{ + GtkIMContextWayland *context_wayland; + + context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + + context_wayland->cursor_rect = *rect; + notify_cursor_location (context_wayland); + commit_state (context_wayland); +} + +static void +gtk_im_context_wayland_set_use_preedit (GtkIMContext *context, + gboolean use_preedit) +{ + GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + + context_wayland->use_preedit = !!use_preedit; +} + +static void +gtk_im_context_wayland_set_surrounding (GtkIMContext *context, + const gchar *text, + gint len, + gint cursor_index) +{ + GtkIMContextWayland *context_wayland; + + context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + + g_free (context_wayland->surrounding.text); + context_wayland->surrounding.text = g_strdup (text); + context_wayland->surrounding.cursor_idx = cursor_index; + + notify_surrounding_text (context_wayland); + commit_state (context_wayland); +} + +static gboolean +gtk_im_context_wayland_get_surrounding (GtkIMContext *context, + gchar **text, + gint *cursor_index) +{ + GtkIMContextWayland *context_wayland; + + context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + + if (!context_wayland->surrounding.text) + return FALSE; + + *text = context_wayland->surrounding.text; + *cursor_index = context_wayland->surrounding.cursor_idx; + return TRUE; +} + +static void +gtk_im_context_wayland_class_init (GtkIMContextWaylandClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (klass); + + object_class->finalize = gtk_im_context_wayland_finalize; + + im_context_class->set_client_window = gtk_im_context_wayland_set_client_window; + im_context_class->get_preedit_string = gtk_im_context_wayland_get_preedit_string; + im_context_class->filter_keypress = gtk_im_context_wayland_filter_keypress; + im_context_class->focus_in = gtk_im_context_wayland_focus_in; + im_context_class->focus_out = gtk_im_context_wayland_focus_out; + im_context_class->reset = gtk_im_context_wayland_reset; + im_context_class->set_cursor_location = gtk_im_context_wayland_set_cursor_location; + im_context_class->set_use_preedit = gtk_im_context_wayland_set_use_preedit; + im_context_class->set_surrounding = gtk_im_context_wayland_set_surrounding; + im_context_class->get_surrounding = gtk_im_context_wayland_get_surrounding; + + parent_class = g_type_class_peek_parent (klass); +} + +static void +on_content_type_changed (GtkIMContextWayland *context) +{ + notify_content_type (context); + commit_state (context); +} + +static void +gtk_im_context_wayland_init (GtkIMContextWayland *context) +{ + context->use_preedit = TRUE; + g_signal_connect_swapped (context, "notify::input-purpose", + G_CALLBACK (on_content_type_changed), context); + g_signal_connect_swapped (context, "notify::input-hints", + G_CALLBACK (on_content_type_changed), context); +} + +static void +gtk_im_context_wayland_register_type (GTypeModule *module) +{ + const GTypeInfo object_info = { + sizeof (GtkIMContextWaylandClass), + NULL, NULL, + (GClassInitFunc) gtk_im_context_wayland_class_init, + NULL, NULL, + sizeof (GtkIMContextWayland), + 0, + (GInstanceInitFunc) gtk_im_context_wayland_init, + }; + + type_wayland = g_type_module_register_type (module, + GTK_TYPE_IM_CONTEXT_SIMPLE, + "GtkIMContextWayland", + &object_info, 0); +} + +MODULE_ENTRY (void, init) (GTypeModule * module) +{ + gtk_im_context_wayland_register_type (module); + gtk_im_context_wayland_global_init (gdk_display_get_default ()); +} + +MODULE_ENTRY (void, exit) (void) +{ +} + +MODULE_ENTRY (void, list) (const GtkIMContextInfo *** contexts, int *n_contexts) +{ + *contexts = info_list; + *n_contexts = G_N_ELEMENTS (info_list); +} + +MODULE_ENTRY (GtkIMContext *, create) (const gchar * context_id) +{ + if (strcmp (context_id, "waylandgtk") == 0) + return g_object_new (type_wayland, NULL); + else + return NULL; +} diff --git a/modules/input/text-input-unstable-v3.xml b/modules/input/text-input-unstable-v3.xml new file mode 100644 index 0000000000..8b710fd68b --- /dev/null +++ b/modules/input/text-input-unstable-v3.xml @@ -0,0 +1,441 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows compositors to act as input methods and to send text + to applications. A text input object is used to manage state of what are + typically text entry fields in the application. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + The zwp_text_input_v3 interface represents text input and input methods + associated with a seat. It provides enter/leave events to follow the + text input focus for a seat. + + Requests are used to enable/disable the text-input object and set + state information like surrounding and selected text or the content type. + The information about the entered text is sent to the text-input object + via the preedit_string and commit_string events. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + must not point to middle bytes inside a code point: they must either + point to the first byte of a code point or to the end of the buffer. + Lengths must be measured between two valid indices. + + Focus moving throughout surfaces will result in the emission of + zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused + surface must commit zwp_text_input_v3.enable and + zwp_text_input_v3.disable requests as the keyboard focus moves across + editable and non-editable elements of the UI. Those two requests are not + expected to be paired with each other, the compositor must be able to + handle consecutive series of the same request. + + State is sent by the state requests (set_surrounding_text, + set_content_type and set_cursor_rectangle) and a commit request. After an + enter event or disable request all state information is invalidated and + needs to be resent by the client. + + + + + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object. + + + + + + Requests text input on the surface previously obtained from the enter + event. + + This request must be issued every time the active text input changes + to a new one, including within the current surface. Use + zwp_text_input_v3.disable when there is no longer any input focus on + the current surface. + + This request resets all state associated with previous enable, disable, + set_surrounding_text, set_text_change_cause, set_content_type, and + set_cursor_rectangle requests, as well as the state associated with + preedit_string, commit_string, and delete_surrounding_text events. + + The set_surrounding_text, set_content_type and set_cursor_rectangle + requests must follow if the text input supports the necessary + functionality. + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The changes must be applied by the compositor after issuing a + zwp_text_input_v3.commit request. + + + + + + Explicitly disable text input on the current surface (typically when + there is no focus on any text entry inside the surface). + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request. + + + + + + Sets the surrounding plain text around the input, excluding the preedit + text. + + The client should notify the compositor of any changes in any of the + values carried with this request, including changes caused by handling + incoming text-input events as well as changes caused by other + mechanisms like keyboard typing. + + If the client is unaware of the text around the cursor, it should not + issue this request, to signify lack of support to the compositor. + + Text is UTF-8 encoded, and should include the cursor position, the + complete selection and additional characters before and after them. + There is a maximum length of wayland messages, so text can not be + longer than 4000 bytes. + + Cursor is the byte offset of the cursor within text buffer. + + Anchor is the byte offset of the selection anchor within text buffer. + If there is no selected text, anchor is the same as cursor. + + If any preedit text is present, it is replaced with a cursor for the + purpose of this event. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + + + + + + + + + Reason for the change of surrounding text or cursor posision. + + + + + + + + Tells the compositor why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor posision, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this request is double-buffered. It must be applied + and reset to initial at the next zwp_text_input_v3.commit request. + + The initial value of cause is input_method. + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + The content purpose allows to specify the primary purpose of a text + input. + + This allows an input method to show special purpose input panels with + extra characters or to disallow some characters. + + + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some of + the behavior. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request. + Subsequent attempts to update them may have no effect. The values + remain valid until the next committed enable or disable request. + + The initial value for hint is none, and the initial value for purpose + is normal. + + + + + + + + Marks an area around the cursor as a x, y, width, height rectangle in + surface local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor, without obstructing the text being input. + + If the client is unaware of the position of edited text, it should not + issue this request, to signify lack of support to the compositor. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial values describing a cursor rectangle are empty. That means + the text input does not support describing the cursor area. If the + empty values get applied, subsequent attempts to change them may have + no effect. + + + + + + + + + + Atomically applies state changes recently sent to the compositor. + + The commit request establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (enabled status, content purpose, content hint, + surrounding text and change cause, cursor rectangle) is conceptually + double-buffered within the context of a text input, i.e. between a + committed enable request and the following committed enable or disable + request. + + Protocol requests modify the pending state, as opposed to the current + state in use by the input method. A commit request atomically applies + all pending state, replacing the current state. After commit, the new + pending state is as documented for each related request. + + Requests are applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + The compositor must count the number of commit requests coming from + each zwp_text_input_v3 object and use the count as the serial in done + events. + + + + + + Notification that this seat's text-input focus is on a certain surface. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. This event sets the current surface for the + text-input object. + + + + + + + Notification that this seat's text-input focus is no longer on a + certain surface. The client should reset any preedit string previously + set. + + The leave notification clears the current surface. It is sent before + the enter notification for the new focus. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + Notify when a new composing text (pre-edit) should be set at the + current cursor position. Any previously set composing text must be + removed. Any previously existing selected text must be removed. + + The argument text contains the pre-edit string buffer. + + The parameters cursor_begin and cursor_end are counted in bytes + relative to the beginning of the submitted text buffer. Cursor should + be hidden when both are equal to -1. + + They could be represented by the client as a line if both values are + the same, or as a text highlight otherwise. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string, and cursor_begin, + cursor_end and cursor_hidden are all 0. + + + + + + + + + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string. + + + + + + + Notify when the text around the current cursor position should be + deleted. + + Before_length and after_length are the number of bytes before and after + the current cursor index (excluding the selection) to delete. + + If a preedit text is present, in effect before_length is counted from + the beginning of it, and after_length from its end (see done event + sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial values of both before_length and after_length are 0. + + + + + + + + Instruct the application to apply changes to state requested by the + preedit_string, commit_string and delete_surrounding_text events. The + state relating to these events is double-buffered, and each one + modifies the pending state. This event replaces the current state with + the pending state. + + The application must proceed by evaluating the changes in the following + order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_text_input_v3 + object known to the compositor. The value of the serial argument must + be equal to the number of commit requests already issued on that object. + When the client receives a done event with a serial different than the + number of past commit requests, it must proceed as normal, except it + should not change the current state of the zwp_text_input_v3 object. + + + + + + + + A factory for text-input objects. This object is a global singleton. + + + + + Destroy the wp_text_input_manager object. + + + + + + Creates a new text-input object for a given seat. + + + + + + diff --git a/po-properties/POTFILES.in b/po-properties/POTFILES.in index c058227122..780733bf36 100644 --- a/po-properties/POTFILES.in +++ b/po-properties/POTFILES.in @@ -345,6 +345,7 @@ modules/input/imti-er.c modules/input/imti-et.c modules/input/imviqr.c modules/input/imwayland.c +modules/input/imwaylandgtk.c modules/input/imxim.c modules/printbackends/cloudprint/gtkprintbackendcloudprint.c modules/printbackends/cloudprint/gtkprintercloudprint.c