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