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 (&gtk_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