diff --git a/gtk/gtkimmodule.c b/gtk/gtkimmodule.c index f3f25409bc..a1099e2dfa 100644 --- a/gtk/gtkimmodule.c +++ b/gtk/gtkimmodule.c @@ -460,6 +460,9 @@ gtk_im_module_initialize (void) #ifdef INCLUDE_IM_broadway do_builtin (broadway); #endif +#ifdef INCLUDE_IM_wayland + do_builtin (wayland); +#endif #undef do_builtin diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c index 6deb1f05cb..c3f52dcb52 100644 --- a/gtk/gtksettings.c +++ b/gtk/gtksettings.c @@ -1172,6 +1172,31 @@ gtk_settings_create_for_display (GdkDisplay *display) "gtk-im-module", "broadway", NULL); else +#endif +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY (display)) + { + const gchar *immodule = NULL; + + if (gdk_wayland_display_query_registry (display, + "gtk_text_input_manager")) + { + settings = g_object_new (GTK_TYPE_SETTINGS, + "gtk-im-module", "wayland", + NULL); + } + else + { + /* Fallback to other IM methods if the compositor does not + * implement the interface(s). + */ + settings = g_object_new (GTK_TYPE_SETTINGS, NULL); + } + + immodule = "wayland"; + + } + else #endif settings = g_object_new (GTK_TYPE_SETTINGS, NULL); diff --git a/meson.build b/meson.build index 8f5ad42f97..3dc05e6cde 100644 --- a/meson.build +++ b/meson.build @@ -422,6 +422,7 @@ if wayland_enabled wlprotocolsdep = dependency('wayland-protocols', version: wayland_proto_req) wlcursordep = dependency('wayland-cursor', version: wayland_req) wlegldep = dependency('wayland-egl') + backend_immodules += ['wayland'] wayland_pkgs = [ 'wayland-client', wayland_req, diff --git a/modules/input/gtk-text-input.xml b/modules/input/gtk-text-input.xml new file mode 100644 index 0000000000..a134a19f61 --- /dev/null +++ b/modules/input/gtk-text-input.xml @@ -0,0 +1,302 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + + 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. + + + + + The gtk_text_input 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 pre-edit and commit_string events. Using this interface removes + the need for applications to directly process hardware key events and + compose text out of them. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + have to always point to the first byte of an UTF-8 encoded code point. + Lengths are not allowed to contain just a part of an UTF-8 encoded code + point. + + Focus moving throughout surfaces will result in the emission of + gtk_text_input.enter and gtk_text_input.leave events. The focused + surface must perform gtk_text_input.enable and + gtk_text_input.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. + + This protocol defines requests and events necessary for regular clients + to communicate with an input method. The gtk_input_method protocol + defines the interfaces necessary to implement standalone input methods. + If a compositor implements both interfaces, it will be the arbiter of the + communication between both. + + 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. + + + + + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + Requests text input on a surface. The serial provided must be the one + received on gtk_text_input.enter. + + + + + + + + Explicitly disable text input in a surface (typically when there is no + focus on any text entry inside the surface). + + + + + + Sets the plain surrounding text around the input position. Text is + UTF-8 encoded. Cursor is the byte offset within the surrounding text. + Anchor is the byte offset of the selection anchor within the + surrounding text. If there is no selected text, anchor is the same as + cursor. + + Make sure to always send some text before and after the cursor + except when the cursor is at the beginning or end of text. + + When there was a configure_surrounding_text event take the + before_cursor and after_cursor arguments into account for picking how + much surrounding text to send. + + There is a maximum length of wayland messages so text can not be + longer than 4000 bytes. + + + + + + + + + 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. + + When no content type is explicitly set, a normal content purpose with + none hint should be assumed. + + + + + + + + Sets the cursor outline as a x, y, width, height rectangle in surface + local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor. + + + + + + + + + + Allows to atomically send state updates from client. The previous + set_surrounding_text, set_content_type and set_cursor_rectangle + become effective after this call. + + Serial should be set to the serial from the last wp_text_input.enter + event. + + To make sure to not receive outdated input method events after a + state update, wl_display_sync() should be called after making this + request. + + + + + + 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. + + + + + + + + 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 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 around the + current cursor position. Any previously set composing text should + be removed. + + + + + + + + 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). + + The text argument could be also null if some text is removed (see + gtk_text_input.delete_surrounding_text). + + Any previously set composing text should be removed. + + + + + + + Notify when the text around the current cursor position should be + deleted. Before_length and after_length is the length (in bytes) of text + before and after the current cursor position (excluding the selection) + to delete. + + This event should be handled as part of a following commit_string or + preedit_string event. + + + + + + + + + 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/modules/input/imwayland.c b/modules/input/imwayland.c new file mode 100644 index 0000000000..9395380339 --- /dev/null +++ b/modules/input/imwayland.c @@ -0,0 +1,678 @@ +/* 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; + 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; + 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 = +{ + "wayland", /* ID */ + NC_("input method menu", "Wayland"), /* 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) +{ + 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_registry_bind (global->registry, 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; + + gtk_text_input_destroy (global->text_input); + global->text_input = NULL; + + gtk_text_input_manager_destroy (global->text_input_manager); + global->text_input_manager = NULL; +} + +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->widget) + return; + + rect = context->cursor_rect; + gtk_widget_translate_coordinates (context->widget, + gtk_widget_get_toplevel (context->widget), + 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->widget); + 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; + + 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_widget (GtkIMContext *context, + GtkWidget *widget) +{ + GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context); + + if (widget == context_wayland->widget) + return; + + if (context_wayland->widget && context_wayland->widget != widget) + g_clear_object (&context_wayland->gesture); + + g_set_object (&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; + + 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 && **str) + return; + + 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) + { + *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_widget = gtk_im_context_wayland_set_client_widget; + 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, "wayland") == 0) + return g_object_new (type_wayland, NULL); + else + return NULL; +} diff --git a/modules/input/meson.build b/modules/input/meson.build index 0111790c73..bd9f56323f 100644 --- a/modules/input/meson.build +++ b/modules/input/meson.build @@ -27,6 +27,7 @@ all_immodules = [ 'ti-er', 'ti-et', 'viqr', + 'wayland', ] all_immodules += backend_immodules @@ -65,6 +66,53 @@ mp_cargs = [ install_data('im-multipress.conf', install_dir : mp_confdir) +# Format: +# - protocol name +# - protocol stability ('stable' or 'unstable') +# - protocol version (if stability is 'unstable') +proto_sources = [ + ['gtk-text-input', 'stable', ], +] + +im_wayland_gen_headers = [] +im_wayland_sources = files('imwayland.c') +wayland_scanner = find_program('wayland-scanner') +genprotocols = find_program('../../gdk/wayland/genprotocolfiles.py') + +foreach p: proto_sources + proto_name = p.get(0) + proto_stability = p.get(1) + + if proto_stability == 'stable' + output_base = proto_name + input = '@0@.xml'.format(proto_name) + else + proto_version = p.get(2) + output_base = '@0@-@1@-@2@'.format(proto_name, proto_stability, proto_version) + input = join_paths(proto_dir, '@0@/@1@/@2@.xml'.format(proto_stability, proto_name, output_base)) + endif + + im_wayland_sources += custom_target('@0@ client header'.format(output_base), + input: input, + output: '@0@-client-protocol.h'.format(output_base), + command: [ + genprotocols, + wayland_scanner, + '@INPUT@', '@OUTPUT@', + 'client-header', + ]) + + im_wayland_sources += custom_target('@0@ source'.format(output_base), + input: input, + output: '@0@-protocol.c'.format(output_base), + command: [ + genprotocols, + wayland_scanner, + '@INPUT@', '@OUTPUT@', + 'code', + ]) +endforeach + method_defs = [ ['am-et', files('imam-et.c')], ['cedilla', files('imcedilla.c')], @@ -81,6 +129,7 @@ method_defs = [ ['quartz', ('imquartz.c'), [], ('-xobjective-c')], ['xim', files('gtkimcontextxim.c', 'imxim.c')], ['ime', files('gtkimcontextime.c', 'imime.c'), ['imm32']], + ['wayland', im_wayland_sources], ] inc_im_method_defs = []