1 /*
2  * SPDX-FileCopyrightText: 2010~2020 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  */
6 
7 /**
8  * @file fcitximcontext.c
9  *
10  * This is a gtk im module for fcitx, using DBus as a protocol.
11  *        This is compromise to gtk and firefox, users are being sucked by them
12  *        again and again.
13  */
14 #include "fcitximcontext.h"
15 #include "config.h"
16 #include "fcitx-gclient/fcitxgclient.h"
17 #include "fcitx-gclient/fcitxgwatcher.h"
18 #include "fcitxflags.h"
19 #include "fcitxtheme.h"
20 #include "gtk4inputwindow.h"
21 #include <gdk/gdk.h>
22 #include <gdk/gdkevents.h>
23 #include <gdk/gdkkeysyms.h>
24 #include <gtk/gtk.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <xcb/xcb.h>
28 #include <xkbcommon/xkbcommon-compose.h>
29 
30 #ifdef GDK_WINDOWING_WAYLAND
31 #include <gdk/wayland/gdkwayland.h>
32 #endif
33 
34 #ifdef GDK_WINDOWING_X11
35 #include <gdk/x11/gdkx.h>
36 #endif
37 
38 using namespace fcitx::gtk;
39 
40 constexpr int MAX_CACHED_HANDLED_EVENT = 40;
41 
42 static const uint64_t purpose_related_capability =
43     fcitx::FcitxCapabilityFlag_Alpha | fcitx::FcitxCapabilityFlag_Digit |
44     fcitx::FcitxCapabilityFlag_Number | fcitx::FcitxCapabilityFlag_Dialable |
45     fcitx::FcitxCapabilityFlag_Url | fcitx::FcitxCapabilityFlag_Email |
46     fcitx::FcitxCapabilityFlag_Password;
47 
48 static const uint64_t hints_related_capability =
49     fcitx::FcitxCapabilityFlag_SpellCheck |
50     fcitx::FcitxCapabilityFlag_NoSpellCheck |
51     fcitx::FcitxCapabilityFlag_WordCompletion |
52     fcitx::FcitxCapabilityFlag_Lowercase |
53     fcitx::FcitxCapabilityFlag_Uppercase |
54     fcitx::FcitxCapabilityFlag_UppercaseWords |
55     fcitx::FcitxCapabilityFlag_UppwercaseSentences |
56     fcitx::FcitxCapabilityFlag_NoOnScreenKeyboard;
57 
58 struct KeyPressCallbackData {
KeyPressCallbackDataKeyPressCallbackData59     KeyPressCallbackData(FcitxIMContext *context, GdkEvent *event)
60         : context_(FCITX_IM_CONTEXT(g_object_ref(context))),
61           event_(gdk_event_ref(event)) {}
62 
~KeyPressCallbackDataKeyPressCallbackData63     ~KeyPressCallbackData() {
64         gdk_event_unref(event_);
65         g_object_unref(context_);
66     }
67 
68     FcitxIMContext *context_;
69     GdkEvent *event_;
70 };
71 
72 extern "C" {
73 
get_boolean_env(const char * name,bool defval)74 static bool get_boolean_env(const char *name, bool defval) {
75     const char *value = getenv(name);
76 
77     if (value == nullptr) {
78         return defval;
79     }
80 
81     if (g_strcmp0(value, "") == 0 || g_strcmp0(value, "0") == 0 ||
82         g_strcmp0(value, "false") == 0 || g_strcmp0(value, "False") == 0 ||
83         g_strcmp0(value, "FALSE") == 0) {
84         return false;
85     }
86 
87     return true;
88 }
89 
90 struct _FcitxIMContext {
91     GtkIMContext parent;
92 
93     GtkWidget *client_widget;
94     GdkRectangle area;
95     FcitxGClient *client;
96     GtkIMContext *slave;
97     int has_focus;
98     guint32 time;
99     guint32 last_key_code;
100     bool last_is_release;
101     gboolean use_preedit;
102     gboolean support_surrounding_text;
103     gboolean is_inpreedit;
104     gboolean is_wayland;
105     char *preedit_string;
106     char *surrounding_text;
107     int cursor_pos;
108     guint64 capability_from_toolkit;
109     guint64 last_updated_capability;
110     PangoAttrList *attrlist;
111     int last_cursor_pos;
112     int last_anchor_pos;
113     struct xkb_compose_state *xkbComposeState;
114 
115     GHashTable *pending_events;
116     GHashTable *handled_events;
117     GQueue *handled_events_list;
118 
119     Gtk4InputWindow *candidate_window;
120 };
121 
122 struct _FcitxIMContextClass {
123     GtkIMContextClass parent;
124     /* klass members */
125 };
126 
127 /* functions prototype */
128 static void fcitx_im_context_class_init(FcitxIMContextClass *klass, gpointer);
129 static void fcitx_im_context_class_fini(FcitxIMContextClass *klass, gpointer);
130 static void fcitx_im_context_init(FcitxIMContext *im_context, gpointer);
131 static void fcitx_im_context_finalize(GObject *obj);
132 static void fcitx_im_context_set_client_widget(GtkIMContext *context,
133                                                GtkWidget *client_widget);
134 static gboolean fcitx_im_context_filter_keypress(GtkIMContext *context,
135                                                  GdkEvent *key);
136 static void fcitx_im_context_reset(GtkIMContext *context);
137 static void fcitx_im_context_focus_in(GtkIMContext *context);
138 static void fcitx_im_context_focus_out(GtkIMContext *context);
139 static void fcitx_im_context_set_cursor_location(GtkIMContext *context,
140                                                  GdkRectangle *area);
141 static void fcitx_im_context_set_use_preedit(GtkIMContext *context,
142                                              gboolean use_preedit);
143 static void fcitx_im_context_set_surrounding_with_selection(
144     GtkIMContext *context, const char *text, int len, int cursor_index,
145     int anchor_index);
146 static void fcitx_im_context_get_preedit_string(GtkIMContext *context,
147                                                 char **str,
148                                                 PangoAttrList **attrs,
149                                                 int *cursor_pos);
150 
151 static gboolean _set_cursor_location_internal(FcitxIMContext *fcitxcontext);
152 static gboolean _defer_request_surrounding_text(FcitxIMContext *fcitxcontext);
153 static void _slave_commit_cb(GtkIMContext *slave, char *string,
154                              FcitxIMContext *context);
155 static void _slave_preedit_changed_cb(GtkIMContext *slave,
156                                       FcitxIMContext *context);
157 static void _slave_preedit_start_cb(GtkIMContext *slave,
158                                     FcitxIMContext *context);
159 static void _slave_preedit_end_cb(GtkIMContext *slave, FcitxIMContext *context);
160 static gboolean _slave_retrieve_surrounding_cb(GtkIMContext *slave,
161                                                FcitxIMContext *context);
162 static gboolean _slave_delete_surrounding_cb(GtkIMContext *slave,
163                                              int offset_from_cursor,
164                                              guint nchars,
165                                              FcitxIMContext *context);
166 static void _fcitx_im_context_commit_string_cb(FcitxGClient *client, char *str,
167                                                void *user_data);
168 static void _fcitx_im_context_forward_key_cb(FcitxGClient *client, guint keyval,
169                                              guint state, int type,
170                                              void *user_data);
171 static void _fcitx_im_context_delete_surrounding_text_cb(FcitxGClient *client,
172                                                          int offset_from_cursor,
173                                                          guint nchars,
174                                                          void *user_data);
175 static void _fcitx_im_context_connect_cb(FcitxGClient *client, void *user_data);
176 static void _fcitx_im_context_update_formatted_preedit_cb(FcitxGClient *im,
177                                                           GPtrArray *array,
178                                                           int cursor_pos,
179                                                           void *user_data);
180 static void _fcitx_im_context_process_key_cb(GObject *source_object,
181                                              GAsyncResult *res,
182                                              gpointer user_data);
183 static void _fcitx_im_context_set_capability(FcitxIMContext *fcitxcontext,
184                                              gboolean force);
185 
186 static void _fcitx_im_context_input_hints_changed_cb(GObject *gobject,
187                                                      GParamSpec *pspec,
188                                                      gpointer user_data);
189 static void _fcitx_im_context_input_purpose_changed_cb(GObject *gobject,
190                                                        GParamSpec *pspec,
191                                                        gpointer user_data);
192 
193 static void _request_surrounding_text(FcitxIMContext **context);
194 
195 guint _update_auto_repeat_state(FcitxIMContext *context, GdkEvent *event);
196 
197 static GType _fcitx_type_im_context = 0;
198 static GtkIMContextClass *parent_class = NULL;
199 
200 static guint _signal_commit_id = 0;
201 static guint _signal_preedit_changed_id = 0;
202 static guint _signal_preedit_start_id = 0;
203 static guint _signal_preedit_end_id = 0;
204 static guint _signal_delete_surrounding_id = 0;
205 static guint _signal_retrieve_surrounding_id = 0;
206 static gboolean _use_preedit = TRUE;
207 static gboolean _use_sync_mode = TRUE;
208 
209 static GtkIMContext *_focus_im_context = NULL;
210 static const char *_no_preedit_apps = NO_PREEDIT_APPS;
211 static const char *_sync_mode_apps = SYNC_MODE_APPS;
212 static FcitxGWatcher *_watcher = NULL;
213 static struct xkb_context *xkbContext = NULL;
214 static struct xkb_compose_table *xkbComposeTable = NULL;
215 static ClassicUIConfig *_uiconfig = nullptr;
216 
fcitx_im_context_register_type(GTypeModule * type_module)217 void fcitx_im_context_register_type(GTypeModule *type_module) {
218     static const GTypeInfo fcitx_im_context_info = {
219         sizeof(FcitxIMContextClass),
220         (GBaseInitFunc)NULL,
221         (GBaseFinalizeFunc)NULL,
222         (GClassInitFunc)fcitx_im_context_class_init,
223         (GClassFinalizeFunc)fcitx_im_context_class_fini,
224         NULL, /* klass data */
225         sizeof(FcitxIMContext),
226         0,
227         (GInstanceInitFunc)fcitx_im_context_init,
228         0};
229 
230     if (_fcitx_type_im_context) {
231         return;
232     }
233     if (type_module) {
234         _fcitx_type_im_context = g_type_module_register_type(
235             type_module, GTK_TYPE_IM_CONTEXT, "FcitxIMContext",
236             &fcitx_im_context_info, (GTypeFlags)0);
237     } else {
238         _fcitx_type_im_context =
239             g_type_register_static(GTK_TYPE_IM_CONTEXT, "FcitxIMContext",
240                                    &fcitx_im_context_info, (GTypeFlags)0);
241     }
242 }
243 
fcitx_im_context_get_type(void)244 GType fcitx_im_context_get_type(void) {
245     if (_fcitx_type_im_context == 0) {
246         fcitx_im_context_register_type(NULL);
247     }
248 
249     g_assert(_fcitx_type_im_context != 0);
250     return _fcitx_type_im_context;
251 }
252 
fcitx_im_context_new(void)253 FcitxIMContext *fcitx_im_context_new(void) {
254     GObject *obj = (GObject *)g_object_new(FCITX_TYPE_IM_CONTEXT, NULL);
255     return FCITX_IM_CONTEXT(obj);
256 }
257 
check_app_name(const char * pattern)258 static gboolean check_app_name(const char *pattern) {
259     bool result = FALSE;
260     const char *prgname = g_get_prgname();
261     char **p;
262     char **apps = g_strsplit(pattern, ",", 0);
263     for (p = apps; *p != NULL; p++) {
264         if (g_regex_match_simple(*p, prgname, (GRegexCompileFlags)0,
265                                  (GRegexMatchFlags)0)) {
266             result = TRUE;
267             break;
268         }
269     }
270     g_strfreev(apps);
271     return result;
272 }
273 
274 ///
fcitx_im_context_class_init(FcitxIMContextClass * klass,gpointer)275 static void fcitx_im_context_class_init(FcitxIMContextClass *klass, gpointer) {
276     GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS(klass);
277     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
278 
279     parent_class = (GtkIMContextClass *)g_type_class_peek_parent(klass);
280 
281     im_context_class->set_client_widget = fcitx_im_context_set_client_widget;
282     im_context_class->filter_keypress = fcitx_im_context_filter_keypress;
283     im_context_class->reset = fcitx_im_context_reset;
284     im_context_class->get_preedit_string = fcitx_im_context_get_preedit_string;
285     im_context_class->focus_in = fcitx_im_context_focus_in;
286     im_context_class->focus_out = fcitx_im_context_focus_out;
287     im_context_class->set_cursor_location =
288         fcitx_im_context_set_cursor_location;
289     im_context_class->set_use_preedit = fcitx_im_context_set_use_preedit;
290     im_context_class->set_surrounding_with_selection =
291         fcitx_im_context_set_surrounding_with_selection;
292     gobject_class->finalize = fcitx_im_context_finalize;
293 
294     _signal_commit_id = g_signal_lookup("commit", G_TYPE_FROM_CLASS(klass));
295     g_assert(_signal_commit_id != 0);
296 
297     _signal_preedit_changed_id =
298         g_signal_lookup("preedit-changed", G_TYPE_FROM_CLASS(klass));
299     g_assert(_signal_preedit_changed_id != 0);
300 
301     _signal_preedit_start_id =
302         g_signal_lookup("preedit-start", G_TYPE_FROM_CLASS(klass));
303     g_assert(_signal_preedit_start_id != 0);
304 
305     _signal_preedit_end_id =
306         g_signal_lookup("preedit-end", G_TYPE_FROM_CLASS(klass));
307     g_assert(_signal_preedit_end_id != 0);
308 
309     _signal_delete_surrounding_id =
310         g_signal_lookup("delete-surrounding", G_TYPE_FROM_CLASS(klass));
311     g_assert(_signal_delete_surrounding_id != 0);
312 
313     _signal_retrieve_surrounding_id =
314         g_signal_lookup("retrieve-surrounding", G_TYPE_FROM_CLASS(klass));
315     g_assert(_signal_retrieve_surrounding_id != 0);
316 
317     // Check preedit blacklist
318     if (g_getenv("FCITX_NO_PREEDIT_APPS")) {
319         _no_preedit_apps = g_getenv("FCITX_NO_PREEDIT_APPS");
320     }
321     _use_preedit = !check_app_name(_no_preedit_apps);
322 
323     // Check sync mode
324     if (g_getenv("FCITX_SYNC_MODE_APPS")) {
325         _sync_mode_apps = g_getenv("FCITX_SYNC_MODE_APPS");
326     }
327     _use_sync_mode = _use_sync_mode || check_app_name(_sync_mode_apps);
328     if (g_getenv("IBUS_ENABLE_SYNC_MODE") ||
329         g_getenv("FCITX_ENABLE_SYNC_MODE")) {
330         /* make ibus fix benefits us */
331         _use_sync_mode = get_boolean_env("IBUS_ENABLE_SYNC_MODE", FALSE) ||
332                          get_boolean_env("FCITX_ENABLE_SYNC_MODE", FALSE);
333     }
334 }
335 
fcitx_im_context_class_fini(FcitxIMContextClass *,gpointer)336 static void fcitx_im_context_class_fini(FcitxIMContextClass *, gpointer) {}
337 
fcitx_im_context_init(FcitxIMContext * context,gpointer)338 static void fcitx_im_context_init(FcitxIMContext *context, gpointer) {
339     context->client = NULL;
340     context->area.x = -1;
341     context->area.y = -1;
342     context->area.width = 0;
343     context->area.height = 0;
344     context->use_preedit = _use_preedit;
345     context->cursor_pos = 0;
346     context->last_anchor_pos = -1;
347     context->last_cursor_pos = -1;
348     context->preedit_string = NULL;
349     context->attrlist = NULL;
350     context->last_updated_capability =
351         (guint64)fcitx::FcitxCapabilityFlag_SurroundingText;
352 
353 #ifdef GDK_WINDOWING_WAYLAND
354     if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) {
355         context->is_wayland = TRUE;
356     }
357 #endif
358     context->slave = gtk_im_context_simple_new();
359 
360     g_signal_connect(context->slave, "commit", G_CALLBACK(_slave_commit_cb),
361                      context);
362     g_signal_connect(context->slave, "preedit-start",
363                      G_CALLBACK(_slave_preedit_start_cb), context);
364     g_signal_connect(context->slave, "preedit-end",
365                      G_CALLBACK(_slave_preedit_end_cb), context);
366     g_signal_connect(context->slave, "preedit-changed",
367                      G_CALLBACK(_slave_preedit_changed_cb), context);
368     g_signal_connect(context->slave, "retrieve-surrounding",
369                      G_CALLBACK(_slave_retrieve_surrounding_cb), context);
370     g_signal_connect(context->slave, "delete-surrounding",
371                      G_CALLBACK(_slave_delete_surrounding_cb), context);
372 
373     g_signal_connect(context, "notify::input-hints",
374                      G_CALLBACK(_fcitx_im_context_input_hints_changed_cb),
375                      NULL);
376     g_signal_connect(context, "notify::input-purpose",
377                      G_CALLBACK(_fcitx_im_context_input_purpose_changed_cb),
378                      NULL);
379 
380     context->time = GDK_CURRENT_TIME;
381     context->pending_events =
382         g_hash_table_new_full(g_direct_hash, g_direct_equal,
383                               (GDestroyNotify)gdk_event_unref, nullptr);
384     context->handled_events =
385         g_hash_table_new_full(g_direct_hash, g_direct_equal,
386                               (GDestroyNotify)gdk_event_unref, nullptr);
387     context->handled_events_list = g_queue_new();
388 
389     static gsize has_info = 0;
390     if (g_once_init_enter(&has_info)) {
391         _watcher = fcitx_g_watcher_new();
392         _uiconfig = new ClassicUIConfig;
393         fcitx_g_watcher_set_watch_portal(_watcher, TRUE);
394         fcitx_g_watcher_watch(_watcher);
395         g_object_ref_sink(_watcher);
396 
397         xkbContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
398 
399         if (xkbContext) {
400             xkb_context_set_log_level(xkbContext, XKB_LOG_LEVEL_CRITICAL);
401         }
402 
403         const char *locale = getenv("LC_ALL");
404         if (!locale)
405             locale = getenv("LC_CTYPE");
406         if (!locale)
407             locale = getenv("LANG");
408         if (!locale)
409             locale = "C";
410 
411         xkbComposeTable =
412             xkbContext ? xkb_compose_table_new_from_locale(
413                              xkbContext, locale, XKB_COMPOSE_COMPILE_NO_FLAGS)
414                        : NULL;
415 
416         g_once_init_leave(&has_info, 1);
417     }
418 
419     context->client = fcitx_g_client_new_with_watcher(_watcher);
420     fcitx_g_client_set_program(context->client, g_get_prgname());
421     if (context->is_wayland) {
422         fcitx_g_client_set_display(context->client, "wayland:");
423     } else {
424 #ifdef GDK_WINDOWING_X11
425         if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
426             fcitx_g_client_set_display(context->client, "x11:");
427         }
428 #endif
429     }
430     g_signal_connect(context->client, "connected",
431                      G_CALLBACK(_fcitx_im_context_connect_cb), context);
432     g_signal_connect(context->client, "forward-key",
433                      G_CALLBACK(_fcitx_im_context_forward_key_cb), context);
434     g_signal_connect(context->client, "commit-string",
435                      G_CALLBACK(_fcitx_im_context_commit_string_cb), context);
436     g_signal_connect(context->client, "delete-surrounding-text",
437                      G_CALLBACK(_fcitx_im_context_delete_surrounding_text_cb),
438                      context);
439     g_signal_connect(context->client, "update-formatted-preedit",
440                      G_CALLBACK(_fcitx_im_context_update_formatted_preedit_cb),
441                      context);
442 
443     context->xkbComposeState =
444         xkbComposeTable
445             ? xkb_compose_state_new(xkbComposeTable, XKB_COMPOSE_STATE_NO_FLAGS)
446             : NULL;
447 }
448 
fcitx_im_context_finalize(GObject * obj)449 static void fcitx_im_context_finalize(GObject *obj) {
450     FcitxIMContext *context = FCITX_IM_CONTEXT(obj);
451 
452     g_clear_pointer(&context->handled_events_list, g_queue_free);
453     g_clear_pointer(&context->pending_events, g_hash_table_unref);
454     g_clear_pointer(&context->handled_events, g_hash_table_unref);
455     fcitx_im_context_set_client_widget(GTK_IM_CONTEXT(context), NULL);
456 
457 #ifndef g_signal_handlers_disconnect_by_data
458 #define g_signal_handlers_disconnect_by_data(instance, data)                   \
459     g_signal_handlers_disconnect_matched((instance), G_SIGNAL_MATCH_DATA, 0,   \
460                                          0, NULL, NULL, (data))
461 #endif
462 
463     g_clear_pointer(&context->xkbComposeState, xkb_compose_state_unref);
464     if (context->client) {
465         g_signal_handlers_disconnect_by_data(context->client, context);
466     }
467     g_clear_object(&context->client);
468 
469     g_clear_pointer(&context->preedit_string, g_free);
470     g_clear_pointer(&context->surrounding_text, g_free);
471     g_clear_pointer(&context->attrlist, pango_attr_list_unref);
472 
473     G_OBJECT_CLASS(parent_class)->finalize(obj);
474 }
475 
476 ///
fcitx_im_context_set_client_widget(GtkIMContext * context,GtkWidget * client_widget)477 static void fcitx_im_context_set_client_widget(GtkIMContext *context,
478                                                GtkWidget *client_widget) {
479     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context);
480     if (!client_widget)
481         return;
482 
483     g_clear_object(&fcitxcontext->client_widget);
484     fcitxcontext->client_widget = GTK_WIDGET(g_object_ref(client_widget));
485     if (!fcitxcontext->candidate_window) {
486         fcitxcontext->candidate_window =
487             new Gtk4InputWindow(_uiconfig, fcitxcontext->client);
488         fcitxcontext->candidate_window->setParent(fcitxcontext->client_widget);
489         fcitxcontext->candidate_window->setCursorRect(fcitxcontext->area);
490     }
491 }
492 
493 static gboolean
fcitx_im_context_filter_keypress_fallback(FcitxIMContext * context,GdkEvent * event)494 fcitx_im_context_filter_keypress_fallback(FcitxIMContext *context,
495                                           GdkEvent *event) {
496     if (!context->xkbComposeState ||
497         gdk_event_get_event_type(event) == GDK_KEY_RELEASE) {
498         return gtk_im_context_filter_keypress(context->slave, event);
499     }
500 
501     struct xkb_compose_state *xkbComposeState = context->xkbComposeState;
502 
503     enum xkb_compose_feed_result result = xkb_compose_state_feed(
504         xkbComposeState, gdk_key_event_get_keyval(event));
505     if (result == XKB_COMPOSE_FEED_IGNORED) {
506         return gtk_im_context_filter_keypress(context->slave, event);
507     }
508 
509     enum xkb_compose_status status =
510         xkb_compose_state_get_status(xkbComposeState);
511     if (status == XKB_COMPOSE_NOTHING) {
512         return gtk_im_context_filter_keypress(context->slave, event);
513     } else if (status == XKB_COMPOSE_COMPOSED) {
514         char buffer[] = {'\0', '\0', '\0', '\0', '\0', '\0', '\0'};
515         int length =
516             xkb_compose_state_get_utf8(xkbComposeState, buffer, sizeof(buffer));
517         xkb_compose_state_reset(xkbComposeState);
518         if (length != 0) {
519             g_signal_emit(context, _signal_commit_id, 0, buffer);
520         }
521 
522     } else if (status == XKB_COMPOSE_CANCELLED) {
523         xkb_compose_state_reset(xkbComposeState);
524     }
525 
526     return TRUE;
527 }
528 
fcitx_im_context_mark_event_handled(FcitxIMContext * fcitxcontext,GdkEvent * event)529 void fcitx_im_context_mark_event_handled(FcitxIMContext *fcitxcontext,
530                                          GdkEvent *event) {
531     g_hash_table_add(fcitxcontext->handled_events,
532                      gdk_event_ref(GDK_EVENT(event)));
533     g_hash_table_remove(fcitxcontext->pending_events, event);
534     g_queue_push_tail(fcitxcontext->handled_events_list, event);
535 
536     while (g_hash_table_size(fcitxcontext->handled_events) >
537            MAX_CACHED_HANDLED_EVENT) {
538         g_hash_table_remove(
539             fcitxcontext->handled_events,
540             g_queue_pop_head(fcitxcontext->handled_events_list));
541     }
542 }
543 
544 ///
fcitx_im_context_filter_keypress(GtkIMContext * context,GdkEvent * event)545 static gboolean fcitx_im_context_filter_keypress(GtkIMContext *context,
546                                                  GdkEvent *event) {
547     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context);
548     if (g_hash_table_contains(fcitxcontext->handled_events, event)) {
549         return TRUE;
550     }
551 
552     if (g_hash_table_contains(fcitxcontext->pending_events, event)) {
553         fcitx_im_context_mark_event_handled(fcitxcontext, event);
554         return gtk_im_context_filter_keypress(fcitxcontext->slave, event);
555     }
556 
557     if (fcitx_g_client_is_valid(fcitxcontext->client) &&
558         fcitxcontext->has_focus) {
559         _request_surrounding_text(&fcitxcontext);
560         if (G_UNLIKELY(!fcitxcontext))
561             return FALSE;
562 
563         auto state = _update_auto_repeat_state(fcitxcontext, event);
564 
565         if (_use_sync_mode) {
566             gboolean ret = fcitx_g_client_process_key_sync(
567                 fcitxcontext->client, gdk_key_event_get_keyval(event),
568                 gdk_key_event_get_keycode(event), state,
569                 (gdk_event_get_event_type(event) != GDK_KEY_PRESS),
570                 gdk_event_get_time(event));
571             if (ret) {
572                 return TRUE;
573             } else {
574                 return fcitx_im_context_filter_keypress_fallback(fcitxcontext,
575                                                                  event);
576             }
577         } else {
578             g_hash_table_add(fcitxcontext->pending_events,
579                              gdk_event_ref(GDK_EVENT(event)));
580             fcitx_g_client_process_key(
581                 fcitxcontext->client, gdk_key_event_get_keyval(event),
582                 gdk_key_event_get_keycode(event), state,
583                 (gdk_event_get_event_type(event) != GDK_KEY_PRESS),
584                 gdk_event_get_time(event), -1, NULL,
585                 _fcitx_im_context_process_key_cb,
586                 new KeyPressCallbackData(fcitxcontext, event));
587             return TRUE;
588         }
589     } else {
590         return fcitx_im_context_filter_keypress_fallback(fcitxcontext, event);
591     }
592     return FALSE;
593 }
594 
_fcitx_im_context_process_key_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)595 static void _fcitx_im_context_process_key_cb(GObject *source_object,
596                                              GAsyncResult *res,
597                                              gpointer user_data) {
598     KeyPressCallbackData *data = (KeyPressCallbackData *)user_data;
599     gboolean ret =
600         fcitx_g_client_process_key_finish(FCITX_G_CLIENT(source_object), res);
601     if (!ret) {
602         gdk_display_put_event(gdk_event_get_display(data->event_),
603                               data->event_);
604     } else {
605         fcitx_im_context_mark_event_handled(data->context_, data->event_);
606     }
607     delete data;
608 }
609 
_fcitx_im_context_update_preedit(FcitxIMContext * context,GPtrArray * array,int cursor_pos)610 static void _fcitx_im_context_update_preedit(FcitxIMContext *context,
611                                              GPtrArray *array, int cursor_pos) {
612     context->attrlist = pango_attr_list_new();
613 
614     GString *gstr = g_string_new(NULL);
615 
616     unsigned int i = 0;
617     for (i = 0; i < array->len; i++) {
618         size_t bytelen = strlen(gstr->str);
619         FcitxGPreeditItem *preedit =
620             (FcitxGPreeditItem *)g_ptr_array_index(array, i);
621         const char *s = preedit->string;
622         int type = preedit->type;
623 
624         PangoAttribute *pango_attr = NULL;
625         if ((type & (guint32)fcitx::FcitxTextFormatFlag_Underline)) {
626             pango_attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
627             pango_attr->start_index = bytelen;
628             pango_attr->end_index = bytelen + strlen(s);
629             pango_attr_list_insert(context->attrlist, pango_attr);
630         }
631         if ((type & (guint32)fcitx::FcitxTextFormatFlag_Strike)) {
632             pango_attr = pango_attr_strikethrough_new(true);
633             pango_attr->start_index = bytelen;
634             pango_attr->end_index = bytelen + strlen(s);
635             pango_attr_list_insert(context->attrlist, pango_attr);
636         }
637         if ((type & (guint32)fcitx::FcitxTextFormatFlag_Bold)) {
638             pango_attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
639             pango_attr->start_index = bytelen;
640             pango_attr->end_index = bytelen + strlen(s);
641             pango_attr_list_insert(context->attrlist, pango_attr);
642         }
643         if ((type & (guint32)fcitx::FcitxTextFormatFlag_Italic)) {
644             pango_attr = pango_attr_style_new(PANGO_STYLE_ITALIC);
645             pango_attr->start_index = bytelen;
646             pango_attr->end_index = bytelen + strlen(s);
647             pango_attr_list_insert(context->attrlist, pango_attr);
648         }
649 
650         if (type & (guint32)fcitx::FcitxTextFormatFlag_HighLight) {
651             gboolean hasColor = false;
652             guint fg_red = 0, fg_green = 0, fg_blue = 0, bg_red = 0,
653                   bg_green = 0, bg_blue = 0;
654 
655             if (context->client_widget) {
656                 hasColor = true;
657                 GtkStyleContext *styleContext =
658                     gtk_widget_get_style_context(context->client_widget);
659                 GdkRGBA fg_rgba, bg_rgba;
660                 hasColor =
661                     gtk_style_context_lookup_color(
662                         styleContext, "theme_selected_bg_color", &bg_rgba) &&
663                     gtk_style_context_lookup_color(
664                         styleContext, "theme_selected_fg_color", &fg_rgba);
665 
666                 if (!(fg_rgba.red == bg_rgba.red &&
667                       fg_rgba.green == bg_rgba.green &&
668                       fg_rgba.blue == bg_rgba.blue)) {
669                     hasColor = false;
670                 }
671                 if (hasColor) {
672                     fg_red = CLAMP((gint)(fg_rgba.red * 65535), 0, 65535);
673                     fg_green = CLAMP((gint)(fg_rgba.green * 65535), 0, 65535);
674                     fg_blue = CLAMP((gint)(fg_rgba.blue * 65535), 0, 65535);
675                     bg_red = CLAMP((gint)(bg_rgba.red * 65535), 0, 65535);
676                     bg_green = CLAMP((gint)(bg_rgba.green * 65535), 0, 65535);
677                     bg_blue = CLAMP((gint)(bg_rgba.blue * 65535), 0, 65535);
678                 }
679             }
680 
681             if (!hasColor) {
682                 fg_red = 0xffff;
683                 fg_green = 0xffff;
684                 fg_blue = 0xffff;
685                 bg_red = 0x43ff;
686                 bg_green = 0xacff;
687                 bg_blue = 0xe8ff;
688             }
689 
690             pango_attr = pango_attr_foreground_new(fg_red, fg_green, fg_blue);
691             pango_attr->start_index = bytelen;
692             pango_attr->end_index = bytelen + strlen(s);
693             pango_attr_list_insert(context->attrlist, pango_attr);
694             pango_attr = pango_attr_background_new(bg_red, bg_green, bg_blue);
695             pango_attr->start_index = bytelen;
696             pango_attr->end_index = bytelen + strlen(s);
697             pango_attr_list_insert(context->attrlist, pango_attr);
698         }
699         gstr = g_string_append(gstr, s);
700     }
701 
702     char *str = g_string_free(gstr, FALSE);
703 
704     context->preedit_string = str;
705     context->cursor_pos = g_utf8_pointer_to_offset(str, str + cursor_pos);
706 
707     if (context->preedit_string != NULL && context->preedit_string[0] == 0) {
708         g_clear_pointer(&context->preedit_string, g_free);
709     }
710 }
711 
_fcitx_im_context_update_formatted_preedit_cb(FcitxGClient *,GPtrArray * array,int cursor_pos,void * user_data)712 static void _fcitx_im_context_update_formatted_preedit_cb(FcitxGClient *,
713                                                           GPtrArray *array,
714                                                           int cursor_pos,
715                                                           void *user_data) {
716     FcitxIMContext *context = FCITX_IM_CONTEXT(user_data);
717 
718     gboolean visible = false;
719 
720     if (cursor_pos < 0) {
721         cursor_pos = 0;
722     }
723 
724     if (context->preedit_string != NULL) {
725         if (strlen(context->preedit_string) != 0) {
726             visible = true;
727         }
728 
729         g_clear_pointer(&context->preedit_string, g_free);
730     }
731     g_clear_pointer(&context->attrlist, pango_attr_list_unref);
732 
733     if (context->use_preedit) {
734         _fcitx_im_context_update_preedit(context, array, cursor_pos);
735     }
736 
737     gboolean new_visible = context->preedit_string != NULL;
738 
739     gboolean flag = new_visible != visible;
740 
741     if (new_visible) {
742         if (flag) {
743             /* invisible => visible */
744             g_signal_emit(context, _signal_preedit_start_id, 0);
745         }
746         g_signal_emit(context, _signal_preedit_changed_id, 0);
747     } else {
748         if (flag) {
749             /* visible => invisible */
750             g_signal_emit(context, _signal_preedit_changed_id, 0);
751             g_signal_emit(context, _signal_preedit_end_id, 0);
752         } else {
753             /* still invisible */
754             /* do nothing */
755         }
756     }
757 }
758 
759 ///
fcitx_im_context_focus_in(GtkIMContext * context)760 static void fcitx_im_context_focus_in(GtkIMContext *context) {
761     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context);
762 
763     if (fcitxcontext->has_focus) {
764         return;
765     }
766 
767     _fcitx_im_context_set_capability(fcitxcontext, FALSE);
768 
769     fcitxcontext->has_focus = true;
770 
771 /*
772  * Do not call gtk_im_context_focus_out() here.
773  * This might workaround some chrome issue
774  */
775 #if 0
776     if (_focus_im_context != NULL) {
777         g_assert (_focus_im_context != context);
778         gtk_im_context_focus_out (_focus_im_context);
779         g_assert (_focus_im_context == NULL);
780     }
781 #endif
782 
783     if (fcitx_g_client_is_valid(fcitxcontext->client)) {
784         fcitx_g_client_focus_in(fcitxcontext->client);
785     }
786 
787     gtk_im_context_focus_in(fcitxcontext->slave);
788 
789     /* set_cursor_location_internal() will get origin from X server,
790      * it blocks UI. So delay it to idle callback. */
791     g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
792                     (GSourceFunc)_set_cursor_location_internal,
793                     g_object_ref(fcitxcontext), (GDestroyNotify)g_object_unref);
794 
795     /* _request_surrounding_text may trigger freeze in Libreoffice. After
796      * focus in, the request is not as urgent as key event. Delay it to main
797      * idle callback. */
798     g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
799                     (GSourceFunc)_defer_request_surrounding_text,
800                     g_object_ref(fcitxcontext), (GDestroyNotify)g_object_unref);
801 
802     g_object_add_weak_pointer((GObject *)context,
803                               (gpointer *)&_focus_im_context);
804     _focus_im_context = context;
805 
806     return;
807 }
808 
fcitx_im_context_focus_out(GtkIMContext * context)809 static void fcitx_im_context_focus_out(GtkIMContext *context) {
810     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context);
811 
812     if (!fcitxcontext->has_focus) {
813         return;
814     }
815 
816     g_object_remove_weak_pointer((GObject *)context,
817                                  (gpointer *)&_focus_im_context);
818     _focus_im_context = NULL;
819 
820     fcitxcontext->has_focus = false;
821     fcitxcontext->last_key_code = 0;
822     fcitxcontext->last_is_release = false;
823 
824     if (fcitx_g_client_is_valid(fcitxcontext->client)) {
825         fcitx_g_client_focus_out(fcitxcontext->client);
826     }
827 
828     fcitxcontext->cursor_pos = 0;
829     if (fcitxcontext->preedit_string != NULL) {
830         g_clear_pointer(&fcitxcontext->preedit_string, g_free);
831         g_signal_emit(fcitxcontext, _signal_preedit_changed_id, 0);
832         g_signal_emit(fcitxcontext, _signal_preedit_end_id, 0);
833     }
834 
835     gtk_im_context_focus_out(fcitxcontext->slave);
836 
837     return;
838 }
839 
840 ///
fcitx_im_context_set_cursor_location(GtkIMContext * context,GdkRectangle * area)841 static void fcitx_im_context_set_cursor_location(GtkIMContext *context,
842                                                  GdkRectangle *area) {
843     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context);
844 
845     if (fcitxcontext->area.x == area->x && fcitxcontext->area.y == area->y &&
846         fcitxcontext->area.width == area->width &&
847         fcitxcontext->area.height == area->height) {
848         return;
849     }
850     fcitxcontext->area = *area;
851     if (fcitxcontext->candidate_window) {
852         fcitxcontext->candidate_window->setCursorRect(fcitxcontext->area);
853     }
854 
855     if (fcitx_g_client_is_valid(fcitxcontext->client)) {
856         _set_cursor_location_internal(fcitxcontext);
857     }
858     gtk_im_context_set_cursor_location(fcitxcontext->slave, area);
859 
860     return;
861 }
862 
_set_cursor_location_internal(FcitxIMContext * fcitxcontext)863 static gboolean _set_cursor_location_internal(FcitxIMContext *fcitxcontext) {
864     GdkRectangle area;
865 
866     if (fcitxcontext->client_widget == NULL ||
867         !fcitx_g_client_is_valid(fcitxcontext->client)) {
868         return FALSE;
869     }
870 
871     auto *root = gtk_widget_get_root(fcitxcontext->client_widget);
872     if (!root) {
873         return FALSE;
874     }
875 
876     area = fcitxcontext->area;
877 
878     int scale = gtk_widget_get_scale_factor(fcitxcontext->client_widget);
879     GdkDisplay *display = gtk_widget_get_display(fcitxcontext->client_widget);
880     double px, py;
881     gtk_widget_translate_coordinates(fcitxcontext->client_widget,
882                                      GTK_WIDGET(root), area.x, area.y, &px,
883                                      &py);
884     // Add frame.
885     double offsetX = 0, offsetY = 0;
886     if (auto native = gtk_widget_get_native(GTK_WIDGET(root))) {
887         gtk_native_get_surface_transform(native, &offsetX, &offsetY);
888     }
889     area.x = px + offsetX;
890     area.y = py + offsetY;
891 #ifdef GDK_WINDOWING_X11
892     if (GDK_IS_X11_DISPLAY(display)) {
893         if (auto *native = gtk_widget_get_native(GTK_WIDGET(root))) {
894             if (auto *surface = gtk_native_get_surface(native);
895                 surface && GDK_IS_X11_SURFACE(surface)) {
896                 if (area.x == -1 && area.y == -1 && area.width == 0 &&
897                     area.height == 0) {
898                     area.x = 0;
899                     area.y += gdk_surface_get_height(surface);
900                 }
901                 int rootX, rootY;
902                 Window child;
903                 XTranslateCoordinates(
904                     GDK_SURFACE_XDISPLAY(surface), GDK_SURFACE_XID(surface),
905                     gdk_x11_display_get_xrootwindow(display), area.x * scale,
906                     area.y * scale, &rootX, &rootY, &child);
907 
908                 rootX /= scale;
909                 rootY /= scale;
910                 area.x = rootX;
911                 area.y = rootY;
912             }
913         }
914     }
915 #endif
916     area.x *= scale;
917     area.y *= scale;
918     area.width *= scale;
919     area.height *= scale;
920 
921     // We don't really need this check, but we can keep certain level of
922     // compatibility for fcitx 4.
923     if (fcitxcontext->is_wayland) {
924         fcitx_g_client_set_cursor_rect_with_scale_factor(
925             fcitxcontext->client, area.x, area.y, area.width, area.height,
926             scale);
927     } else {
928         fcitx_g_client_set_cursor_rect(fcitxcontext->client, area.x, area.y,
929                                        area.width, area.height);
930     }
931     return FALSE;
932 }
933 
_defer_request_surrounding_text(FcitxIMContext * fcitxcontext)934 static gboolean _defer_request_surrounding_text(FcitxIMContext *fcitxcontext) {
935     _request_surrounding_text(&fcitxcontext);
936     return FALSE;
937 }
938 
939 ///
fcitx_im_context_set_use_preedit(GtkIMContext * context,gboolean use_preedit)940 static void fcitx_im_context_set_use_preedit(GtkIMContext *context,
941                                              gboolean use_preedit) {
942     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context);
943 
944     fcitxcontext->use_preedit = _use_preedit && use_preedit;
945     _fcitx_im_context_set_capability(fcitxcontext, FALSE);
946 
947     gtk_im_context_set_use_preedit(fcitxcontext->slave, use_preedit);
948 }
949 
get_selection_anchor_point(FcitxIMContext * fcitxcontext,guint cursor_pos,guint surrounding_text_len)950 static guint get_selection_anchor_point(FcitxIMContext *fcitxcontext,
951                                         guint cursor_pos,
952                                         guint surrounding_text_len) {
953     if (fcitxcontext->client_widget == NULL) {
954         return cursor_pos;
955     }
956     if (!GTK_IS_TEXT_VIEW(fcitxcontext->client_widget)) {
957         return cursor_pos;
958     }
959 
960     GtkTextView *text_view = GTK_TEXT_VIEW(fcitxcontext->client_widget);
961     GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
962 
963     if (!gtk_text_buffer_get_has_selection(buffer)) {
964         return cursor_pos;
965     }
966 
967     GtkTextIter start_iter, end_iter, cursor_iter;
968     if (!gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter)) {
969         return cursor_pos;
970     }
971 
972     gtk_text_buffer_get_iter_at_mark(buffer, &cursor_iter,
973                                      gtk_text_buffer_get_insert(buffer));
974 
975     guint start_index = gtk_text_iter_get_offset(&start_iter);
976     guint end_index = gtk_text_iter_get_offset(&end_iter);
977     guint cursor_index = gtk_text_iter_get_offset(&cursor_iter);
978 
979     guint anchor;
980 
981     if (start_index == cursor_index) {
982         anchor = end_index;
983     } else if (end_index == cursor_index) {
984         anchor = start_index;
985     } else {
986         return cursor_pos;
987     }
988 
989     // Change absolute index to relative position.
990     guint relative_origin = cursor_index - cursor_pos;
991 
992     if (anchor < relative_origin) {
993         return cursor_pos;
994     }
995     anchor -= relative_origin;
996 
997     if (anchor > surrounding_text_len) {
998         return cursor_pos;
999     }
1000 
1001     return anchor;
1002 }
1003 
fcitx_im_context_set_surrounding_with_selection(GtkIMContext * context,const char * text,int l,int cursor_index,int anchor_index)1004 static void fcitx_im_context_set_surrounding_with_selection(
1005     GtkIMContext *context, const char *text, int l, int cursor_index,
1006     int anchor_index) {
1007     g_return_if_fail(context != NULL);
1008     g_return_if_fail(FCITX_IS_IM_CONTEXT(context));
1009     g_return_if_fail(text != NULL);
1010 
1011     int len = l;
1012     if (len < 0) {
1013         len = strlen(text);
1014     }
1015 
1016     g_return_if_fail(0 <= cursor_index && cursor_index <= len);
1017 
1018     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context);
1019 
1020     if (fcitx_g_client_is_valid(fcitxcontext->client) &&
1021         !(fcitxcontext->last_updated_capability &
1022           (guint64)fcitx::FcitxCapabilityFlag_Password)) {
1023         int cursor_pos;
1024         guint utf8_len;
1025         char *p;
1026 
1027         p = g_strndup(text, len);
1028         cursor_pos = g_utf8_strlen(p, cursor_index);
1029         utf8_len = g_utf8_strlen(p, len);
1030 
1031         int anchor_pos;
1032         if (anchor_index == cursor_index) {
1033             anchor_pos =
1034                 get_selection_anchor_point(fcitxcontext, cursor_pos, utf8_len);
1035         } else {
1036             anchor_pos = g_utf8_strlen(p, anchor_index);
1037         }
1038         if (g_strcmp0(fcitxcontext->surrounding_text, p) == 0) {
1039             g_clear_pointer(&p, g_free);
1040         } else {
1041             g_free(fcitxcontext->surrounding_text);
1042             fcitxcontext->surrounding_text = p;
1043         }
1044 
1045         if (p || fcitxcontext->last_cursor_pos != cursor_pos ||
1046             fcitxcontext->last_anchor_pos != anchor_pos) {
1047             fcitxcontext->last_cursor_pos = cursor_pos;
1048             fcitxcontext->last_anchor_pos = anchor_pos;
1049             fcitx_g_client_set_surrounding_text(fcitxcontext->client, p,
1050                                                 cursor_pos, anchor_pos);
1051         }
1052     }
1053     gtk_im_context_set_surrounding_with_selection(fcitxcontext->slave, text, l,
1054                                                   cursor_index, anchor_index);
1055 }
1056 
_fcitx_im_context_set_capability(FcitxIMContext * fcitxcontext,gboolean force)1057 void _fcitx_im_context_set_capability(FcitxIMContext *fcitxcontext,
1058                                       gboolean force) {
1059     if (fcitx_g_client_is_valid(fcitxcontext->client)) {
1060         guint64 flags = fcitxcontext->capability_from_toolkit;
1061         // toolkit hint always not have preedit / surrounding hint
1062         // no need to check them
1063         if (fcitxcontext->use_preedit) {
1064             flags |= (guint64)fcitx::FcitxCapabilityFlag_Preedit |
1065                      (guint64)fcitx::FcitxCapabilityFlag_FormattedPreedit;
1066         }
1067         if (fcitxcontext->support_surrounding_text) {
1068             flags |= (guint64)fcitx::FcitxCapabilityFlag_SurroundingText;
1069         }
1070         if (fcitxcontext->is_wayland) {
1071             flags |= (guint64)fcitx::FcitxCapabilityFlag_RelativeRect;
1072         }
1073         flags |= (guint64)fcitx::FcitxCapabilityFlag_ClientSideInputPanel;
1074         flags |= (guint64)fcitx::FcitxCapabilityFlag_ReportKeyRepeat;
1075 
1076         // always run this code against all gtk version
1077         // seems visibility != PASSWORD hint
1078         if (fcitxcontext->client_widget != NULL) {
1079             if (GTK_IS_TEXT(fcitxcontext->client_widget) &&
1080                 !gtk_text_get_visibility(
1081                     GTK_TEXT(fcitxcontext->client_widget))) {
1082                 flags |= (guint64)fcitx::FcitxCapabilityFlag_Password;
1083             }
1084         }
1085 
1086         gboolean update = FALSE;
1087         if (G_UNLIKELY(fcitxcontext->last_updated_capability != flags)) {
1088             fcitxcontext->last_updated_capability = flags;
1089             update = TRUE;
1090         }
1091         if (G_UNLIKELY(update || force))
1092             fcitx_g_client_set_capability(
1093                 fcitxcontext->client, fcitxcontext->last_updated_capability);
1094     }
1095 }
1096 
1097 ///
fcitx_im_context_reset(GtkIMContext * context)1098 static void fcitx_im_context_reset(GtkIMContext *context) {
1099     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context);
1100 
1101     if (fcitx_g_client_is_valid(fcitxcontext->client)) {
1102         fcitx_g_client_reset(fcitxcontext->client);
1103     }
1104 
1105     if (fcitxcontext->xkbComposeState) {
1106         xkb_compose_state_reset(fcitxcontext->xkbComposeState);
1107     }
1108 
1109     gtk_im_context_reset(fcitxcontext->slave);
1110 }
1111 
fcitx_im_context_get_preedit_string(GtkIMContext * context,char ** str,PangoAttrList ** attrs,int * cursor_pos)1112 static void fcitx_im_context_get_preedit_string(GtkIMContext *context,
1113                                                 char **str,
1114                                                 PangoAttrList **attrs,
1115                                                 int *cursor_pos) {
1116     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context);
1117 
1118     if (fcitx_g_client_is_valid(fcitxcontext->client)) {
1119         if (str) {
1120             *str = g_strdup(fcitxcontext->preedit_string
1121                                 ? fcitxcontext->preedit_string
1122                                 : "");
1123         }
1124         if (attrs) {
1125             if (fcitxcontext->attrlist == NULL) {
1126                 *attrs = pango_attr_list_new();
1127 
1128                 if (str) {
1129                     PangoAttribute *pango_attr;
1130                     pango_attr =
1131                         pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
1132                     pango_attr->start_index = 0;
1133                     pango_attr->end_index = strlen(*str);
1134                     pango_attr_list_insert(*attrs, pango_attr);
1135                 }
1136             } else {
1137                 *attrs = pango_attr_list_ref(fcitxcontext->attrlist);
1138             }
1139         }
1140         if (cursor_pos)
1141             *cursor_pos = fcitxcontext->cursor_pos;
1142 
1143     } else {
1144         gtk_im_context_get_preedit_string(fcitxcontext->slave, str, attrs,
1145                                           cursor_pos);
1146     }
1147     return;
1148 }
1149 
1150 /* Callback functions for slave context */
_slave_commit_cb(GtkIMContext *,char * string,FcitxIMContext * context)1151 static void _slave_commit_cb(GtkIMContext *, char *string,
1152                              FcitxIMContext *context) {
1153     g_signal_emit(context, _signal_commit_id, 0, string);
1154 }
_slave_preedit_changed_cb(GtkIMContext *,FcitxIMContext * context)1155 static void _slave_preedit_changed_cb(GtkIMContext *, FcitxIMContext *context) {
1156     if (context->client) {
1157         return;
1158     }
1159 
1160     g_signal_emit(context, _signal_preedit_changed_id, 0);
1161 }
_slave_preedit_start_cb(GtkIMContext *,FcitxIMContext * context)1162 static void _slave_preedit_start_cb(GtkIMContext *, FcitxIMContext *context) {
1163     if (context->client) {
1164         return;
1165     }
1166 
1167     g_signal_emit(context, _signal_preedit_start_id, 0);
1168 }
1169 
_slave_preedit_end_cb(GtkIMContext *,FcitxIMContext * context)1170 static void _slave_preedit_end_cb(GtkIMContext *, FcitxIMContext *context) {
1171     if (context->client) {
1172         return;
1173     }
1174     g_signal_emit(context, _signal_preedit_end_id, 0);
1175 }
1176 
_slave_retrieve_surrounding_cb(GtkIMContext *,FcitxIMContext * context)1177 static gboolean _slave_retrieve_surrounding_cb(GtkIMContext *,
1178                                                FcitxIMContext *context) {
1179     gboolean return_value;
1180 
1181     if (context->client) {
1182         return FALSE;
1183     }
1184     g_signal_emit(context, _signal_retrieve_surrounding_id, 0, &return_value);
1185     return return_value;
1186 }
1187 
_slave_delete_surrounding_cb(GtkIMContext *,int offset_from_cursor,guint nchars,FcitxIMContext * context)1188 static gboolean _slave_delete_surrounding_cb(GtkIMContext *,
1189                                              int offset_from_cursor,
1190                                              guint nchars,
1191                                              FcitxIMContext *context) {
1192     gboolean return_value;
1193 
1194     if (context->client) {
1195         return FALSE;
1196     }
1197     g_signal_emit(context, _signal_delete_surrounding_id, 0, offset_from_cursor,
1198                   nchars, &return_value);
1199     return return_value;
1200 }
1201 
_fcitx_im_context_commit_string_cb(FcitxGClient *,char * str,void * user_data)1202 void _fcitx_im_context_commit_string_cb(FcitxGClient *, char *str,
1203                                         void *user_data) {
1204     FcitxIMContext *context = FCITX_IM_CONTEXT(user_data);
1205     g_signal_emit(context, _signal_commit_id, 0, str);
1206 
1207     // Better request surrounding after commit.
1208     g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
1209                     (GSourceFunc)_defer_request_surrounding_text,
1210                     g_object_ref(context), (GDestroyNotify)g_object_unref);
1211 }
1212 
_fcitx_im_context_forward_key_cb(FcitxGClient *,guint,guint,gboolean,void *)1213 void _fcitx_im_context_forward_key_cb(FcitxGClient *, guint, guint, gboolean,
1214                                       void *) {}
1215 
_fcitx_im_context_delete_surrounding_text_cb(FcitxGClient *,int offset_from_cursor,guint nchars,void * user_data)1216 static void _fcitx_im_context_delete_surrounding_text_cb(FcitxGClient *,
1217                                                          int offset_from_cursor,
1218                                                          guint nchars,
1219                                                          void *user_data) {
1220     FcitxIMContext *context = FCITX_IM_CONTEXT(user_data);
1221     gboolean return_value;
1222     g_signal_emit(context, _signal_delete_surrounding_id, 0, offset_from_cursor,
1223                   nchars, &return_value);
1224 }
1225 
1226 #ifdef GDK_WINDOWING_X11
send_uuid_to_x11(Display * xdisplay,const guint8 * uuid)1227 void send_uuid_to_x11(Display *xdisplay, const guint8 *uuid) {
1228     Atom atom = XInternAtom(xdisplay, "_FCITX_SERVER", False);
1229     if (!atom) {
1230         return;
1231     }
1232     Window window = XGetSelectionOwner(xdisplay, atom);
1233     if (!window) {
1234         return;
1235     }
1236     XEvent ev;
1237 
1238     memset(&ev, 0, sizeof(ev));
1239     ev.xclient.type = ClientMessage;
1240     ev.xclient.window = window;
1241     ev.xclient.message_type = atom;
1242     ev.xclient.format = 8;
1243     memcpy(ev.xclient.data.b, uuid, 16);
1244 
1245     XSendEvent(xdisplay, window, False, NoEventMask, &ev);
1246     XSync(xdisplay, False);
1247 }
1248 #endif
1249 
_fcitx_im_context_connect_cb(FcitxGClient * im G_GNUC_UNUSED,void * user_data)1250 void _fcitx_im_context_connect_cb(FcitxGClient *im G_GNUC_UNUSED,
1251                                   void *user_data) {
1252     FcitxIMContext *context = FCITX_IM_CONTEXT(user_data);
1253 #ifdef GDK_WINDOWING_X11
1254     Display *display = NULL;
1255     if (context->client_widget) {
1256         auto gdkDisplay = gtk_widget_get_display(context->client_widget);
1257         if (gdkDisplay) {
1258             auto x11DisplayType = g_type_from_name("GdkDisplayX11");
1259             if (x11DisplayType &&
1260                 G_TYPE_CHECK_INSTANCE_TYPE(gdkDisplay, x11DisplayType)) {
1261                 display = GDK_DISPLAY_XDISPLAY(gdkDisplay);
1262             }
1263         }
1264     }
1265     if (!display) {
1266         GdkDisplay *gdkDisplay = gdk_display_get_default();
1267         if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
1268             display = GDK_DISPLAY_XDISPLAY(gdkDisplay);
1269         }
1270     }
1271 
1272     if (display) {
1273         send_uuid_to_x11(display, fcitx_g_client_get_uuid(im));
1274     }
1275 #endif
1276 
1277     _fcitx_im_context_set_capability(context, TRUE);
1278     if (context->has_focus && _focus_im_context == (GtkIMContext *)context &&
1279         fcitx_g_client_is_valid(context->client))
1280         fcitx_g_client_focus_in(context->client);
1281     /* set_cursor_location_internal() will get origin from X server,
1282      * it blocks UI. So delay it to idle callback. */
1283     g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
1284                     (GSourceFunc)_set_cursor_location_internal,
1285                     g_object_ref(context), (GDestroyNotify)g_object_unref);
1286 }
1287 
_request_surrounding_text(FcitxIMContext ** context)1288 static void _request_surrounding_text(FcitxIMContext **context) {
1289     if (*context && fcitx_g_client_is_valid((*context)->client) &&
1290         (*context)->has_focus) {
1291         gboolean return_value;
1292 
1293         /* according to RH#859879, something bad could happen here. */
1294         g_object_add_weak_pointer((GObject *)*context, (gpointer *)context);
1295         /* some unref can happen here */
1296         g_signal_emit(*context, _signal_retrieve_surrounding_id, 0,
1297                       &return_value);
1298         if (*context)
1299             g_object_remove_weak_pointer((GObject *)*context,
1300                                          (gpointer *)context);
1301         else
1302             return;
1303         if (return_value) {
1304             (*context)->support_surrounding_text = TRUE;
1305             _fcitx_im_context_set_capability(*context, FALSE);
1306         } else {
1307             (*context)->support_surrounding_text = FALSE;
1308             _fcitx_im_context_set_capability(*context, FALSE);
1309         }
1310     }
1311 }
1312 
_update_auto_repeat_state(FcitxIMContext * context,GdkEvent * event)1313 guint _update_auto_repeat_state(FcitxIMContext *context, GdkEvent *event) {
1314     // GDK calls to XkbSetDetectableAutoRepeat by default, so normal there will
1315     // be no key release. But it might be also override by the application
1316     // itself.
1317     bool is_auto_repeat = false;
1318     if (gdk_event_get_event_type(event) == GDK_KEY_RELEASE) {
1319         // Always mark key release as non auto repeat, because we don't know if
1320         // it is real release.
1321         is_auto_repeat = false;
1322     } else {
1323         // If timestamp is same as last release
1324         if (context->last_is_release) {
1325             if (context->time && context->time == gdk_event_get_time(event) &&
1326                 context->last_key_code == gdk_key_event_get_keycode(event)) {
1327                 is_auto_repeat = true;
1328             }
1329         } else {
1330             if (context->last_key_code == gdk_key_event_get_keycode(event)) {
1331                 is_auto_repeat = true;
1332             }
1333         }
1334     }
1335 
1336     context->last_key_code = gdk_key_event_get_keycode(event);
1337     context->last_is_release =
1338         gdk_event_get_event_type(event) == GDK_KEY_RELEASE;
1339     context->time = gdk_event_get_time(event);
1340     auto state = static_cast<uint32_t>(gdk_event_get_modifier_state(event));
1341     if (is_auto_repeat) {
1342         // KeyState::Repeat
1343         state |= (1u << 31);
1344     }
1345     return state;
1346 }
1347 
_fcitx_im_context_input_purpose_changed_cb(GObject * gobject,GParamSpec *,gpointer)1348 void _fcitx_im_context_input_purpose_changed_cb(GObject *gobject, GParamSpec *,
1349                                                 gpointer) {
1350     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(gobject);
1351 
1352     GtkInputPurpose purpose;
1353     g_object_get(gobject, "input-purpose", &purpose, NULL);
1354 
1355     fcitxcontext->capability_from_toolkit &= ~purpose_related_capability;
1356 
1357 #define CASE_PURPOSE(_PURPOSE, _CAPABILITY)                                    \
1358     case _PURPOSE:                                                             \
1359         fcitxcontext->capability_from_toolkit |= (guint64)_CAPABILITY;         \
1360         break;
1361 
1362     switch (purpose) {
1363         CASE_PURPOSE(GTK_INPUT_PURPOSE_ALPHA, fcitx::FcitxCapabilityFlag_Alpha)
1364         CASE_PURPOSE(GTK_INPUT_PURPOSE_DIGITS,
1365                      fcitx::FcitxCapabilityFlag_Digit);
1366         CASE_PURPOSE(GTK_INPUT_PURPOSE_NUMBER,
1367                      fcitx::FcitxCapabilityFlag_Number)
1368         CASE_PURPOSE(GTK_INPUT_PURPOSE_PHONE,
1369                      fcitx::FcitxCapabilityFlag_Dialable)
1370         CASE_PURPOSE(GTK_INPUT_PURPOSE_URL, fcitx::FcitxCapabilityFlag_Url)
1371         CASE_PURPOSE(GTK_INPUT_PURPOSE_EMAIL, fcitx::FcitxCapabilityFlag_Email)
1372         CASE_PURPOSE(GTK_INPUT_PURPOSE_NAME, fcitx::FcitxCapabilityFlag_Name)
1373         CASE_PURPOSE(GTK_INPUT_PURPOSE_PASSWORD,
1374                      fcitx::FcitxCapabilityFlag_Password)
1375         CASE_PURPOSE(GTK_INPUT_PURPOSE_PIN,
1376                      (guint64)fcitx::FcitxCapabilityFlag_Password |
1377                          (guint64)fcitx::FcitxCapabilityFlag_Digit)
1378     case GTK_INPUT_PURPOSE_FREE_FORM:
1379     default:
1380         break;
1381     }
1382 
1383     _fcitx_im_context_set_capability(fcitxcontext, FALSE);
1384 }
1385 
_fcitx_im_context_input_hints_changed_cb(GObject * gobject,GParamSpec *,gpointer)1386 void _fcitx_im_context_input_hints_changed_cb(GObject *gobject, GParamSpec *,
1387                                               gpointer) {
1388     FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(gobject);
1389 
1390     GtkInputHints hints;
1391     g_object_get(gobject, "input-hints", &hints, NULL);
1392 
1393     fcitxcontext->capability_from_toolkit &= ~hints_related_capability;
1394 
1395 #define CHECK_HINTS(_HINTS, _CAPABILITY)                                       \
1396     if (hints & _HINTS)                                                        \
1397         fcitxcontext->capability_from_toolkit |= (guint64)_CAPABILITY;
1398 
1399     CHECK_HINTS(GTK_INPUT_HINT_SPELLCHECK,
1400                 fcitx::FcitxCapabilityFlag_SpellCheck)
1401     CHECK_HINTS(GTK_INPUT_HINT_NO_SPELLCHECK,
1402                 fcitx::FcitxCapabilityFlag_NoSpellCheck);
1403     CHECK_HINTS(GTK_INPUT_HINT_WORD_COMPLETION,
1404                 fcitx::FcitxCapabilityFlag_WordCompletion)
1405     CHECK_HINTS(GTK_INPUT_HINT_LOWERCASE, fcitx::FcitxCapabilityFlag_Lowercase)
1406     CHECK_HINTS(GTK_INPUT_HINT_UPPERCASE_CHARS,
1407                 fcitx::FcitxCapabilityFlag_Uppercase)
1408     CHECK_HINTS(GTK_INPUT_HINT_UPPERCASE_WORDS,
1409                 fcitx::FcitxCapabilityFlag_UppercaseWords)
1410     CHECK_HINTS(GTK_INPUT_HINT_UPPERCASE_SENTENCES,
1411                 fcitx::FcitxCapabilityFlag_UppwercaseSentences)
1412     CHECK_HINTS(GTK_INPUT_HINT_INHIBIT_OSK,
1413                 fcitx::FcitxCapabilityFlag_NoOnScreenKeyboard)
1414 
1415     _fcitx_im_context_set_capability(fcitxcontext, FALSE);
1416 }
1417 }
1418 
1419 // kate: indent-mode cstyle; replace-tabs on;
1420