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