1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2000 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include <gdk/gdk.h>
21
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include "gtkprivate.h"
26 #include "gtkaccelgroup.h"
27 #include "gtkimcontextsimple.h"
28 #include "gtksettings.h"
29 #include "gtkwidget.h"
30 #include "gtkdebug.h"
31 #include "gtkintl.h"
32 #include "gtkcomposetable.h"
33 #include "gtkimmoduleprivate.h"
34
35 #include "gtkimcontextsimpleseqs.h"
36 #include "gdk/gdkprofilerprivate.h"
37
38 /**
39 * GtkIMContextSimple:
40 *
41 * `GtkIMContextSimple` is an input method supporting table-based input methods.
42 *
43 * ## Compose sequences
44 *
45 * `GtkIMContextSimple` reads compose sequences from the first of the
46 * following files that is found: ~/.config/gtk-4.0/Compose, ~/.XCompose,
47 * /usr/share/X11/locale/$locale/Compose (for locales that have a nontrivial
48 * Compose file). The syntax of these files is described in the Compose(5)
49 * manual page.
50 *
51 * If none of these files is found, `GtkIMContextSimple` uses a built-in table
52 * of compose sequences that is derived from the X11 Compose files.
53 *
54 * Note that compose sequences typically start with the Compose_key, which is
55 * often not available as a dedicated key on keyboards. Keyboard layouts may
56 * map this keysym to other keys, such as the right Control key.
57 *
58 * ## Unicode characters
59 *
60 * `GtkIMContextSimple` also supports numeric entry of Unicode characters
61 * by typing <kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>u</kbd>, followed by a
62 * hexadecimal Unicode codepoint.
63 *
64 * For example,
65 *
66 * Ctrl-Shift-u 1 2 3 Enter
67 *
68 * yields U+0123 LATIN SMALL LETTER G WITH CEDILLA, i.e. ģ.
69 *
70 * ## Dead keys
71 *
72 * `GtkIMContextSimple` supports dead keys. For example, typing
73 *
74 * dead_acute a
75 *
76 * yields U+00E! LATIN SMALL LETTER_A WITH ACUTE, i.e. á. Note that this
77 * depends on the keyboard layout including dead keys.
78 */
79
80 struct _GtkIMContextSimplePrivate
81 {
82 guint *compose_buffer;
83 int compose_buffer_len;
84 GString *tentative_match;
85 int tentative_match_len;
86
87 guint in_hex_sequence : 1;
88 guint in_compose_sequence : 1;
89 guint modifiers_dropped : 1;
90 };
91
92 #include "gtk/compose/gtkcomposedata.h"
93
94 GtkComposeTable builtin_compose_table = {
95 NULL,
96 NULL,
97 MAX_SEQ_LEN,
98 N_INDEX_SIZE,
99 DATA_SIZE,
100 N_CHARS,
101 0
102 };
103
104 static void
init_builtin_table(void)105 init_builtin_table (void)
106 {
107 GBytes *bytes;
108
109 bytes = g_resources_lookup_data ("/org/gtk/libgtk/compose/sequences", 0, NULL);
110 builtin_compose_table.data = (guint16 *) g_bytes_get_data (bytes, NULL);
111 g_bytes_unref (bytes);
112
113 bytes = g_resources_lookup_data ("/org/gtk/libgtk/compose/chars", 0, NULL);
114 builtin_compose_table.char_data = (char *) g_bytes_get_data (bytes, NULL);
115 g_bytes_unref (bytes);
116 }
117
118 G_LOCK_DEFINE_STATIC (global_tables);
119 static GSList *global_tables;
120
121 static const guint gtk_compose_ignore[] = {
122 0, /* Yes, XKB will send us key press events with NoSymbol :( */
123 GDK_KEY_Overlay1_Enable,
124 GDK_KEY_Overlay2_Enable,
125 GDK_KEY_Shift_L,
126 GDK_KEY_Shift_R,
127 GDK_KEY_Control_L,
128 GDK_KEY_Control_R,
129 GDK_KEY_Caps_Lock,
130 GDK_KEY_Shift_Lock,
131 GDK_KEY_Meta_L,
132 GDK_KEY_Meta_R,
133 GDK_KEY_Alt_L,
134 GDK_KEY_Alt_R,
135 GDK_KEY_Super_L,
136 GDK_KEY_Super_R,
137 GDK_KEY_Hyper_L,
138 GDK_KEY_Hyper_R,
139 GDK_KEY_Mode_switch,
140 GDK_KEY_ISO_Level3_Shift,
141 GDK_KEY_ISO_Level3_Latch,
142 GDK_KEY_ISO_Level5_Shift,
143 GDK_KEY_ISO_Level5_Latch
144 };
145
146 static void gtk_im_context_simple_finalize (GObject *obj);
147 static gboolean gtk_im_context_simple_filter_keypress (GtkIMContext *context,
148 GdkEvent *key);
149 static void gtk_im_context_simple_reset (GtkIMContext *context);
150 static void gtk_im_context_simple_get_preedit_string (GtkIMContext *context,
151 char **str,
152 PangoAttrList **attrs,
153 int *cursor_pos);
154
155 static void init_compose_table_async (GCancellable *cancellable,
156 GAsyncReadyCallback callback,
157 gpointer user_data);
158
159 G_DEFINE_TYPE_WITH_CODE (GtkIMContextSimple, gtk_im_context_simple, GTK_TYPE_IM_CONTEXT,
160 G_ADD_PRIVATE (GtkIMContextSimple)
161 gtk_im_module_ensure_extension_point ();
162 g_io_extension_point_implement (GTK_IM_MODULE_EXTENSION_POINT_NAME,
163 g_define_type_id,
164 "gtk-im-context-simple",
165 G_MININT))
166
167 static void
gtk_im_context_simple_class_init(GtkIMContextSimpleClass * class)168 gtk_im_context_simple_class_init (GtkIMContextSimpleClass *class)
169 {
170 GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
171 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
172
173 im_context_class->filter_keypress = gtk_im_context_simple_filter_keypress;
174 im_context_class->reset = gtk_im_context_simple_reset;
175 im_context_class->get_preedit_string = gtk_im_context_simple_get_preedit_string;
176 gobject_class->finalize = gtk_im_context_simple_finalize;
177
178 init_builtin_table ();
179 init_compose_table_async (NULL, NULL, NULL);
180 }
181
182 static char *
get_x11_compose_file_dir(void)183 get_x11_compose_file_dir (void)
184 {
185 char * compose_file_dir;
186
187 #if defined (X11_DATA_PREFIX)
188 compose_file_dir = g_strdup (X11_DATA_PREFIX "/share/X11/locale");
189 #else
190 compose_file_dir = g_build_filename (_gtk_get_datadir (), "X11", "locale", NULL);
191 #endif
192
193 return compose_file_dir;
194 }
195
196 static int
gtk_compose_table_find(gconstpointer data1,gconstpointer data2)197 gtk_compose_table_find (gconstpointer data1,
198 gconstpointer data2)
199 {
200 const GtkComposeTable *compose_table = (const GtkComposeTable *) data1;
201 guint32 hash = (guint32) GPOINTER_TO_INT (data2);
202 return compose_table->id != hash;
203 }
204
205 static gboolean
add_compose_table_from_file(const char * compose_file)206 add_compose_table_from_file (const char *compose_file)
207 {
208 guint hash;
209 gboolean ret = FALSE;
210
211 G_LOCK (global_tables);
212
213 hash = g_str_hash (compose_file);
214 if (!g_slist_find_custom (global_tables, GINT_TO_POINTER (hash), gtk_compose_table_find))
215 {
216 GtkComposeTable *table;
217
218 table = gtk_compose_table_new_with_file (compose_file);
219
220 if (table)
221 {
222 ret = TRUE;
223 global_tables = g_slist_prepend (global_tables, table);
224 }
225 }
226
227 G_UNLOCK (global_tables);
228
229 return ret;
230 }
231
232 static void
add_builtin_compose_table(void)233 add_builtin_compose_table (void)
234 {
235 G_LOCK (global_tables);
236
237 global_tables = g_slist_prepend (global_tables, &builtin_compose_table);
238
239 G_UNLOCK (global_tables);
240 }
241
242 static void
add_compose_table_from_data(guint16 * data,int max_seq_len,int n_seqs)243 add_compose_table_from_data (guint16 *data,
244 int max_seq_len,
245 int n_seqs)
246 {
247 guint hash;
248
249 G_LOCK (global_tables);
250
251 hash = gtk_compose_table_data_hash (data, max_seq_len, n_seqs);
252 if (!g_slist_find_custom (global_tables, GINT_TO_POINTER (hash), gtk_compose_table_find))
253 {
254 GtkComposeTable *table;
255
256 table = gtk_compose_table_new_with_data (data, max_seq_len, n_seqs);
257
258 if (table)
259 global_tables = g_slist_prepend (global_tables, table);
260 }
261
262 G_UNLOCK (global_tables);
263 }
264
265 static void
gtk_im_context_simple_init_compose_table(void)266 gtk_im_context_simple_init_compose_table (void)
267 {
268 char *path = NULL;
269 const char *home;
270 const char *locale;
271 char **langs = NULL;
272 char **lang = NULL;
273 gboolean added;
274 const char * const sys_langs[] = { "el_gr", "fi_fi", "pt_br", NULL };
275 const char * const *sys_lang = NULL;
276 char *x11_compose_file_dir = get_x11_compose_file_dir ();
277
278 path = g_build_filename (g_get_user_config_dir (), "gtk-4.0", "Compose", NULL);
279 if (g_file_test (path, G_FILE_TEST_EXISTS))
280 {
281 added = add_compose_table_from_file (path);
282 g_free (path);
283 if (added)
284 return;
285 }
286 g_clear_pointer (&path, g_free);
287
288 home = g_get_home_dir ();
289 if (home == NULL)
290 return;
291
292 path = g_build_filename (home, ".XCompose", NULL);
293 if (g_file_test (path, G_FILE_TEST_EXISTS))
294 {
295 added = add_compose_table_from_file (path);
296 g_free (path);
297 if (added)
298 return;
299 }
300 g_clear_pointer (&path, g_free);
301
302 locale = g_getenv ("LC_CTYPE");
303 if (locale == NULL)
304 locale = g_getenv ("LANG");
305 if (locale == NULL)
306 locale = "C";
307
308 /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=751826 */
309 langs = g_get_locale_variants (locale);
310
311 for (lang = langs; *lang; lang++)
312 {
313 if (g_str_has_prefix (*lang, "en_US"))
314 break;
315 if (**lang == 'C')
316 break;
317
318 /* Other languages just include en_us compose table. */
319 for (sys_lang = sys_langs; *sys_lang; sys_lang++)
320 {
321 if (g_ascii_strncasecmp (*lang, *sys_lang, strlen (*sys_lang)) == 0)
322 {
323 path = g_build_filename (x11_compose_file_dir, *lang, "Compose", NULL);
324 break;
325 }
326 }
327
328 if (path == NULL)
329 continue;
330
331 if (g_file_test (path, G_FILE_TEST_EXISTS))
332 break;
333 g_clear_pointer (&path, g_free);
334 }
335
336 g_free (x11_compose_file_dir);
337 g_strfreev (langs);
338
339 if (path != NULL)
340 {
341 added = add_compose_table_from_file (path);
342 g_free (path);
343 }
344 if (added)
345 return;
346
347 add_builtin_compose_table ();
348 }
349
350 static void
init_compose_table_thread_cb(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)351 init_compose_table_thread_cb (GTask *task,
352 gpointer source_object,
353 gpointer task_data,
354 GCancellable *cancellable)
355 {
356 gint64 before G_GNUC_UNUSED;
357
358 before = GDK_PROFILER_CURRENT_TIME;
359
360 if (g_task_return_error_if_cancelled (task))
361 return;
362
363 gtk_im_context_simple_init_compose_table ();
364
365 gdk_profiler_end_mark (before, "im compose table load (thread)", NULL);
366 }
367
368 static void
init_compose_table_async(GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)369 init_compose_table_async (GCancellable *cancellable,
370 GAsyncReadyCallback callback,
371 gpointer user_data)
372 {
373 GTask *task = g_task_new (NULL, cancellable, callback, user_data);
374 g_task_set_source_tag (task, init_compose_table_async);
375 g_task_run_in_thread (task, init_compose_table_thread_cb);
376 g_object_unref (task);
377 }
378
379 static void
gtk_im_context_simple_init(GtkIMContextSimple * context_simple)380 gtk_im_context_simple_init (GtkIMContextSimple *context_simple)
381 {
382 GtkIMContextSimplePrivate *priv;
383
384 priv = context_simple->priv = gtk_im_context_simple_get_instance_private (context_simple);
385
386 priv->compose_buffer_len = builtin_compose_table.max_seq_len + 1;
387 priv->compose_buffer = g_new0 (guint, priv->compose_buffer_len);
388 priv->tentative_match = g_string_new ("");
389 priv->tentative_match_len = 0;
390 }
391
392 static void
gtk_im_context_simple_finalize(GObject * obj)393 gtk_im_context_simple_finalize (GObject *obj)
394 {
395 GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (obj);
396 GtkIMContextSimplePrivate *priv = context_simple->priv;;
397
398 g_free (priv->compose_buffer);
399 g_string_free (priv->tentative_match, TRUE);
400
401 G_OBJECT_CLASS (gtk_im_context_simple_parent_class)->finalize (obj);
402 }
403
404 /**
405 * gtk_im_context_simple_new:
406 *
407 * Creates a new `GtkIMContextSimple`.
408 *
409 * Returns: a new `GtkIMContextSimple`
410 */
411 GtkIMContext *
gtk_im_context_simple_new(void)412 gtk_im_context_simple_new (void)
413 {
414 return g_object_new (GTK_TYPE_IM_CONTEXT_SIMPLE, NULL);
415 }
416
417 static void
gtk_im_context_simple_commit_string(GtkIMContextSimple * context_simple,const char * str)418 gtk_im_context_simple_commit_string (GtkIMContextSimple *context_simple,
419 const char *str)
420 {
421 GtkIMContextSimplePrivate *priv = context_simple->priv;
422
423 if (priv->in_hex_sequence ||
424 priv->tentative_match_len > 0 ||
425 priv->compose_buffer[0] != 0)
426 {
427 g_string_set_size (priv->tentative_match, 0);
428 priv->tentative_match_len = 0;
429 priv->in_hex_sequence = FALSE;
430 priv->in_compose_sequence = FALSE;
431 priv->compose_buffer[0] = 0;
432
433 g_signal_emit_by_name (context_simple, "preedit-changed");
434 g_signal_emit_by_name (context_simple, "preedit-end");
435 }
436
437 g_signal_emit_by_name (context_simple, "commit", str);
438 }
439
440 static void
gtk_im_context_simple_commit_char(GtkIMContextSimple * context_simple,gunichar ch)441 gtk_im_context_simple_commit_char (GtkIMContextSimple *context_simple,
442 gunichar ch)
443 {
444 char buf[8] = { 0, };
445
446 g_unichar_to_utf8 (ch, buf);
447
448 gtk_im_context_simple_commit_string (context_simple, buf);
449 }
450
451 /* In addition to the table-driven sequences, we allow Unicode hex
452 * codes to be entered. The method chosen here is similar to the
453 * one recommended in ISO 14755, but not exactly the same, since we
454 * don’t want to steal 16 valuable key combinations.
455 *
456 * A hex Unicode sequence must be started with Ctrl-Shift-U, followed
457 * by a sequence of hex digits entered with Ctrl-Shift still held.
458 * Releasing one of the modifiers or pressing space while the modifiers
459 * are still held commits the character. It is possible to erase
460 * digits using backspace.
461 *
462 * As an extension to the above, we also allow to start the sequence
463 * with Ctrl-Shift-U, then release the modifiers before typing any
464 * digits, and enter the digits without modifiers.
465 */
466
467 static gboolean
check_hex(GtkIMContextSimple * context_simple,int n_compose)468 check_hex (GtkIMContextSimple *context_simple,
469 int n_compose)
470 {
471 GtkIMContextSimplePrivate *priv = context_simple->priv;
472 /* See if this is a hex sequence, return TRUE if so */
473 int i;
474 GString *str;
475 gulong n;
476 char *nptr = NULL;
477 char buf[7];
478
479 g_string_set_size (priv->tentative_match, 0);
480 priv->tentative_match_len = 0;
481
482 str = g_string_new (NULL);
483
484 i = 0;
485 while (i < n_compose)
486 {
487 gunichar ch;
488
489 ch = gdk_keyval_to_unicode (priv->compose_buffer[i]);
490
491 if (ch == 0)
492 return FALSE;
493
494 if (!g_unichar_isxdigit (ch))
495 return FALSE;
496
497 buf[g_unichar_to_utf8 (ch, buf)] = '\0';
498
499 g_string_append (str, buf);
500
501 ++i;
502 }
503
504 n = strtoul (str->str, &nptr, 16);
505
506 /* If strtoul fails it probably means non-latin digits were used;
507 * we should in principle handle that, but we probably don't.
508 */
509 if (nptr - str->str < str->len)
510 {
511 g_string_free (str, TRUE);
512 return FALSE;
513 }
514 else
515 g_string_free (str, TRUE);
516
517 if (g_unichar_validate (n))
518 {
519 g_string_set_size (priv->tentative_match, 0);
520 g_string_append_unichar (priv->tentative_match, n);
521 priv->tentative_match_len = n_compose;
522 }
523
524 return TRUE;
525 }
526
527 static void
beep_surface(GdkSurface * surface)528 beep_surface (GdkSurface *surface)
529 {
530 GdkDisplay *display = gdk_surface_get_display (surface);
531 gboolean beep;
532
533 g_object_get (gtk_settings_get_for_display (display),
534 "gtk-error-bell", &beep,
535 NULL);
536
537 if (beep)
538 gdk_surface_beep (surface);
539 }
540
541 static inline gboolean
is_dead_key(guint keysym)542 is_dead_key (guint keysym)
543 {
544 return GDK_KEY_dead_grave <= keysym && keysym <= GDK_KEY_dead_greek;
545 }
546
547 static void
append_dead_key(GString * string,guint keysym)548 append_dead_key (GString *string,
549 guint keysym)
550 {
551 /* Sadly, not all the dead keysyms have spacing mark equivalents
552 * in Unicode. For those that don't, we use NBSP + the non-spacing
553 * mark as an approximation.
554 */
555 switch (keysym)
556 {
557 #define CASE(keysym, unicode, sp) \
558 case GDK_KEY_dead_##keysym: \
559 if (sp) \
560 g_string_append_unichar (string, 0xA0); \
561 g_string_append_unichar (string, unicode); \
562 break;
563
564 CASE (grave, 0x60, 0);
565 CASE (acute, 0xb4, 0);
566 CASE (circumflex, 0x5e, 0);
567 CASE (tilde, 0x7e, 0);
568 CASE (macron, 0xaf, 0);
569 CASE (breve, 0x2d8, 0);
570 CASE (abovedot, 0x307, 1);
571 CASE (diaeresis, 0xa8, 0);
572 CASE (abovering, 0x2da, 0);
573 CASE (hook, 0x2c0, 0);
574 CASE (doubleacute, 0x2dd, 0);
575 CASE (caron, 0x2c7, 0);
576 CASE (cedilla, 0xb8, 0);
577 CASE (ogonek, 0x2db, 0);
578 CASE (iota, 0x37a, 0);
579 CASE (voiced_sound, 0x3099, 1);
580 CASE (semivoiced_sound, 0x309a, 1);
581 CASE (belowdot, 0x323, 1);
582 CASE (horn, 0x31b, 1);
583 CASE (stroke, 0x335, 1);
584 CASE (abovecomma, 0x2bc, 0);
585 CASE (abovereversedcomma, 0x2bd, 1);
586 CASE (doublegrave, 0x30f, 1);
587 CASE (belowring, 0x2f3, 0);
588 CASE (belowmacron, 0x2cd, 0);
589 CASE (belowcircumflex, 0x32d, 1);
590 CASE (belowtilde, 0x330, 1);
591 CASE (belowbreve, 0x32e, 1);
592 CASE (belowdiaeresis, 0x324, 1);
593 CASE (invertedbreve, 0x32f, 1);
594 CASE (belowcomma, 0x326, 1);
595 CASE (lowline, 0x5f, 0);
596 CASE (aboveverticalline, 0x2c8, 0);
597 CASE (belowverticalline, 0x2cc, 0);
598 CASE (longsolidusoverlay, 0x338, 1);
599 CASE (a, 0x363, 1);
600 CASE (A, 0x363, 1);
601 CASE (e, 0x364, 1);
602 CASE (E, 0x364, 1);
603 CASE (i, 0x365, 1);
604 CASE (I, 0x365, 1);
605 CASE (o, 0x366, 1);
606 CASE (O, 0x366, 1);
607 CASE (u, 0x367, 1);
608 CASE (U, 0x367, 1);
609 CASE (small_schwa, 0x1dea, 1);
610 CASE (capital_schwa, 0x1dea, 1);
611 #undef CASE
612 default:
613 g_string_append_unichar (string, gdk_keyval_to_unicode (keysym));
614 }
615 }
616
617 static gboolean
no_sequence_matches(GtkIMContextSimple * context_simple,int n_compose,GdkEvent * event)618 no_sequence_matches (GtkIMContextSimple *context_simple,
619 int n_compose,
620 GdkEvent *event)
621 {
622 GtkIMContextSimplePrivate *priv = context_simple->priv;
623 GtkIMContext *context;
624 gunichar ch;
625 guint keyval;
626
627 context = GTK_IM_CONTEXT (context_simple);
628
629 priv->in_compose_sequence = FALSE;
630
631 /* No compose sequences found, check first if we have a partial
632 * match pending.
633 */
634 if (priv->tentative_match_len > 0)
635 {
636 int len = priv->tentative_match_len;
637 int i;
638 guint *compose_buffer;
639 char *str;
640
641 compose_buffer = alloca (sizeof (guint) * priv->compose_buffer_len);
642
643 memcpy (compose_buffer, priv->compose_buffer, sizeof (guint) * priv->compose_buffer_len);
644
645 str = g_strdup (priv->tentative_match->str);
646 gtk_im_context_simple_commit_string (context_simple, str);
647 g_free (str);
648
649 for (i = 0; i < n_compose - len - 1; i++)
650 {
651 GdkTranslatedKey translated;
652 translated.keyval = compose_buffer[len + i];
653 translated.consumed = 0;
654 translated.layout = 0;
655 translated.level = 0;
656 GdkEvent *tmp_event = gdk_key_event_new (GDK_KEY_PRESS,
657 gdk_event_get_surface (event),
658 gdk_event_get_device (event),
659 gdk_event_get_time (event),
660 compose_buffer[len + i],
661 gdk_event_get_modifier_state (event),
662 FALSE,
663 &translated,
664 &translated);
665
666 gtk_im_context_filter_keypress (context, tmp_event);
667 gdk_event_unref (tmp_event);
668 }
669
670 return gtk_im_context_filter_keypress (context, event);
671 }
672 else
673 {
674 int i;
675
676 for (i = 0; i < n_compose && is_dead_key (priv->compose_buffer[i]); i++)
677 ;
678
679 if (n_compose > 1 && i >= n_compose - 1)
680 {
681 GString *s;
682
683 s = g_string_new ("");
684
685 if (i == n_compose - 1)
686 {
687 /* dead keys are never *really* dead */
688 for (int j = 0; j < i; j++)
689 append_dead_key (s, priv->compose_buffer[j]);
690
691 ch = gdk_keyval_to_unicode (priv->compose_buffer[i]);
692 if (ch != 0 && ch != ' ' && !g_unichar_iscntrl (ch))
693 g_string_append_unichar (s, ch);
694
695 gtk_im_context_simple_commit_string (context_simple, s->str);
696 }
697 else
698 {
699 append_dead_key (s, priv->compose_buffer[0]);
700 gtk_im_context_simple_commit_string (context_simple, s->str);
701
702 for (i = 1; i < n_compose; i++)
703 priv->compose_buffer[i - 1] = priv->compose_buffer[i];
704 priv->compose_buffer[n_compose - 1] = 0;
705
706 priv->in_compose_sequence = TRUE;
707
708 g_signal_emit_by_name (context, "preedit-start");
709 g_signal_emit_by_name (context, "preedit-changed");
710 }
711
712 g_string_free (s, TRUE);
713
714 return TRUE;
715 }
716
717 priv->compose_buffer[0] = 0;
718 if (n_compose > 1) /* Invalid sequence */
719 {
720 beep_surface (gdk_event_get_surface (event));
721 g_signal_emit_by_name (context, "preedit-changed");
722 g_signal_emit_by_name (context, "preedit-end");
723 return TRUE;
724 }
725
726 keyval = gdk_key_event_get_keyval (event);
727 ch = gdk_keyval_to_unicode (keyval);
728 if (ch != 0 && !g_unichar_iscntrl (ch))
729 {
730 gtk_im_context_simple_commit_char (context_simple, ch);
731 return TRUE;
732 }
733 else
734 return FALSE;
735 }
736 return FALSE;
737 }
738
739 static gboolean
is_hex_keyval(guint keyval)740 is_hex_keyval (guint keyval)
741 {
742 gunichar ch = gdk_keyval_to_unicode (keyval);
743
744 return g_unichar_isxdigit (ch);
745 }
746
747 static guint
canonical_hex_keyval(GdkEvent * event)748 canonical_hex_keyval (GdkEvent *event)
749 {
750 guint keyval, event_keyval;
751 guint *keyvals = NULL;
752 int n_vals = 0;
753 int i;
754
755 event_keyval = gdk_key_event_get_keyval (event);
756
757 /* See if the keyval is already a hex digit */
758 if (is_hex_keyval (event_keyval))
759 return event_keyval;
760
761 /* See if this key would have generated a hex keyval in
762 * any other state, and return that hex keyval if so
763 */
764 gdk_display_map_keycode (gdk_event_get_display (event),
765 gdk_key_event_get_keycode (event),
766 NULL,
767 &keyvals, &n_vals);
768
769 keyval = 0;
770 i = 0;
771 while (i < n_vals)
772 {
773 if (is_hex_keyval (keyvals[i]))
774 {
775 keyval = keyvals[i];
776 break;
777 }
778
779 ++i;
780 }
781
782 g_free (keyvals);
783
784 if (keyval)
785 return keyval;
786 else
787 /* No way to make it a hex digit
788 */
789 return 0;
790 }
791
792 static gboolean
gtk_im_context_simple_filter_keypress(GtkIMContext * context,GdkEvent * event)793 gtk_im_context_simple_filter_keypress (GtkIMContext *context,
794 GdkEvent *event)
795 {
796 GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context);
797 GtkIMContextSimplePrivate *priv = context_simple->priv;
798 GdkSurface *surface = gdk_event_get_surface ((GdkEvent *) event);
799 GSList *tmp_list;
800 int n_compose = 0;
801 GdkModifierType hex_mod_mask;
802 gboolean have_hex_mods;
803 gboolean is_hex_start;
804 gboolean is_hex_end;
805 gboolean is_backspace;
806 gboolean is_escape;
807 guint hex_keyval;
808 int i;
809 gboolean compose_finish;
810 gboolean compose_match;
811 guint keyval, state;
812
813 while (priv->compose_buffer[n_compose] != 0 && n_compose < priv->compose_buffer_len)
814 n_compose++;
815
816 keyval = gdk_key_event_get_keyval (event);
817 state = gdk_event_get_modifier_state (event);
818
819 if (gdk_event_get_event_type (event) == GDK_KEY_RELEASE)
820 {
821 if (priv->in_hex_sequence &&
822 (keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R ||
823 keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R))
824 {
825 if (priv->tentative_match->len > 0)
826 {
827 char *str = g_strdup (priv->tentative_match->str);
828 gtk_im_context_simple_commit_string (context_simple, str);
829 g_free (str);
830
831 return TRUE;
832 }
833 else if (n_compose == 0)
834 {
835 priv->modifiers_dropped = TRUE;
836
837 return TRUE;
838 }
839 else if (priv->in_hex_sequence)
840 {
841 /* invalid hex sequence */
842 beep_surface (surface);
843
844 g_string_set_size (priv->tentative_match, 0);
845 priv->in_hex_sequence = FALSE;
846 priv->compose_buffer[0] = 0;
847
848 g_signal_emit_by_name (context_simple, "preedit-changed");
849 g_signal_emit_by_name (context_simple, "preedit-end");
850
851 return TRUE;
852 }
853 }
854
855 if (priv->in_hex_sequence || priv->in_compose_sequence)
856 return TRUE; /* Don't leak random key events during preedit */
857
858 return FALSE;
859 }
860
861 /* Ignore modifier key presses */
862 for (i = 0; i < G_N_ELEMENTS (gtk_compose_ignore); i++)
863 if (keyval == gtk_compose_ignore[i])
864 {
865 if (priv->in_hex_sequence || priv->in_compose_sequence)
866 return TRUE; /* Don't leak random key events during preedit */
867
868 return FALSE;
869 }
870
871 hex_mod_mask = GDK_CONTROL_MASK|GDK_SHIFT_MASK;
872
873 if (priv->in_hex_sequence && priv->modifiers_dropped)
874 have_hex_mods = TRUE;
875 else
876 have_hex_mods = (state & (hex_mod_mask)) == hex_mod_mask;
877 is_hex_start = keyval == GDK_KEY_U;
878 is_hex_end = (keyval == GDK_KEY_space ||
879 keyval == GDK_KEY_KP_Space ||
880 keyval == GDK_KEY_Return ||
881 keyval == GDK_KEY_ISO_Enter ||
882 keyval == GDK_KEY_KP_Enter);
883 is_backspace = keyval == GDK_KEY_BackSpace;
884 is_escape = keyval == GDK_KEY_Escape;
885 hex_keyval = canonical_hex_keyval (event);
886
887 /* If we are already in a non-hex sequence, or
888 * this keystroke is not hex modifiers + hex digit, don't filter
889 * key events with accelerator modifiers held down. We only treat
890 * Control and Alt as accel modifiers here, since Super, Hyper and
891 * Meta are often co-located with Mode_Switch, Multi_Key or
892 * ISO_Level3_Switch.
893 */
894 if (!have_hex_mods ||
895 (n_compose > 0 && !priv->in_hex_sequence) ||
896 (n_compose == 0 && !priv->in_hex_sequence && !is_hex_start) ||
897 (priv->in_hex_sequence && !hex_keyval &&
898 !is_hex_start && !is_hex_end && !is_escape && !is_backspace))
899 {
900 GdkModifierType no_text_input_mask;
901
902 no_text_input_mask = GDK_ALT_MASK|GDK_CONTROL_MASK;
903
904 if (priv->in_hex_sequence && priv->modifiers_dropped &&
905 (keyval == GDK_KEY_Return ||
906 keyval == GDK_KEY_ISO_Enter ||
907 keyval == GDK_KEY_KP_Enter))
908 {
909 return FALSE;
910 }
911
912 if (state & no_text_input_mask)
913 {
914 if (priv->in_hex_sequence || priv->in_compose_sequence)
915 return TRUE; /* Don't leak random key events during preedit */
916
917 return FALSE;
918 }
919 }
920
921 /* Handle backspace */
922 if (priv->in_hex_sequence && have_hex_mods && is_backspace)
923 {
924 if (n_compose > 0)
925 {
926 n_compose--;
927 priv->compose_buffer[n_compose] = 0;
928 check_hex (context_simple, n_compose);
929 }
930 else
931 {
932 priv->in_hex_sequence = FALSE;
933 }
934
935 g_signal_emit_by_name (context_simple, "preedit-changed");
936
937 if (!priv->in_hex_sequence)
938 g_signal_emit_by_name (context_simple, "preedit-end");
939
940 return TRUE;
941 }
942
943 if (!priv->in_hex_sequence && n_compose > 0 && is_backspace)
944 {
945 n_compose--;
946 priv->compose_buffer[n_compose] = 0;
947
948 g_signal_emit_by_name (context_simple, "preedit-changed");
949
950 if (n_compose == 0)
951 g_signal_emit_by_name (context_simple, "preedit-end");
952
953 return TRUE;
954 }
955
956 /* Check for hex sequence restart */
957 if (priv->in_hex_sequence && have_hex_mods && is_hex_start)
958 {
959 if (priv->tentative_match->len > 0)
960 {
961 char *str = g_strdup (priv->tentative_match->str);
962 gtk_im_context_simple_commit_string (context_simple, str);
963 g_free (str);
964 }
965 else
966 {
967 /* invalid hex sequence */
968 if (n_compose > 0)
969 beep_surface (surface);
970
971 g_string_set_size (priv->tentative_match, 0);
972 priv->in_hex_sequence = FALSE;
973 priv->compose_buffer[0] = 0;
974 }
975 }
976
977 /* Check for hex sequence start */
978 if (!priv->in_hex_sequence && have_hex_mods && is_hex_start)
979 {
980 priv->compose_buffer[0] = 0;
981 priv->in_hex_sequence = TRUE;
982 priv->modifiers_dropped = FALSE;
983 g_string_set_size (priv->tentative_match, 0);
984
985 g_signal_emit_by_name (context_simple, "preedit-start");
986 g_signal_emit_by_name (context_simple, "preedit-changed");
987
988 return TRUE;
989 }
990
991 if (is_escape)
992 {
993 if (priv->in_hex_sequence || priv->in_compose_sequence)
994 {
995 gtk_im_context_simple_reset (context);
996 return TRUE;
997 }
998
999 return FALSE;
1000 }
1001
1002 if (priv->in_hex_sequence)
1003 {
1004 if (hex_keyval && n_compose < 6)
1005 priv->compose_buffer[n_compose++] = hex_keyval;
1006 else if (!is_hex_end)
1007 {
1008 /* non-hex character in hex sequence, or sequence too long */
1009 beep_surface (surface);
1010 return TRUE;
1011 }
1012 }
1013 else
1014 {
1015 if (n_compose + 1 == priv->compose_buffer_len)
1016 {
1017 priv->compose_buffer_len += 1;
1018 priv->compose_buffer = g_renew (guint, priv->compose_buffer, priv->compose_buffer_len);
1019 }
1020
1021 priv->compose_buffer[n_compose++] = keyval;
1022 }
1023
1024 priv->compose_buffer[n_compose] = 0;
1025
1026 if (priv->in_hex_sequence)
1027 {
1028 /* If the modifiers are still held down, consider the sequence again */
1029 if (have_hex_mods)
1030 {
1031 /* space or return ends the sequence, and we eat the key */
1032 if (n_compose > 0 && is_hex_end)
1033 {
1034 if (priv->tentative_match->len > 0)
1035 {
1036 char *str = g_strdup (priv->tentative_match->str);
1037 gtk_im_context_simple_commit_string (context_simple, str);
1038 g_free (str);
1039
1040 return TRUE;
1041 }
1042 else
1043 {
1044 /* invalid hex sequence */
1045 beep_surface (surface);
1046
1047 g_string_set_size (priv->tentative_match, 0);
1048 priv->in_hex_sequence = FALSE;
1049 priv->compose_buffer[0] = 0;
1050 }
1051 }
1052 else if (!check_hex (context_simple, n_compose))
1053 beep_surface (surface);
1054
1055 g_signal_emit_by_name (context_simple, "preedit-changed");
1056
1057 if (!priv->in_hex_sequence)
1058 g_signal_emit_by_name (context_simple, "preedit-end");
1059
1060 return TRUE;
1061 }
1062 }
1063 else /* Then, check for compose sequences */
1064 {
1065 gboolean success = FALSE;
1066 int prefix = 0;
1067 GString *output;
1068
1069 output = g_string_new ("");
1070
1071 G_LOCK (global_tables);
1072
1073 tmp_list = global_tables;
1074 while (tmp_list)
1075 {
1076 if (gtk_compose_table_check ((GtkComposeTable *)tmp_list->data,
1077 priv->compose_buffer, n_compose,
1078 &compose_finish, &compose_match,
1079 output))
1080 {
1081 if (!priv->in_compose_sequence)
1082 {
1083 priv->in_compose_sequence = TRUE;
1084 g_signal_emit_by_name (context_simple, "preedit-start");
1085 }
1086
1087 if (compose_finish)
1088 {
1089 if (compose_match)
1090 gtk_im_context_simple_commit_string (context_simple, output->str);
1091 }
1092 else
1093 {
1094 if (compose_match)
1095 {
1096 g_string_assign (priv->tentative_match, output->str);
1097 priv->tentative_match_len = n_compose;
1098 }
1099 g_signal_emit_by_name (context_simple, "preedit-changed");
1100 }
1101
1102 success = TRUE;
1103 break;
1104 }
1105 else
1106 {
1107 int table_prefix;
1108
1109 gtk_compose_table_get_prefix ((GtkComposeTable *)tmp_list->data,
1110 priv->compose_buffer, n_compose,
1111 &table_prefix);
1112
1113 prefix = MAX (prefix, table_prefix);
1114 }
1115
1116 tmp_list = tmp_list->next;
1117 }
1118
1119 G_UNLOCK (global_tables);
1120
1121 if (success)
1122 {
1123 g_string_free (output, TRUE);
1124 return TRUE;
1125 }
1126
1127 if (gtk_check_algorithmically (priv->compose_buffer, n_compose, output))
1128 {
1129 if (!priv->in_compose_sequence)
1130 {
1131 priv->in_compose_sequence = TRUE;
1132 g_signal_emit_by_name (context_simple, "preedit-start");
1133 }
1134
1135 if (output->len > 0)
1136 gtk_im_context_simple_commit_string (context_simple, output->str);
1137 else
1138 g_signal_emit_by_name (context_simple, "preedit-changed");
1139
1140 g_string_free (output, TRUE);
1141
1142 return TRUE;
1143 }
1144
1145 g_string_free (output, TRUE);
1146
1147 /* If we get here, no Compose sequence matched.
1148 * Only beep if we were in a sequence before.
1149 */
1150 if (prefix > 0)
1151 {
1152 for (i = prefix; i < n_compose; i++)
1153 priv->compose_buffer[i] = 0;
1154
1155 beep_surface (gdk_event_get_surface (event));
1156
1157 g_signal_emit_by_name (context_simple, "preedit-changed");
1158
1159 return TRUE;
1160 }
1161 }
1162
1163 /* The current compose_buffer doesn't match anything */
1164 return no_sequence_matches (context_simple, n_compose, (GdkEvent *)event);
1165 }
1166
1167 static void
gtk_im_context_simple_reset(GtkIMContext * context)1168 gtk_im_context_simple_reset (GtkIMContext *context)
1169 {
1170 GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context);
1171 GtkIMContextSimplePrivate *priv = context_simple->priv;
1172
1173 priv->compose_buffer[0] = 0;
1174
1175 if (priv->tentative_match->len > 0 ||
1176 priv->in_hex_sequence ||
1177 priv->in_compose_sequence)
1178 {
1179 priv->in_hex_sequence = FALSE;
1180 priv->in_compose_sequence = FALSE;
1181 g_string_set_size (priv->tentative_match, 0);
1182 priv->tentative_match_len = 0;
1183 g_signal_emit_by_name (context_simple, "preedit-changed");
1184 g_signal_emit_by_name (context_simple, "preedit-end");
1185 }
1186 }
1187
1188 static void
gtk_im_context_simple_get_preedit_string(GtkIMContext * context,char ** str,PangoAttrList ** attrs,int * cursor_pos)1189 gtk_im_context_simple_get_preedit_string (GtkIMContext *context,
1190 char **str,
1191 PangoAttrList **attrs,
1192 int *cursor_pos)
1193 {
1194 GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context);
1195 GtkIMContextSimplePrivate *priv = context_simple->priv;
1196 GString *s;
1197 int i;
1198
1199 s = g_string_new ("");
1200
1201 if (priv->in_hex_sequence)
1202 {
1203 g_string_append_c (s, 'u');
1204
1205 for (i = 0; priv->compose_buffer[i]; i++)
1206 g_string_append_unichar (s, gdk_keyval_to_unicode (priv->compose_buffer[i]));
1207 }
1208 else if (priv->in_compose_sequence)
1209 {
1210 if (priv->tentative_match_len > 0 && priv->compose_buffer[0] != 0)
1211 {
1212 g_string_append (s, priv->tentative_match->str);
1213 }
1214 else
1215 {
1216 for (i = 0; priv->compose_buffer[i]; i++)
1217 {
1218 if (priv->compose_buffer[i] == GDK_KEY_Multi_key)
1219 {
1220 /* We only show the Compose key visibly when it is the
1221 * only glyph in the preedit, or when the sequence contains
1222 * multiple Compose keys, or when it occurs in the
1223 * middle of the sequence. Sadly, the official character,
1224 * U+2384, COMPOSITION SYMBOL, is bit too distracting, so
1225 * we use U+00B7, MIDDLE DOT.
1226 */
1227 if (priv->compose_buffer[1] == 0 || i > 0 ||
1228 priv->compose_buffer[i + 1] == GDK_KEY_Multi_key)
1229 g_string_append (s, "·");
1230 }
1231 else
1232 {
1233 gunichar ch;
1234
1235 if (is_dead_key (priv->compose_buffer[i]))
1236 {
1237 append_dead_key (s, priv->compose_buffer[i]);
1238 }
1239 else
1240 {
1241 ch = gdk_keyval_to_unicode (priv->compose_buffer[i]);
1242 if (ch)
1243 g_string_append_unichar (s, ch);
1244 }
1245 }
1246 }
1247 }
1248 }
1249
1250 if (cursor_pos)
1251 *cursor_pos = g_utf8_strlen (s->str, s->len);
1252
1253 if (attrs)
1254 {
1255 *attrs = pango_attr_list_new ();
1256
1257 if (s->len)
1258 {
1259 PangoAttribute *attr;
1260
1261 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
1262 attr->start_index = 0;
1263 attr->end_index = s->len;
1264 pango_attr_list_insert (*attrs, attr);
1265
1266 attr = pango_attr_fallback_new (TRUE);
1267 attr->start_index = 0;
1268 attr->end_index = s->len;
1269 pango_attr_list_insert (*attrs, attr);
1270 }
1271 }
1272
1273 if (str)
1274 *str = g_string_free (s, FALSE);
1275 }
1276
1277 /**
1278 * gtk_im_context_simple_add_table: (skip)
1279 * @context_simple: A `GtkIMContextSimple`
1280 * @data: (array): the table
1281 * @max_seq_len: Maximum length of a sequence in the table
1282 * @n_seqs: number of sequences in the table
1283 *
1284 * Adds an additional table to search to the input context.
1285 * Each row of the table consists of @max_seq_len key symbols
1286 * followed by two #guint16 interpreted as the high and low
1287 * words of a #gunicode value. Tables are searched starting
1288 * from the last added.
1289 *
1290 * The table must be sorted in dictionary order on the
1291 * numeric value of the key symbol fields. (Values beyond
1292 * the length of the sequence should be zero.)
1293 *
1294 * Deprecated: 4.4: Use gtk_im_context_simple_add_compose_file()
1295 */
1296 void
gtk_im_context_simple_add_table(GtkIMContextSimple * context_simple,guint16 * data,int max_seq_len,int n_seqs)1297 gtk_im_context_simple_add_table (GtkIMContextSimple *context_simple,
1298 guint16 *data,
1299 int max_seq_len,
1300 int n_seqs)
1301 {
1302 g_return_if_fail (GTK_IS_IM_CONTEXT_SIMPLE (context_simple));
1303
1304 add_compose_table_from_data (data, max_seq_len, n_seqs);
1305 }
1306
1307 /**
1308 * gtk_im_context_simple_add_compose_file:
1309 * @context_simple: A `GtkIMContextSimple`
1310 * @compose_file: The path of compose file
1311 *
1312 * Adds an additional table from the X11 compose file.
1313 */
1314 void
gtk_im_context_simple_add_compose_file(GtkIMContextSimple * context_simple,const char * compose_file)1315 gtk_im_context_simple_add_compose_file (GtkIMContextSimple *context_simple,
1316 const char *compose_file)
1317 {
1318 g_return_if_fail (GTK_IS_IM_CONTEXT_SIMPLE (context_simple));
1319
1320 add_compose_table_from_file (compose_file);
1321 }
1322