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

source code of gtk/gtk/gtkimcontextwayland.c