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