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