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, ®istry_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