1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2017 Red Hat, Inc.
3  * Copyright (C) 2018 Purism SPC
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include <string.h>
22 #include <wayland-client-protocol.h>
23 
24 #include <gtk/gtk.h>
25 #include "gtk/gtkintl.h"
26 #include "gtk/gtkimmodule.h"
27 
28 #include "gdk/wayland/gdkwayland.h"
29 #include "text-input-unstable-v3-client-protocol.h"
30 
31 typedef struct _GtkIMContextWaylandGlobal GtkIMContextWaylandGlobal;
32 typedef struct _GtkIMContextWayland GtkIMContextWayland;
33 typedef struct _GtkIMContextWaylandClass GtkIMContextWaylandClass;
34 
35 struct _GtkIMContextWaylandGlobal
36 {
37   struct wl_display *display;
38   struct wl_registry *registry;
39   uint32_t text_input_manager_wl_id;
40   struct zwp_text_input_manager_v3 *text_input_manager;
41   struct zwp_text_input_v3 *text_input;
42 
43   GtkIMContext *current;
44 
45   /* The input-method.enter event may happen before or after GTK focus-in,
46    * so the context may not exist at the time. Same for leave and focus-out. */
47   gboolean focused;
48 
49   guint serial;
50 };
51 
52 struct _GtkIMContextWaylandClass
53 {
54   GtkIMContextSimpleClass parent_class;
55 };
56 
57 struct preedit {
58   gchar *text;
59   gint cursor_begin;
60   gint cursor_end;
61 };
62 
63 struct surrounding_delete {
64   guint before_length;
65   guint after_length;
66 };
67 
68 struct _GtkIMContextWayland
69 {
70   GtkIMContextSimple parent_instance;
71   GdkWindow *window;
72   GtkWidget *widget;
73 
74   GtkGesture *gesture;
75   gdouble press_x;
76   gdouble press_y;
77 
78   struct {
79     gchar *text;
80     gint cursor_idx;
81     gint anchor_idx;
82   } surrounding;
83 
84   enum zwp_text_input_v3_change_cause surrounding_change;
85 
86   struct surrounding_delete pending_surrounding_delete;
87 
88   struct preedit current_preedit;
89   struct preedit pending_preedit;
90 
91   gchar *pending_commit;
92 
93   cairo_rectangle_int_t cursor_rect;
94   guint use_preedit : 1;
95   guint enabled : 1;
96 };
97 
98 GType type_wayland = 0;
99 static GObjectClass *parent_class;
100 static GtkIMContextWaylandGlobal *global = NULL;
101 
102 static const GtkIMContextInfo imwayland_info =
103 {
104   "wayland",      /* ID */
105   NC_("input method menu", "Wayland"),      /* Human readable name */
106   GETTEXT_PACKAGE, /* Translation domain */
107   GTK_LOCALEDIR,   /* Dir for bindtextdomain (not strictly needed for "gtk+") */
108   "",              /* Languages for which this module is the default */
109 };
110 
111 static const GtkIMContextInfo *info_list[] =
112 {
113   &imwayland_info,
114 };
115 
116 static void gtk_im_context_wayland_focus_out (GtkIMContext *context);
117 
118 #define GTK_IM_CONTEXT_WAYLAND(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), type_wayland, GtkIMContextWayland))
119 
120 #ifndef INCLUDE_IM_wayland
121 #define MODULE_ENTRY(type,function) G_MODULE_EXPORT type im_module_ ## function
122 #else
123 #define MODULE_ENTRY(type, function) type _gtk_immodule_wayland_ ## function
124 #endif
125 
126 static void
notify_external_change(GtkIMContextWayland * context)127 notify_external_change (GtkIMContextWayland *context)
128 {
129   gboolean result;
130 
131   if (!global->current)
132     return;
133 
134   if (!context->enabled)
135     return;
136 
137   context->surrounding_change = ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER;
138   g_signal_emit_by_name (global->current, "retrieve-surrounding", &result);
139 }
140 
141 static void
text_input_preedit(void * data,struct zwp_text_input_v3 * text_input,const char * text,gint cursor_begin,gint cursor_end)142 text_input_preedit (void                     *data,
143                     struct zwp_text_input_v3 *text_input,
144                     const char               *text,
145                     gint                      cursor_begin,
146                     gint                      cursor_end)
147 {
148   GtkIMContextWayland *context;
149   GtkIMContextWaylandGlobal *global = data;
150 
151   if (!global->current)
152     return;
153 
154   context = GTK_IM_CONTEXT_WAYLAND (global->current);
155 
156   g_free (context->pending_preedit.text);
157   context->pending_preedit.text = g_strdup (text);
158   context->pending_preedit.cursor_begin = cursor_begin;
159   context->pending_preedit.cursor_end = cursor_end;
160 }
161 
162 static void
text_input_preedit_apply(GtkIMContextWaylandGlobal * global)163 text_input_preedit_apply (GtkIMContextWaylandGlobal *global)
164 {
165   GtkIMContextWayland *context;
166   gboolean state_change;
167   struct preedit defaults = {0};
168 
169   if (!global->current)
170     return;
171 
172   context = GTK_IM_CONTEXT_WAYLAND (global->current);
173 
174   state_change = ((context->pending_preedit.text == NULL)
175                  != (context->current_preedit.text == NULL));
176 
177   if (state_change && !context->current_preedit.text)
178     g_signal_emit_by_name (context, "preedit-start");
179 
180   g_free (context->current_preedit.text);
181   context->current_preedit = context->pending_preedit;
182   context->pending_preedit = defaults;
183 
184   g_signal_emit_by_name (context, "preedit-changed");
185 
186   if (state_change && !context->current_preedit.text)
187     g_signal_emit_by_name (context, "preedit-end");
188 }
189 
190 static void
text_input_commit(void * data,struct zwp_text_input_v3 * text_input,const char * text)191 text_input_commit (void                     *data,
192                    struct zwp_text_input_v3 *text_input,
193                    const char               *text)
194 {
195   GtkIMContextWaylandGlobal *global = data;
196   GtkIMContextWayland *context;
197 
198   if (!global->current)
199       return;
200 
201   context = GTK_IM_CONTEXT_WAYLAND (global->current);
202 
203   g_free (context->pending_commit);
204   context->pending_commit = g_strdup (text);
205 }
206 
207 static void
text_input_commit_apply(GtkIMContextWaylandGlobal * global,gboolean valid)208 text_input_commit_apply (GtkIMContextWaylandGlobal *global, gboolean valid)
209 {
210   GtkIMContextWayland *context;
211   context = GTK_IM_CONTEXT_WAYLAND (global->current);
212   if (context->pending_commit && valid)
213     g_signal_emit_by_name (global->current, "commit", context->pending_commit);
214   g_free (context->pending_commit);
215   context->pending_commit = NULL;
216 }
217 
218 static void
text_input_delete_surrounding_text(void * data,struct zwp_text_input_v3 * text_input,uint32_t before_length,uint32_t after_length)219 text_input_delete_surrounding_text (void                     *data,
220                                     struct zwp_text_input_v3 *text_input,
221                                     uint32_t                  before_length,
222                                     uint32_t                  after_length)
223 {
224   GtkIMContextWaylandGlobal *global = data;
225   GtkIMContextWayland *context;
226 
227   if (!global->current)
228       return;
229 
230   context = GTK_IM_CONTEXT_WAYLAND (global->current);
231 
232   context->pending_surrounding_delete.before_length = before_length;
233   context->pending_surrounding_delete.after_length = after_length;
234 }
235 
236 static void
text_input_delete_surrounding_text_apply(GtkIMContextWaylandGlobal * global,gboolean valid)237 text_input_delete_surrounding_text_apply (GtkIMContextWaylandGlobal *global,
238   gboolean valid)
239 {
240   GtkIMContextWayland *context;
241   gboolean retval;
242   gint len;
243   struct surrounding_delete defaults = {0};
244 
245   context = GTK_IM_CONTEXT_WAYLAND (global->current);
246 
247   len = context->pending_surrounding_delete.after_length
248       + context->pending_surrounding_delete.before_length;
249   if (len > 0 && valid)
250     g_signal_emit_by_name (global->current, "delete-surrounding",
251                            -context->pending_surrounding_delete.before_length,
252                            len, &retval);
253   context->pending_surrounding_delete = defaults;
254 }
255 
256 static void
text_input_done(void * data,struct zwp_text_input_v3 * text_input,uint32_t serial)257 text_input_done (void                     *data,
258                  struct zwp_text_input_v3 *text_input,
259                  uint32_t                  serial)
260 {
261   GtkIMContextWaylandGlobal *global = data;
262   gboolean result;
263   gboolean valid;
264 
265   if (!global->current)
266     return;
267 
268   valid = serial == global->serial;
269   text_input_delete_surrounding_text_apply(global, valid);
270   text_input_commit_apply(global, valid);
271   g_signal_emit_by_name (global->current, "retrieve-surrounding", &result);
272   text_input_preedit_apply(global);
273 }
274 
275 static void
notify_surrounding_text(GtkIMContextWayland * context)276 notify_surrounding_text (GtkIMContextWayland *context)
277 {
278 #define MAX_LEN 4000
279   const gchar *start, *end;
280   int len, cursor, anchor;
281   char *str = NULL;
282 
283   if (!global || !global->text_input)
284     return;
285   if (global->current != GTK_IM_CONTEXT (context))
286     return;
287   if (!context->enabled || !context->surrounding.text)
288     return;
289 
290   len = strlen (context->surrounding.text);
291   cursor = context->surrounding.cursor_idx;
292   anchor = context->surrounding.anchor_idx;
293 
294   /* The protocol specifies a maximum length of 4KiB on transfers,
295    * mangle the surrounding text if it's bigger than that, and relocate
296    * cursor/anchor locations as per the string being sent.
297    */
298   if (len > MAX_LEN)
299     {
300       if (context->surrounding.cursor_idx < MAX_LEN &&
301           context->surrounding.anchor_idx < MAX_LEN)
302         {
303           start = context->surrounding.text;
304           end = &context->surrounding.text[MAX_LEN];
305         }
306       else if (context->surrounding.cursor_idx > len - MAX_LEN &&
307                context->surrounding.anchor_idx > len - MAX_LEN)
308         {
309           start = &context->surrounding.text[len - MAX_LEN];
310           end = &context->surrounding.text[len];
311         }
312       else
313         {
314           int mid, a, b;
315           int cursor_len = ABS (context->surrounding.cursor_idx -
316                                 context->surrounding.anchor_idx);
317 
318           if (cursor_len > MAX_LEN)
319             {
320               g_warn_if_reached ();
321               return;
322             }
323 
324           mid = MIN (context->surrounding.cursor_idx,
325                      context->surrounding.anchor_idx) + (cursor_len / 2);
326           a = MAX (0, mid - (MAX_LEN / 2));
327           b = MIN (len, mid + (MAX_LEN / 2));
328 
329           start = &context->surrounding.text[a];
330           end = &context->surrounding.text[b];
331         }
332 
333       if (start != context->surrounding.text)
334         start = g_utf8_next_char (start);
335       if (end != &context->surrounding.text[len])
336         end = g_utf8_find_prev_char (context->surrounding.text, end);
337 
338       cursor -= start - context->surrounding.text;
339       anchor -= start - context->surrounding.text;
340 
341       str = g_strndup (start, end - start);
342     }
343 
344   zwp_text_input_v3_set_surrounding_text (global->text_input,
345                                           str ? str : context->surrounding.text,
346                                           cursor, anchor);
347   zwp_text_input_v3_set_text_change_cause (global->text_input,
348                                            context->surrounding_change);
349   g_free (str);
350 #undef MAX_LEN
351 }
352 
353 static void
notify_cursor_location(GtkIMContextWayland * context)354 notify_cursor_location (GtkIMContextWayland *context)
355 {
356   cairo_rectangle_int_t rect;
357 
358   if (!global || !global->text_input)
359     return;
360   if (global->current != GTK_IM_CONTEXT (context))
361     return;
362   if (!context->enabled || !context->window)
363     return;
364 
365   rect = context->cursor_rect;
366   gdk_window_get_root_coords (context->window, rect.x, rect.y,
367                               &rect.x, &rect.y);
368 
369   zwp_text_input_v3_set_cursor_rectangle (global->text_input,
370                                           rect.x, rect.y,
371                                           rect.width, rect.height);
372 }
373 
374 static uint32_t
translate_hints(GtkInputHints input_hints,GtkInputPurpose purpose)375 translate_hints (GtkInputHints   input_hints,
376                  GtkInputPurpose purpose)
377 {
378   uint32_t hints = 0;
379 
380   if (input_hints & GTK_INPUT_HINT_SPELLCHECK)
381     hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK;
382   if (input_hints & GTK_INPUT_HINT_WORD_COMPLETION)
383     hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION;
384   if (input_hints & GTK_INPUT_HINT_LOWERCASE)
385     hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE;
386   if (input_hints & GTK_INPUT_HINT_UPPERCASE_CHARS)
387     hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE;
388   if (input_hints & GTK_INPUT_HINT_UPPERCASE_WORDS)
389     hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE;
390   if (input_hints & GTK_INPUT_HINT_UPPERCASE_SENTENCES)
391     hints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION;
392 
393   if (purpose == GTK_INPUT_PURPOSE_PIN ||
394       purpose == GTK_INPUT_PURPOSE_PASSWORD)
395     {
396       hints |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT |
397                 ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
398     }
399 
400   return hints;
401 }
402 
403 static uint32_t
translate_purpose(GtkInputPurpose purpose)404 translate_purpose (GtkInputPurpose purpose)
405 {
406   switch (purpose)
407     {
408     case GTK_INPUT_PURPOSE_FREE_FORM:
409       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
410     case GTK_INPUT_PURPOSE_ALPHA:
411       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA;
412     case GTK_INPUT_PURPOSE_DIGITS:
413       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS;
414     case GTK_INPUT_PURPOSE_NUMBER:
415       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER;
416     case GTK_INPUT_PURPOSE_PHONE:
417       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE;
418     case GTK_INPUT_PURPOSE_URL:
419       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL;
420     case GTK_INPUT_PURPOSE_EMAIL:
421       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL;
422     case GTK_INPUT_PURPOSE_NAME:
423       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME;
424     case GTK_INPUT_PURPOSE_PASSWORD:
425       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
426     case GTK_INPUT_PURPOSE_PIN:
427       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
428     case GTK_INPUT_PURPOSE_TERMINAL:
429       return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL;
430     }
431 
432   return ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
433 }
434 
435 static void
notify_content_type(GtkIMContextWayland * context)436 notify_content_type (GtkIMContextWayland *context)
437 {
438   GtkInputHints hints;
439   GtkInputPurpose purpose;
440 
441   if (global->current != GTK_IM_CONTEXT (context))
442     return;
443 
444   if (!context->enabled)
445     return;
446 
447   g_object_get (context,
448                 "input-hints", &hints,
449                 "input-purpose", &purpose,
450                 NULL);
451 
452   zwp_text_input_v3_set_content_type (global->text_input,
453                                       translate_hints (hints, purpose),
454                                       translate_purpose (purpose));
455 }
456 
457 static void
commit_state(GtkIMContextWayland * context)458 commit_state (GtkIMContextWayland *context)
459 {
460   if (global->current != GTK_IM_CONTEXT (context))
461     return;
462   if (!context->enabled)
463     return;
464   global->serial++;
465   zwp_text_input_v3_commit (global->text_input);
466   context->surrounding_change = ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD;
467 }
468 
469 static void
gtk_im_context_wayland_finalize(GObject * object)470 gtk_im_context_wayland_finalize (GObject *object)
471 {
472   GtkIMContextWayland *context = GTK_IM_CONTEXT_WAYLAND (object);
473 
474   gtk_im_context_wayland_focus_out (GTK_IM_CONTEXT (context));
475 
476   g_clear_object (&context->window);
477   g_clear_object (&context->gesture);
478   g_free (context->surrounding.text);
479   g_free (context->current_preedit.text);
480   g_free (context->pending_preedit.text);
481   g_free (context->pending_commit);
482 
483   G_OBJECT_CLASS (parent_class)->finalize (object);
484 }
485 
486 /* We want a unified experience between GtkIMContextSimple and IBus / Wayland
487  * when it comes to Compose sequences. IBus initial implementation of preedit
488  * for Compose sequences shows U+2384, which has been described as 'distracting'.
489  * This function tries to detect this case, and tweaks the text to match what
490  * GtkIMContextSimple produces.
491  */
492 static char *
tweak_preedit(const char * text)493 tweak_preedit (const char *text)
494 {
495   GString *s;
496   guint len;
497   const char *p;
498 
499   s = g_string_new ("");
500 
501   len = g_utf8_strlen (text, -1);
502 
503   for (p = text; *p; p = g_utf8_next_char (p))
504     {
505       gunichar ch = g_utf8_get_char (p);
506 
507       if (ch == 0x2384)
508         {
509           if (len == 1 || p > text)
510             g_string_append (s, "·");
511         }
512       else
513         g_string_append_unichar (s, ch);
514     }
515 
516   return g_string_free (s, FALSE);
517 }
518 
519 
520 static void
gtk_im_context_wayland_get_preedit_string(GtkIMContext * context,gchar ** str,PangoAttrList ** attrs,gint * cursor_pos)521 gtk_im_context_wayland_get_preedit_string (GtkIMContext   *context,
522                                            gchar         **str,
523                                            PangoAttrList **attrs,
524                                            gint           *cursor_pos)
525 {
526   GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context);
527   char *preedit_str;
528 
529   if (attrs)
530     *attrs = NULL;
531 
532   GTK_IM_CONTEXT_CLASS (parent_class)->get_preedit_string (context,
533                                                            str, attrs,
534                                                            cursor_pos);
535 
536   /* If the parent implementation returns a len>0 string, go with it */
537   if (str && *str)
538     {
539       if (**str)
540         return;
541 
542       g_free (*str);
543     }
544 
545   preedit_str =
546     tweak_preedit (context_wayland->current_preedit.text ? context_wayland->current_preedit.text : "");
547 
548   if (cursor_pos)
549     *cursor_pos = g_utf8_strlen (preedit_str,
550                                  context_wayland->current_preedit.cursor_begin);
551 
552   if (attrs)
553     {
554       if (!*attrs)
555         *attrs = pango_attr_list_new ();
556       pango_attr_list_insert (*attrs,
557                               pango_attr_underline_new (PANGO_UNDERLINE_SINGLE));
558       if (context_wayland->current_preedit.cursor_begin
559           != context_wayland->current_preedit.cursor_end)
560         {
561           /* FIXME: Oh noes, how to highlight while taking into account user preferences? */
562           PangoAttribute *cursor = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
563           cursor->start_index = context_wayland->current_preedit.cursor_begin;
564           cursor->end_index = context_wayland->current_preedit.cursor_end;
565           pango_attr_list_insert (*attrs, cursor);
566         }
567     }
568 
569   if (str)
570     *str = preedit_str;
571   else
572     g_free (preedit_str);
573 }
574 
575 static gboolean
gtk_im_context_wayland_filter_keypress(GtkIMContext * context,GdkEventKey * key)576 gtk_im_context_wayland_filter_keypress (GtkIMContext *context,
577                                         GdkEventKey  *key)
578 {
579   /* This is done by the compositor */
580   return GTK_IM_CONTEXT_CLASS (parent_class)->filter_keypress (context, key);
581 }
582 
583 static void
enable(GtkIMContextWayland * context_wayland)584 enable (GtkIMContextWayland *context_wayland)
585 {
586   gboolean result;
587   /* Technically, text input isn't enabled until after the commit.
588    * In reality, enable can't fail, and notify functions need to know
589    * that they are free to send requests. */
590   context_wayland->enabled = TRUE;
591   zwp_text_input_v3_enable (global->text_input);
592   g_signal_emit_by_name (global->current, "retrieve-surrounding", &result);
593   notify_content_type (context_wayland);
594   notify_cursor_location (context_wayland);
595   commit_state (context_wayland);
596 }
597 
598 static void
disable(GtkIMContextWayland * context_wayland)599 disable (GtkIMContextWayland *context_wayland)
600 {
601   zwp_text_input_v3_disable (global->text_input);
602   commit_state (context_wayland);
603   context_wayland->enabled = FALSE;
604 
605   /* after disable, incoming state changes won't take effect anyway */
606   if (context_wayland->current_preedit.text)
607     {
608       text_input_preedit (global, global->text_input, NULL, 0, 0);
609       text_input_preedit_apply (global);
610     }
611 }
612 
613 
614 static void
pressed_cb(GtkGestureMultiPress * gesture,gint n_press,gdouble x,gdouble y,GtkIMContextWayland * context)615 pressed_cb (GtkGestureMultiPress *gesture,
616             gint                  n_press,
617             gdouble               x,
618             gdouble               y,
619             GtkIMContextWayland  *context)
620 {
621   if (n_press == 1)
622     {
623       context->press_x = x;
624       context->press_y = y;
625     }
626 }
627 
628 static void
released_cb(GtkGestureMultiPress * gesture,gint n_press,gdouble x,gdouble y,GtkIMContextWayland * context)629 released_cb (GtkGestureMultiPress *gesture,
630              gint                  n_press,
631              gdouble               x,
632              gdouble               y,
633              GtkIMContextWayland  *context)
634 {
635   GtkInputHints hints;
636 
637   if (!global->current)
638     return;
639 
640   g_object_get (context, "input-hints", &hints, NULL);
641 
642   if (global->focused &&
643       n_press == 1 &&
644       (hints & GTK_INPUT_HINT_INHIBIT_OSK) == 0 &&
645       !gtk_drag_check_threshold (context->widget,
646                                  context->press_x,
647                                  context->press_y,
648                                  x, y))
649     enable (context);
650 }
651 
652 static void
gtk_im_context_wayland_set_client_window(GtkIMContext * context,GdkWindow * window)653 gtk_im_context_wayland_set_client_window (GtkIMContext *context,
654                                           GdkWindow    *window)
655 {
656   GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context);
657   GtkWidget *widget = NULL;
658 
659   if (window == context_wayland->window)
660     return;
661 
662   if (window)
663     gdk_window_get_user_data (window, (gpointer*) &widget);
664 
665   if (context_wayland->widget && context_wayland->widget != widget)
666     g_clear_object (&context_wayland->gesture);
667 
668   g_set_object (&context_wayland->window, window);
669 
670   if (context_wayland->widget != widget)
671     {
672       context_wayland->widget = widget;
673 
674       if (widget)
675         {
676           GtkGesture *gesture;
677 
678           gesture = gtk_gesture_multi_press_new (widget);
679           gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
680                                                       GTK_PHASE_CAPTURE);
681           g_signal_connect (gesture, "pressed",
682                             G_CALLBACK (pressed_cb), context);
683           g_signal_connect (gesture, "released",
684                             G_CALLBACK (released_cb), context);
685           context_wayland->gesture = gesture;
686         }
687     }
688 
689   GTK_IM_CONTEXT_CLASS (parent_class)->set_client_window (context, window);
690 }
691 
692 static void
text_input_enter(void * data,struct zwp_text_input_v3 * text_input,struct wl_surface * surface)693 text_input_enter (void                     *data,
694                   struct zwp_text_input_v3 *text_input,
695                   struct wl_surface        *surface)
696 {
697   global->focused = TRUE;
698 
699   if (global->current)
700     enable (GTK_IM_CONTEXT_WAYLAND (global->current));
701 }
702 
703 static void
text_input_leave(void * data,struct zwp_text_input_v3 * text_input,struct wl_surface * surface)704 text_input_leave (void                     *data,
705                   struct zwp_text_input_v3 *text_input,
706                   struct wl_surface        *surface)
707 {
708   global->focused = FALSE;
709 
710   if (global->current)
711     disable (GTK_IM_CONTEXT_WAYLAND (global->current));
712 }
713 
714 
715 static const struct zwp_text_input_v3_listener text_input_listener = {
716   text_input_enter,
717   text_input_leave,
718   text_input_preedit,
719   text_input_commit,
720   text_input_delete_surrounding_text,
721   text_input_done,
722 };
723 
724 static void
registry_handle_global(void * data,struct wl_registry * registry,uint32_t id,const char * interface,uint32_t version)725 registry_handle_global (void               *data,
726                         struct wl_registry *registry,
727                         uint32_t            id,
728                         const char         *interface,
729                         uint32_t            version)
730 {
731   GtkIMContextWaylandGlobal *global = data;
732   GdkSeat *seat = gdk_display_get_default_seat (gdk_display_get_default ());
733 
734   if (strcmp (interface, "zwp_text_input_manager_v3") == 0)
735     {
736       global->text_input_manager_wl_id = id;
737       global->text_input_manager =
738         wl_registry_bind (global->registry, global->text_input_manager_wl_id,
739                           &zwp_text_input_manager_v3_interface, 1);
740       global->text_input =
741         zwp_text_input_manager_v3_get_text_input (global->text_input_manager,
742                                                   gdk_wayland_seat_get_wl_seat (seat));
743       global->serial = 0;
744       zwp_text_input_v3_add_listener (global->text_input,
745                                       &text_input_listener, global);
746     }
747 }
748 
749 static void
registry_handle_global_remove(void * data,struct wl_registry * registry,uint32_t id)750 registry_handle_global_remove (void               *data,
751                                struct wl_registry *registry,
752                                uint32_t            id)
753 {
754   GtkIMContextWaylandGlobal *global = data;
755 
756   if (id != global->text_input_manager_wl_id)
757     return;
758 
759   g_clear_pointer (&global->text_input, zwp_text_input_v3_destroy);
760   g_clear_pointer (&global->text_input_manager, zwp_text_input_manager_v3_destroy);
761 }
762 
763 static const struct wl_registry_listener registry_listener = {
764     registry_handle_global,
765     registry_handle_global_remove
766 };
767 
768 static void
gtk_im_context_wayland_global_init(GdkDisplay * display)769 gtk_im_context_wayland_global_init (GdkDisplay *display)
770 {
771   g_return_if_fail (global == NULL);
772 
773   global = g_new0 (GtkIMContextWaylandGlobal, 1);
774   global->display = gdk_wayland_display_get_wl_display (display);
775   global->registry = wl_display_get_registry (global->display);
776 
777   wl_registry_add_listener (global->registry, &registry_listener, global);
778 }
779 
780 static void
gtk_im_context_wayland_focus_in(GtkIMContext * context)781 gtk_im_context_wayland_focus_in (GtkIMContext *context)
782 {
783   GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context);
784 
785   if (global->current == context)
786     return;
787   if (!global->text_input)
788     return;
789 
790   if (context_wayland->gesture)
791     gtk_event_controller_reset (GTK_EVENT_CONTROLLER (context_wayland->gesture));
792   global->current = context;
793 
794   if (global->focused)
795     enable (context_wayland);
796 }
797 
798 static void
gtk_im_context_wayland_focus_out(GtkIMContext * context)799 gtk_im_context_wayland_focus_out (GtkIMContext *context)
800 {
801   GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context);
802 
803   if (global->current != context)
804     return;
805 
806   if (global->focused)
807     disable (context_wayland);
808 
809   global->current = NULL;
810 }
811 
812 static void
gtk_im_context_wayland_reset(GtkIMContext * context)813 gtk_im_context_wayland_reset (GtkIMContext *context)
814 {
815   notify_external_change (GTK_IM_CONTEXT_WAYLAND (context));
816 
817   GTK_IM_CONTEXT_CLASS (parent_class)->reset (context);
818 }
819 
820 static void
gtk_im_context_wayland_set_cursor_location(GtkIMContext * context,GdkRectangle * rect)821 gtk_im_context_wayland_set_cursor_location (GtkIMContext *context,
822                                             GdkRectangle *rect)
823 {
824   GtkIMContextWayland *context_wayland;
825   int side;
826 
827   context_wayland = GTK_IM_CONTEXT_WAYLAND (context);
828 
829   if (context_wayland->cursor_rect.x == rect->x &&
830       context_wayland->cursor_rect.y == rect->y &&
831       context_wayland->cursor_rect.width == rect->width &&
832       context_wayland->cursor_rect.height == rect->height)
833     return;
834 
835   /* Reset the gesture if the cursor changes too far (eg. clicking
836    * between disjoint positions in the text).
837    *
838    * Still Allow some jittering (a square almost double the cursor rect height
839    * on either side) as clicking on the exact same position between characters
840    * is hard.
841    */
842   side = context_wayland->cursor_rect.height;
843 
844   if (context_wayland->gesture &&
845       (ABS (rect->x - context_wayland->cursor_rect.x) >= side ||
846        ABS (rect->y - context_wayland->cursor_rect.y) >= side))
847     gtk_event_controller_reset (GTK_EVENT_CONTROLLER (context_wayland->gesture));
848 
849   context_wayland->cursor_rect = *rect;
850   notify_cursor_location (context_wayland);
851   commit_state (context_wayland);
852 }
853 
854 static void
gtk_im_context_wayland_set_use_preedit(GtkIMContext * context,gboolean use_preedit)855 gtk_im_context_wayland_set_use_preedit (GtkIMContext *context,
856                                         gboolean      use_preedit)
857 {
858   GtkIMContextWayland *context_wayland = GTK_IM_CONTEXT_WAYLAND (context);
859 
860   context_wayland->use_preedit = !!use_preedit;
861 }
862 
863 static void
gtk_im_context_wayland_set_surrounding(GtkIMContext * context,const gchar * text,gint len,gint cursor_index)864 gtk_im_context_wayland_set_surrounding (GtkIMContext *context,
865                                         const gchar  *text,
866                                         gint          len,
867                                         gint          cursor_index)
868 {
869   GtkIMContextWayland *context_wayland;
870 
871   context_wayland = GTK_IM_CONTEXT_WAYLAND (context);
872 
873   g_free (context_wayland->surrounding.text);
874   context_wayland->surrounding.text = g_strndup (text, len);
875   context_wayland->surrounding.cursor_idx = cursor_index;
876   /* Anchor is not exposed via the set_surrounding interface, emulating. */
877   context_wayland->surrounding.anchor_idx = cursor_index;
878 
879   notify_surrounding_text (context_wayland);
880   /* State changes coming from reset don't have any other opportunity to get
881    * committed. */
882   if (context_wayland->surrounding_change !=
883       ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD)
884     commit_state (context_wayland);
885 }
886 
887 static gboolean
gtk_im_context_wayland_get_surrounding(GtkIMContext * context,gchar ** text,gint * cursor_index)888 gtk_im_context_wayland_get_surrounding (GtkIMContext  *context,
889                                         gchar        **text,
890                                         gint          *cursor_index)
891 {
892   GtkIMContextWayland *context_wayland;
893 
894   context_wayland = GTK_IM_CONTEXT_WAYLAND (context);
895 
896   if (!context_wayland->surrounding.text)
897     return FALSE;
898 
899   *text = context_wayland->surrounding.text;
900   *cursor_index = context_wayland->surrounding.cursor_idx;
901   return TRUE;
902 }
903 
904 static void
gtk_im_context_wayland_class_init(GtkIMContextWaylandClass * klass)905 gtk_im_context_wayland_class_init (GtkIMContextWaylandClass *klass)
906 {
907   GObjectClass *object_class = G_OBJECT_CLASS (klass);
908   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (klass);
909 
910   object_class->finalize = gtk_im_context_wayland_finalize;
911 
912   im_context_class->set_client_window = gtk_im_context_wayland_set_client_window;
913   im_context_class->get_preedit_string = gtk_im_context_wayland_get_preedit_string;
914   im_context_class->filter_keypress = gtk_im_context_wayland_filter_keypress;
915   im_context_class->focus_in = gtk_im_context_wayland_focus_in;
916   im_context_class->focus_out = gtk_im_context_wayland_focus_out;
917   im_context_class->reset = gtk_im_context_wayland_reset;
918   im_context_class->set_cursor_location = gtk_im_context_wayland_set_cursor_location;
919   im_context_class->set_use_preedit = gtk_im_context_wayland_set_use_preedit;
920   im_context_class->set_surrounding = gtk_im_context_wayland_set_surrounding;
921   im_context_class->get_surrounding = gtk_im_context_wayland_get_surrounding;
922 
923   parent_class = g_type_class_peek_parent (klass);
924 }
925 
926 static void
on_content_type_changed(GtkIMContextWayland * context)927 on_content_type_changed (GtkIMContextWayland *context)
928 {
929   notify_content_type (context);
930   commit_state (context);
931 }
932 
933 static void
gtk_im_context_wayland_init(GtkIMContextWayland * context)934 gtk_im_context_wayland_init (GtkIMContextWayland *context)
935 {
936   context->use_preedit = TRUE;
937   g_signal_connect_swapped (context, "notify::input-purpose",
938                             G_CALLBACK (on_content_type_changed), context);
939   g_signal_connect_swapped (context, "notify::input-hints",
940                             G_CALLBACK (on_content_type_changed), context);
941 }
942 
943 static void
gtk_im_context_wayland_register_type(GTypeModule * module)944 gtk_im_context_wayland_register_type (GTypeModule *module)
945 {
946   const GTypeInfo object_info = {
947     sizeof (GtkIMContextWaylandClass),
948     NULL, NULL,
949     (GClassInitFunc) gtk_im_context_wayland_class_init,
950     NULL, NULL,
951     sizeof (GtkIMContextWayland),
952     0,
953     (GInstanceInitFunc) gtk_im_context_wayland_init,
954   };
955 
956   type_wayland = g_type_module_register_type (module,
957                                               GTK_TYPE_IM_CONTEXT_SIMPLE,
958                                               "GtkIMContextWayland",
959                                               &object_info, 0);
960 }
961 
MODULE_ENTRY(void,init)962 MODULE_ENTRY (void, init) (GTypeModule * module)
963 {
964   gtk_im_context_wayland_register_type (module);
965   gtk_im_context_wayland_global_init (gdk_display_get_default ());
966 }
967 
MODULE_ENTRY(void,exit)968 MODULE_ENTRY (void, exit) (void)
969 {
970 }
971 
MODULE_ENTRY(void,list)972 MODULE_ENTRY (void, list) (const GtkIMContextInfo *** contexts, int *n_contexts)
973 {
974   *contexts = info_list;
975   *n_contexts = G_N_ELEMENTS (info_list);
976 }
977 
MODULE_ENTRY(GtkIMContext *,create)978 MODULE_ENTRY (GtkIMContext *, create) (const gchar * context_id)
979 {
980   if (strcmp (context_id, "wayland") == 0)
981     return g_object_new (type_wayland, NULL);
982   else
983     return NULL;
984 }
985