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