diff options
author | Matthias Clasen <mclasen@redhat.com> | 2018-02-15 03:01:43 +0000 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2018-02-15 03:01:43 +0000 |
commit | c2531b7ff2069d34f34025b17247389d7838cbb7 (patch) | |
tree | 24eb71d878095add6ad04571c23324b351965ad8 /modules | |
parent | 753138a7cc3814ee811eb0dd28e3b3c61cde063c (diff) | |
parent | 21e9fe4f55a611945f17eef6a0c0fd681a511ea3 (diff) | |
download | gtk+-c2531b7ff2069d34f34025b17247389d7838cbb7.tar.gz |
Merge branch 'wip/carlosg/imwayland' into 'master'
modules: Add wayland IM implementation
See merge request GNOME/gtk!4
Diffstat (limited to 'modules')
-rw-r--r-- | modules/input/gtk-text-input.xml | 302 | ||||
-rw-r--r-- | modules/input/imwayland.c | 678 | ||||
-rw-r--r-- | modules/input/meson.build | 49 |
3 files changed, 1029 insertions, 0 deletions
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 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<protocol name="gtk_text_input"> + <copyright> + 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. + </copyright> + + <interface name="gtk_text_input" version="1"> + <description summary="text input"> + 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. + </description> + + <request name="destroy" type="destructor"> + <description summary="Destroy the wp_text_input"> + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object + </description> + </request> + + <enum name="enable_flags" bitfield="true"> + <description summary="enable flags"> + Content hint is a bitmask to allow to modify the behavior of the text + input. + </description> + <entry name="none" value="0x0" summary="no special behaviour"/> + <entry name="can_show_preedit" value="0x1" summary="hints that the UI is capable of showing pre-edit text"/> + <entry name="toggle_input_panel" value="0x2" summary="requests toggling input panel (eg. on-screen keyboard)"/> + </enum> + + <request name="enable"> + <description summary="Request text input to be enabled"> + Requests text input on a surface. The serial provided must be the one + received on gtk_text_input.enter. + </description> + <arg name="serial" type="uint" summary="serial of enter event"/> + <arg name="show_input_panel" type="uint" summary="details of the enable request"/> + </request> + + <request name="disable"> + <description summary="Disable text input on a surface"> + Explicitly disable text input in a surface (typically when there is no + focus on any text entry inside the surface). + </description> + </request> + + <request name="set_surrounding_text"> + <description summary="sets the surrounding text"> + 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. + </description> + <arg name="text" type="string"/> + <arg name="cursor" type="int"/> + <arg name="anchor" type="int"/> + </request> + + <enum name="content_hint" bitfield="true"> + <description summary="content hint"> + Content hint is a bitmask to allow to modify the behavior of the text + input. + </description> + <entry name="none" value="0x0" summary="no special behaviour"/> + <entry name="completion" value="0x1" summary="suggest word completions"/> + <entry name="spellcheck" value="0x2" summary="suggest word corrections"/> + <entry name="auto_capitalization" value="0x4" summary="switch to uppercase letters at the start of a sentence"/> + <entry name="lowercase" value="0x8" summary="prefer lowercase letters"/> + <entry name="uppercase" value="0x10" summary="prefer uppercase letters"/> + <entry name="titlecase" value="0x20" summary="prefer casing for titles and headings (can be language dependent)"/> + <entry name="hidden_text" value="0x40" summary="characters should be hidden"/> + <entry name="sensitive_data" value="0x80" summary="typed text should not be stored"/> + <entry name="latin" value="0x100" summary="just latin characters should be entered"/> + <entry name="multiline" value="0x200" summary="the text input is multiline"/> + </enum> + + <enum name="content_purpose"> + <description summary="content purpose"> + 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. + </description> + <entry name="normal" value="0" summary="default input, allowing all characters"/> + <entry name="alpha" value="1" summary="allow only alphabetic characters"/> + <entry name="digits" value="2" summary="allow only digits"/> + <entry name="number" value="3" summary="input a number (including decimal separator and sign)"/> + <entry name="phone" value="4" summary="input a phone number"/> + <entry name="url" value="5" summary="input an URL"/> + <entry name="email" value="6" summary="input an email address"/> + <entry name="name" value="7" summary="input a name of a person"/> + <entry name="password" value="8" summary="input a password (combine with password or sensitive_data hint)"/> + <entry name="pin" value="9" summary="input is a numeric password (combine with password or sensitive_data hint)"/> + <entry name="date" value="10" summary="input a date"/> + <entry name="time" value="11" summary="input a time"/> + <entry name="datetime" value="12" summary="input a date and time"/> + <entry name="terminal" value="13" summary="input for a terminal"/> + </enum> + + <request name="set_content_type"> + <description summary="set content purpose and hint"> + 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. + </description> + <arg name="hint" type="uint" enum="content_hint"/> + <arg name="purpose" type="uint" enum="content_purpose"/> + </request> + + <request name="set_cursor_rectangle"> + <description summary="set cursor position"> + 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. + </description> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="commit"> + <description summary="commit state"> + 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. + </description> + </request> + + <event name="enter"> + <description summary="enter event"> + 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. + </description> + <arg name="serial" type="uint" summary="serial"/> + <arg name="surface" type="object" interface="wl_surface"/> + </event> + + <event name="leave"> + <description summary="leave event"> + 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. + </description> + <arg name="serial" type="uint"/> + <arg name="surface" type="object" interface="wl_surface"/> + </event> + + <event name="preedit_string"> + <description summary="pre-edit"> + Notify when a new composing text (pre-edit) should be set around the + current cursor position. Any previously set composing text should + be removed. + </description> + <arg name="text" type="string" allow-null="true"/> + <arg name="cursor" type="uint"/> + </event> + + <event name="commit_string"> + <description summary="text commit"> + 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. + </description> + <arg name="text" type="string" allow-null="true"/> + </event> + + <event name="delete_surrounding_text"> + <description summary="delete surrounding text"> + 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. + </description> + <arg name="before_length" type="uint" summary="length of text before current cursor position"/> + <arg name="after_length" type="uint" summary="length of text after current cursor position"/> + </event> + </interface> + + <interface name="gtk_text_input_manager" version="1"> + <description summary="text input manager"> + A factory for text-input objects. This object is a global singleton. + </description> + + <request name="destroy" type="destructor"> + <description summary="Destroy the wp_text_input_manager"> + Destroy the wp_text_input_manager object. + </description> + </request> + + <request name="get_text_input"> + <description summary="create a new text input object"> + Creates a new text-input object for a given seat. + </description> + <arg name="id" type="new_id" interface="gtk_text_input"/> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + </interface> +</protocol> 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 <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <wayland-client-protocol.h> + +#include <gtk/gtk.h> +#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 e60dbcc59e..176830b323 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 @@ -59,6 +60,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')], @@ -75,6 +123,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 = [] |