1 /* vim:set et sts=4: */
2 /* ibus-hangul - The Hangul Engine For IBus
3  * Copyright (C) 2008-2009 Peng Huang <shawn.p.huang@gmail.com>
4  * Copyright (C) 2009-2011 Choe Hwanjin <choe.hwanjin@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <ibus.h>
26 #include <gio/gio.h>
27 #include <hangul.h>
28 #include <string.h>
29 #include <ctype.h>
30 
31 #include "i18n.h"
32 #include "engine.h"
33 #include "ustring.h"
34 
35 
36 typedef struct _IBusHangulEngine IBusHangulEngine;
37 typedef struct _IBusHangulEngineClass IBusHangulEngineClass;
38 
39 typedef struct _HotkeyList HotkeyList;
40 
41 enum {
42     INPUT_MODE_HANGUL,
43     INPUT_MODE_LATIN,
44     INPUT_MODE_COUNT,
45 };
46 
47 struct _IBusHangulEngine {
48     IBusEngineSimple parent;
49 
50     /* members */
51     HangulInputContext *context;
52     UString* preedit;
53     int input_mode;
54     unsigned int input_purpose;
55     gboolean hanja_mode;
56     HanjaList* hanja_list;
57     int last_lookup_method;
58 
59     IBusLookupTable *table;
60 
61     IBusProperty    *prop_hangul_mode;
62     IBusProperty    *prop_hanja_mode;
63     IBusPropList    *prop_list;
64 
65     IBusText        *input_mode_symbols[INPUT_MODE_COUNT];
66 };
67 
68 struct _IBusHangulEngineClass {
69     IBusEngineSimpleClass parent;
70 };
71 
72 struct KeyEvent {
73     guint keyval;
74     guint modifiers;
75 };
76 
77 struct _HotkeyList {
78     guint   all_modifiers;
79     GArray *keys;
80 };
81 
82 enum {
83     LOOKUP_METHOD_EXACT,
84     LOOKUP_METHOD_PREFIX,
85     LOOKUP_METHOD_SUFFIX,
86 };
87 
88 /* functions prototype */
89 static void     ibus_hangul_engine_class_init
90                                             (IBusHangulEngineClass  *klass);
91 static void     ibus_hangul_engine_init     (IBusHangulEngine       *hangul);
92 static GObject*
93                 ibus_hangul_engine_constructor
94                                             (GType                   type,
95                                              guint                   n_construct_params,
96                                              GObjectConstructParam  *construct_params);
97 static void     ibus_hangul_engine_destroy  (IBusHangulEngine       *hangul);
98 static gboolean
99                 ibus_hangul_engine_process_key_event
100                                             (IBusEngine             *engine,
101                                              guint                   keyval,
102                                              guint                   keycode,
103                                              guint                   modifiers);
104 static void ibus_hangul_engine_focus_in     (IBusEngine             *engine);
105 static void ibus_hangul_engine_focus_out    (IBusEngine             *engine);
106 static void ibus_hangul_engine_reset        (IBusEngine             *engine);
107 static void ibus_hangul_engine_enable       (IBusEngine             *engine);
108 static void ibus_hangul_engine_disable      (IBusEngine             *engine);
109 #if 0
110 static void ibus_engine_set_cursor_location (IBusEngine             *engine,
111                                              gint                    x,
112                                              gint                    y,
113                                              gint                    w,
114                                              gint                    h);
115 static void ibus_hangul_engine_set_capabilities
116                                             (IBusEngine             *engine,
117                                              guint                   caps);
118 #endif
119 static void ibus_hangul_engine_page_up      (IBusEngine             *engine);
120 static void ibus_hangul_engine_page_down    (IBusEngine             *engine);
121 static void ibus_hangul_engine_cursor_up    (IBusEngine             *engine);
122 static void ibus_hangul_engine_cursor_down  (IBusEngine             *engine);
123 static void ibus_hangul_engine_property_activate
124                                             (IBusEngine             *engine,
125                                              const gchar            *prop_name,
126                                              guint                   prop_state);
127 #if 0
128 static void ibus_hangul_engine_property_show
129                                                                                         (IBusEngine             *engine,
130                                              const gchar            *prop_name);
131 static void ibus_hangul_engine_property_hide
132                                                                                         (IBusEngine             *engine,
133                                              const gchar            *prop_name);
134 #endif
135 
136 static void ibus_hangul_engine_candidate_clicked
137                                             (IBusEngine             *engine,
138                                              guint                   index,
139                                              guint                   button,
140                                              guint                   state);
141 static void ibus_hangul_engine_set_content_type
142                                             (IBusEngine             *engine,
143                                              guint                   purpose,
144                                              guint                   hints);
145 
146 static void ibus_hangul_engine_flush        (IBusHangulEngine       *hangul);
147 static void ibus_hangul_engine_clear_preedit_text
148                                             (IBusHangulEngine       *hangul);
149 static void ibus_hangul_engine_update_preedit_text
150                                             (IBusHangulEngine       *hangul);
151 
152 static void ibus_hangul_engine_update_lookup_table
153                                             (IBusHangulEngine       *hangul);
154 static gboolean ibus_hangul_engine_has_preedit
155                                             (IBusHangulEngine       *hangul);
156 static void ibus_hangul_engine_switch_input_mode
157                                             (IBusHangulEngine       *hangul);
158 static void ibus_hangul_engine_set_input_mode
159                                             (IBusHangulEngine       *hangul,
160                                              int                     input_mode);
161 static IBusText*
162             ibus_hangul_engine_get_input_mode_symbol
163                                             (IBusHangulEngine       *hangul,
164                                              int                     input_mode);
165 
166 static bool ibus_hangul_engine_on_transition
167                                             (HangulInputContext     *hic,
168                                              ucschar                 c,
169                                              const ucschar          *preedit,
170                                              void                   *data);
171 
172 static void        settings_changed         (GSettings              *settings,
173                                              const gchar            *key,
174                                              gpointer                user_data);
175 
176 static void        lookup_table_set_visible (IBusLookupTable        *table,
177                                              gboolean                flag);
178 static gboolean        lookup_table_is_visible
179                                             (IBusLookupTable        *table);
180 
181 static gboolean key_event_list_match        (GArray                 *list,
182                                              guint                   keyval,
183                                              guint                   modifiers);
184 
185 static void     hotkey_list_init            (HotkeyList           *list);
186 static void     hotkey_list_fini            (HotkeyList           *list);
187 static void     hotkey_list_set_from_string (HotkeyList         *list,
188                                              const char             *str);
189 static void     hotkey_list_append          (HotkeyList           *list,
190                                              guint                   keyval,
191                                              guint                   modifiers);
192 static gboolean hotkey_list_match           (HotkeyList           *list,
193                                              guint                   keyval,
194                                              guint                   modifiers);
195 static gboolean hotkey_list_has_modifier    (HotkeyList           *list,
196                                              guint                   keyval);
197 
198 static glong ucschar_strlen (const ucschar* str);
199 
200 static IBusEngineSimpleClass *parent_class = NULL;
201 static HanjaTable *hanja_table = NULL;
202 static HanjaTable *symbol_table = NULL;
203 static GSettings *settings_hangul = NULL;
204 static GSettings *settings_panel = NULL;
205 static GString    *hangul_keyboard = NULL;
206 static HotkeyList hanja_keys;
207 static HotkeyList switch_keys;
208 static HotkeyList on_keys;
209 static HotkeyList off_keys;
210 static int lookup_table_orientation = 0;
211 static IBusKeymap *keymap = NULL;
212 static gboolean word_commit = FALSE;
213 static gboolean auto_reorder = TRUE;
214 static gboolean disable_latin_mode = FALSE;
215 static int initial_input_mode = INPUT_MODE_LATIN;
216 /**
217  * whether to use event forwarding workaround
218  */
219 static gboolean use_event_forwarding = TRUE;
220 
221 static glong
ucschar_strlen(const ucschar * str)222 ucschar_strlen (const ucschar* str)
223 {
224     const ucschar* p = str;
225     while (*p != 0)
226         p++;
227     return p - str;
228 }
229 
230 GType
ibus_hangul_engine_get_type(void)231 ibus_hangul_engine_get_type (void)
232 {
233     static GType type = 0;
234 
235     static const GTypeInfo type_info = {
236         sizeof (IBusHangulEngineClass),
237         (GBaseInitFunc)     NULL,
238         (GBaseFinalizeFunc) NULL,
239         (GClassInitFunc)    ibus_hangul_engine_class_init,
240         NULL,
241         NULL,
242         sizeof (IBusHangulEngine),
243         0,
244         (GInstanceInitFunc) ibus_hangul_engine_init,
245     };
246 
247     if (type == 0) {
248             type = g_type_register_static (IBUS_TYPE_ENGINE_SIMPLE,
249                                            "IBusHangulEngine",
250                                            &type_info,
251                                            (GTypeFlags) 0);
252     }
253 
254     return type;
255 }
256 
257 void
ibus_hangul_init(IBusBus * bus)258 ibus_hangul_init (IBusBus *bus)
259 {
260     GVariant* value = NULL;
261 
262     hanja_table = hanja_table_load (NULL);
263 
264     symbol_table = hanja_table_load (IBUSHANGUL_DATADIR "/data/symbol.txt");
265 
266     settings_hangul = g_settings_new ("org.freedesktop.ibus.engine.hangul");
267     settings_panel = g_settings_new ("org.freedesktop.ibus.panel");
268 
269     hangul_keyboard = g_string_new_len (NULL, 8);
270     value = g_settings_get_value (settings_hangul, "hangul-keyboard");
271     if (value != NULL) {
272         const gchar* str = g_variant_get_string (value, NULL);
273         g_string_assign (hangul_keyboard, str);
274         g_clear_pointer (&value, g_variant_unref);
275     }
276 
277     hotkey_list_init(&switch_keys);
278 
279     value = g_settings_get_value (settings_hangul, "switch-keys");
280     if (value != NULL) {
281         const gchar* str = g_variant_get_string (value, NULL);
282         hotkey_list_set_from_string(&switch_keys, str);
283         g_clear_pointer (&value, g_variant_unref);
284     } else {
285         hotkey_list_append(&switch_keys, IBUS_Hangul, 0);
286         hotkey_list_append(&switch_keys, IBUS_space, IBUS_SHIFT_MASK);
287     }
288 
289     hotkey_list_init(&hanja_keys);
290 
291     value = g_settings_get_value (settings_hangul, "hanja-keys");
292     if (value != NULL) {
293         const gchar* str = g_variant_get_string (value, NULL);
294         hotkey_list_set_from_string(&hanja_keys, str);
295         g_clear_pointer (&value, g_variant_unref);
296     } else {
297         hotkey_list_append(&hanja_keys, IBUS_Hangul_Hanja, 0);
298         hotkey_list_append(&hanja_keys, IBUS_F9, 0);
299     }
300 
301     hotkey_list_init (&on_keys);
302     value = g_settings_get_value (settings_hangul, "on-keys");
303     if (value != NULL) {
304         const gchar* str = g_variant_get_string (value, NULL);
305         hotkey_list_set_from_string (&on_keys, str);
306         g_clear_pointer (&value, g_variant_unref);
307     }
308 
309     hotkey_list_init (&off_keys);
310     value = g_settings_get_value (settings_hangul, "off-keys");
311     if (value != NULL) {
312         const gchar* str = g_variant_get_string (value, NULL);
313         hotkey_list_set_from_string (&off_keys, str);
314         g_clear_pointer (&value, g_variant_unref);
315     }
316 
317     value = g_settings_get_value (settings_hangul, "word-commit");
318     if (value != NULL) {
319         word_commit = g_variant_get_boolean (value);
320         g_clear_pointer (&value, g_variant_unref);
321     }
322 
323     value = g_settings_get_value (settings_hangul, "auto-reorder");
324     if (value != NULL) {
325         auto_reorder = g_variant_get_boolean (value);
326         g_clear_pointer (&value, g_variant_unref);
327     }
328 
329     value = g_settings_get_value (settings_hangul, "disable-latin-mode");
330     if (value != NULL) {
331         disable_latin_mode = g_variant_get_boolean (value);
332         g_clear_pointer (&value, g_variant_unref);
333     }
334 
335     value = g_settings_get_value (settings_hangul, "initial-input-mode");
336     if (value != NULL) {
337         const gchar* str = g_variant_get_string (value, NULL);
338         if (strcmp(str, "latin") == 0) {
339             initial_input_mode = INPUT_MODE_LATIN;
340         } else if (strcmp(str, "hangul") == 0) {
341             initial_input_mode = INPUT_MODE_HANGUL;
342         }
343         g_clear_pointer (&value, g_variant_unref);
344     }
345 
346     value = g_settings_get_value (settings_hangul, "use-event-forwarding");
347     if (value != NULL) {
348         use_event_forwarding = g_variant_get_boolean (value);
349         g_clear_pointer (&value, g_variant_unref);
350     }
351 
352     value = g_settings_get_value (settings_panel, "lookup-table-orientation");
353     if (value != NULL) {
354         lookup_table_orientation = g_variant_get_int32(value);
355         g_clear_pointer (&value, g_variant_unref);
356     }
357 
358     keymap = ibus_keymap_get("us");
359 }
360 
361 void
ibus_hangul_exit(void)362 ibus_hangul_exit (void)
363 {
364     if (keymap != NULL) {
365 	g_object_unref(keymap);
366 	keymap = NULL;
367     }
368 
369     hotkey_list_fini (&switch_keys);
370     hotkey_list_fini (&hanja_keys);
371     hotkey_list_fini (&on_keys);
372     hotkey_list_fini (&off_keys);
373 
374     hanja_table_delete (hanja_table);
375     hanja_table = NULL;
376 
377     hanja_table_delete (symbol_table);
378     symbol_table = NULL;
379 
380     g_clear_object (&settings_hangul);
381     g_clear_object (&settings_panel);
382 
383     g_string_free (hangul_keyboard, TRUE);
384     hangul_keyboard = NULL;
385 }
386 
387 static void
ibus_hangul_engine_class_init(IBusHangulEngineClass * klass)388 ibus_hangul_engine_class_init (IBusHangulEngineClass *klass)
389 {
390     GObjectClass *object_class = G_OBJECT_CLASS (klass);
391     IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (klass);
392     IBusEngineClass *engine_class = IBUS_ENGINE_CLASS (klass);
393 
394     parent_class = (IBusEngineSimpleClass *) g_type_class_peek_parent (klass);
395 
396     object_class->constructor = ibus_hangul_engine_constructor;
397     ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_hangul_engine_destroy;
398 
399     engine_class->process_key_event = ibus_hangul_engine_process_key_event;
400 
401     engine_class->reset = ibus_hangul_engine_reset;
402     engine_class->enable = ibus_hangul_engine_enable;
403     engine_class->disable = ibus_hangul_engine_disable;
404 
405     engine_class->focus_in = ibus_hangul_engine_focus_in;
406     engine_class->focus_out = ibus_hangul_engine_focus_out;
407 
408     engine_class->page_up = ibus_hangul_engine_page_up;
409     engine_class->page_down = ibus_hangul_engine_page_down;
410 
411     engine_class->cursor_up = ibus_hangul_engine_cursor_up;
412     engine_class->cursor_down = ibus_hangul_engine_cursor_down;
413 
414     engine_class->property_activate = ibus_hangul_engine_property_activate;
415 
416     engine_class->candidate_clicked = ibus_hangul_engine_candidate_clicked;
417     engine_class->set_content_type = ibus_hangul_engine_set_content_type;
418 }
419 
420 static void
ibus_hangul_engine_init(IBusHangulEngine * hangul)421 ibus_hangul_engine_init (IBusHangulEngine *hangul)
422 {
423     IBusProperty* prop;
424     IBusText* label;
425     IBusText* tooltip;
426     IBusText* symbol;
427 
428     hangul->context = hangul_ic_new (hangul_keyboard->str);
429     hangul_ic_connect_callback (hangul->context, "transition",
430                                 ibus_hangul_engine_on_transition, hangul);
431 
432     hangul->preedit = ustring_new();
433     hangul->hanja_list = NULL;
434     hangul->input_mode = initial_input_mode;
435     hangul->input_purpose = IBUS_INPUT_PURPOSE_FREE_FORM;
436     hangul->hanja_mode = FALSE;
437     hangul->last_lookup_method = LOOKUP_METHOD_PREFIX;
438 
439     if (disable_latin_mode) {
440         hangul->input_mode = INPUT_MODE_HANGUL;
441     }
442 
443     hangul->prop_list = ibus_prop_list_new ();
444     g_object_ref_sink (hangul->prop_list);
445 
446     label = ibus_text_new_from_string (_("Hangul mode"));
447     tooltip = ibus_text_new_from_string (_("Enable/Disable Hangul mode"));
448     prop = ibus_property_new ("InputMode",
449                               PROP_TYPE_TOGGLE,
450                               label,
451                               NULL,
452                               tooltip,
453                               TRUE, TRUE, PROP_STATE_UNCHECKED, NULL);
454     symbol = ibus_hangul_engine_get_input_mode_symbol (hangul,
455                                                        hangul->input_mode);
456     ibus_property_set_symbol(prop, symbol);
457     g_object_ref_sink (prop);
458     ibus_prop_list_append (hangul->prop_list, prop);
459     hangul->prop_hangul_mode = prop;
460 
461     label = ibus_text_new_from_string (_("Hanja lock"));
462     tooltip = ibus_text_new_from_string (_("Enable/Disable Hanja mode"));
463     prop = ibus_property_new ("hanja_mode",
464                               PROP_TYPE_TOGGLE,
465                               label,
466                               NULL,
467                               tooltip,
468                               TRUE, TRUE, PROP_STATE_UNCHECKED, NULL);
469     g_object_ref_sink (prop);
470     ibus_prop_list_append (hangul->prop_list, prop);
471     hangul->prop_hanja_mode = prop;
472 
473     label = ibus_text_new_from_string (_("Setup"));
474     tooltip = ibus_text_new_from_string (_("Configure hangul engine"));
475     prop = ibus_property_new ("setup",
476                               PROP_TYPE_NORMAL,
477                               label,
478                               "gtk-preferences",
479                               tooltip,
480                               TRUE, TRUE, PROP_STATE_UNCHECKED, NULL);
481     ibus_prop_list_append (hangul->prop_list, prop);
482 
483     hangul->table = ibus_lookup_table_new (9, 0, TRUE, FALSE);
484     g_object_ref_sink (hangul->table);
485 
486     g_signal_connect (settings_hangul, "changed",
487                       G_CALLBACK (settings_changed), hangul);
488     g_signal_connect (settings_panel, "changed",
489                       G_CALLBACK (settings_changed), hangul);
490 }
491 
492 static GObject*
ibus_hangul_engine_constructor(GType type,guint n_construct_params,GObjectConstructParam * construct_params)493 ibus_hangul_engine_constructor (GType                   type,
494                                 guint                   n_construct_params,
495                                 GObjectConstructParam  *construct_params)
496 {
497     IBusHangulEngine *hangul;
498 
499     hangul = (IBusHangulEngine *) G_OBJECT_CLASS (parent_class)->constructor (type,
500                                                        n_construct_params,
501                                                        construct_params);
502 
503     return (GObject *)hangul;
504 }
505 
506 
507 static void
ibus_hangul_engine_destroy(IBusHangulEngine * hangul)508 ibus_hangul_engine_destroy (IBusHangulEngine *hangul)
509 {
510     int i;
511     IBusText **symbols;
512 
513     if (hangul->prop_hangul_mode) {
514         g_object_unref (hangul->prop_hangul_mode);
515         hangul->prop_hangul_mode = NULL;
516     }
517 
518     if (hangul->prop_hanja_mode) {
519         g_object_unref (hangul->prop_hanja_mode);
520         hangul->prop_hanja_mode = NULL;
521     }
522 
523     if (hangul->prop_list) {
524         g_object_unref (hangul->prop_list);
525         hangul->prop_list = NULL;
526     }
527 
528     if (hangul->table) {
529         g_object_unref (hangul->table);
530         hangul->table = NULL;
531     }
532 
533     if (hangul->context) {
534         hangul_ic_delete (hangul->context);
535         hangul->context = NULL;
536     }
537 
538     symbols = hangul->input_mode_symbols;
539     for (i = 0; i < INPUT_MODE_COUNT; ++i) {
540         if (symbols[i] != NULL) {
541             g_object_unref(symbols[i]);
542             symbols[i] = NULL;
543         }
544     }
545 
546     IBUS_OBJECT_CLASS (parent_class)->destroy ((IBusObject *)hangul);
547 }
548 
549 static void
ibus_hangul_engine_clear_preedit_text(IBusHangulEngine * hangul)550 ibus_hangul_engine_clear_preedit_text (IBusHangulEngine *hangul)
551 {
552     IBusText *text;
553 
554     text = ibus_text_new_from_static_string ("");
555     ibus_engine_update_preedit_text ((IBusEngine *)hangul, text, 0, FALSE);
556 }
557 
558 static void
ibus_hangul_engine_update_preedit_text(IBusHangulEngine * hangul)559 ibus_hangul_engine_update_preedit_text (IBusHangulEngine *hangul)
560 {
561     const ucschar *hic_preedit;
562     IBusText *text;
563     UString *preedit;
564     gint preedit_len;
565 
566     // ibus-hangul's preedit string is made up of ibus context's
567     // internal preedit string and libhangul's preedit string.
568     // libhangul only supports one syllable preedit string.
569     // In order to make longer preedit string, ibus-hangul maintains
570     // internal preedit string.
571     hic_preedit = hangul_ic_get_preedit_string (hangul->context);
572 
573     preedit = ustring_dup (hangul->preedit);
574     preedit_len = ustring_length(preedit);
575     ustring_append_ucs4 (preedit, hic_preedit, -1);
576 
577     if (ustring_length(preedit) > 0) {
578 	IBusPreeditFocusMode preedit_option = IBUS_ENGINE_PREEDIT_COMMIT;
579 
580 	if (hangul->hanja_list != NULL)
581 	    preedit_option = IBUS_ENGINE_PREEDIT_CLEAR;
582 
583         text = ibus_text_new_from_ucs4 ((gunichar*)preedit->data);
584         // ibus-hangul's internal preedit string
585         ibus_text_append_attribute (text, IBUS_ATTR_TYPE_UNDERLINE,
586                 IBUS_ATTR_UNDERLINE_SINGLE, 0, preedit_len);
587         // Preedit string from libhangul context.
588         // This is currently composing syllable.
589         ibus_text_append_attribute (text, IBUS_ATTR_TYPE_FOREGROUND,
590                 0x00ffffff, preedit_len, -1);
591         ibus_text_append_attribute (text, IBUS_ATTR_TYPE_BACKGROUND,
592                 0x00000000, preedit_len, -1);
593         ibus_engine_update_preedit_text_with_mode ((IBusEngine *)hangul,
594                                                    text,
595                                                    ibus_text_get_length (text),
596                                                    TRUE,
597                                                    preedit_option);
598     } else {
599         text = ibus_text_new_from_static_string ("");
600         ibus_engine_update_preedit_text ((IBusEngine *)hangul, text, 0, FALSE);
601     }
602 
603     ustring_delete(preedit);
604 }
605 
606 static void
ibus_hangul_engine_update_lookup_table_ui(IBusHangulEngine * hangul)607 ibus_hangul_engine_update_lookup_table_ui (IBusHangulEngine *hangul)
608 {
609     guint cursor_pos;
610     const char* comment;
611     IBusText* text;
612 
613     // update aux text
614     cursor_pos = ibus_lookup_table_get_cursor_pos (hangul->table);
615     comment = hanja_list_get_nth_comment (hangul->hanja_list, cursor_pos);
616 
617     text = ibus_text_new_from_string (comment);
618     ibus_engine_update_auxiliary_text ((IBusEngine *)hangul, text, TRUE);
619 
620     // update lookup table
621     ibus_engine_update_lookup_table ((IBusEngine *)hangul, hangul->table, TRUE);
622 }
623 
624 static void
ibus_hangul_engine_commit_current_candidate(IBusHangulEngine * hangul)625 ibus_hangul_engine_commit_current_candidate (IBusHangulEngine *hangul)
626 {
627     guint cursor_pos;
628     const char* key;
629     const char* value;
630     const ucschar* hic_preedit;
631     glong key_len;
632     glong hic_preedit_len;
633     glong preedit_len;
634 
635     IBusText* text;
636 
637     cursor_pos = ibus_lookup_table_get_cursor_pos (hangul->table);
638     key = hanja_list_get_nth_key (hangul->hanja_list, cursor_pos);
639     value = hanja_list_get_nth_value (hangul->hanja_list, cursor_pos);
640     hic_preedit = hangul_ic_get_preedit_string (hangul->context);
641 
642     key_len = g_utf8_strlen(key, -1);
643     preedit_len = ustring_length(hangul->preedit);
644     hic_preedit_len = ucschar_strlen (hic_preedit);
645 
646     if (hangul->last_lookup_method == LOOKUP_METHOD_PREFIX) {
647         if (preedit_len == 0 && hic_preedit_len == 0) {
648             /* remove surrounding_text */
649             if (key_len > 0) {
650                 ibus_engine_delete_surrounding_text ((IBusEngine *)hangul,
651                         -key_len , key_len);
652             }
653         } else {
654             /* remove ibus preedit text */
655             if (key_len > 0) {
656                 glong n = MIN(key_len, preedit_len);
657                 ustring_erase (hangul->preedit, 0, n);
658                 key_len -= preedit_len;
659             }
660 
661             /* remove hic preedit text */
662             if (key_len > 0) {
663                 hangul_ic_reset (hangul->context);
664                 key_len -= hic_preedit_len;
665             }
666         }
667     } else {
668         /* remove hic preedit text */
669         if (hic_preedit_len > 0) {
670             hangul_ic_reset (hangul->context);
671             key_len -= hic_preedit_len;
672         }
673 
674         /* remove ibus preedit text */
675         if (key_len > preedit_len) {
676             ustring_erase (hangul->preedit, 0, preedit_len);
677             key_len -= preedit_len;
678         } else if (key_len > 0) {
679             ustring_erase (hangul->preedit, 0, key_len);
680             key_len = 0;
681         }
682 
683         /* remove surrounding_text */
684         if (key_len > 0) {
685             ibus_engine_delete_surrounding_text ((IBusEngine *)hangul,
686                     -key_len , key_len);
687         }
688     }
689 
690     /* clear preedit text before commit */
691     ibus_hangul_engine_clear_preedit_text (hangul);
692 
693     text = ibus_text_new_from_string (value);
694     ibus_engine_commit_text ((IBusEngine *)hangul, text);
695 
696     ibus_hangul_engine_update_preedit_text (hangul);
697 }
698 
699 static gchar*
h_ibus_text_get_substring(IBusText * ibus_text,glong p1,glong p2)700 h_ibus_text_get_substring (IBusText* ibus_text, glong p1, glong p2)
701 {
702     const gchar* text;
703     const gchar* begin;
704     const gchar* end;
705     gchar* substring;
706     glong limit;
707     glong pos;
708     glong n;
709 
710     text = ibus_text_get_text (ibus_text);
711     limit = ibus_text_get_length (ibus_text) + 1;
712     if (text == NULL || limit == 0)
713         return NULL;
714 
715     p1 = MAX(0, p1);
716     p2 = MAX(0, p2);
717 
718     pos = MIN(p1, p2);
719     n = ABS(p2 - p1);
720 
721     if (pos + n > limit)
722         n = limit - pos;
723 
724     begin = g_utf8_offset_to_pointer (text, pos);
725     end = g_utf8_offset_to_pointer (begin, n);
726 
727     substring = g_strndup (begin, end - begin);
728     return substring;
729 }
730 
731 static HanjaList*
ibus_hangul_engine_lookup_hanja_table(const char * key,int method)732 ibus_hangul_engine_lookup_hanja_table (const char* key, int method)
733 {
734     HanjaList* list = NULL;
735 
736     if (key == NULL)
737         return NULL;
738 
739     switch (method) {
740     case LOOKUP_METHOD_EXACT:
741         if (symbol_table != NULL)
742             list = hanja_table_match_exact (symbol_table, key);
743 
744         if (list == NULL)
745             list = hanja_table_match_exact (hanja_table, key);
746 
747         break;
748     case LOOKUP_METHOD_PREFIX:
749         if (symbol_table != NULL)
750             list = hanja_table_match_prefix (symbol_table, key);
751 
752         if (list == NULL)
753             list = hanja_table_match_prefix (hanja_table, key);
754 
755         break;
756     case LOOKUP_METHOD_SUFFIX:
757         if (symbol_table != NULL)
758             list = hanja_table_match_suffix (symbol_table, key);
759 
760         if (list == NULL)
761             list = hanja_table_match_suffix (hanja_table, key);
762 
763         break;
764     }
765 
766     return list;
767 }
768 
769 static void
ibus_hangul_engine_update_hanja_list(IBusHangulEngine * hangul)770 ibus_hangul_engine_update_hanja_list (IBusHangulEngine *hangul)
771 {
772     gchar* hanja_key;
773     gchar* preedit_utf8;
774     const ucschar* hic_preedit;
775     UString* preedit;
776     int lookup_method;
777     IBusText* ibus_text = NULL;
778     guint cursor_pos = 0;
779     guint anchor_pos = 0;
780 
781     if (hangul->hanja_list != NULL) {
782         hanja_list_delete (hangul->hanja_list);
783         hangul->hanja_list = NULL;
784     }
785 
786     hic_preedit = hangul_ic_get_preedit_string (hangul->context);
787 
788     hanja_key = NULL;
789     lookup_method = LOOKUP_METHOD_PREFIX;
790 
791     preedit = ustring_dup (hangul->preedit);
792     ustring_append_ucs4 (preedit, hic_preedit, -1);
793 
794     if (ustring_length(preedit) > 0) {
795         preedit_utf8 = ustring_to_utf8 (preedit, -1);
796         if (word_commit || hangul->hanja_mode) {
797             hanja_key = preedit_utf8;
798             lookup_method = LOOKUP_METHOD_PREFIX;
799         } else {
800             gchar* substr;
801             ibus_engine_get_surrounding_text ((IBusEngine *)hangul, &ibus_text,
802                     &cursor_pos, &anchor_pos);
803 
804             substr = h_ibus_text_get_substring (ibus_text,
805                     cursor_pos - 64, cursor_pos);
806 
807             if (substr != NULL) {
808                 hanja_key = g_strconcat (substr, preedit_utf8, NULL);
809                 g_free (preedit_utf8);
810             } else {
811                 hanja_key = preedit_utf8;
812             }
813             lookup_method = LOOKUP_METHOD_SUFFIX;
814         }
815     } else {
816         ibus_engine_get_surrounding_text ((IBusEngine *)hangul, &ibus_text,
817                 &cursor_pos, &anchor_pos);
818         if (cursor_pos != anchor_pos) {
819             // If we have selection in surrounding text, we use that.
820             hanja_key = h_ibus_text_get_substring (ibus_text,
821                     cursor_pos, anchor_pos);
822             lookup_method = LOOKUP_METHOD_EXACT;
823         } else {
824             hanja_key = h_ibus_text_get_substring (ibus_text,
825                     cursor_pos - 64, cursor_pos);
826             lookup_method = LOOKUP_METHOD_SUFFIX;
827         }
828     }
829 
830     if (hanja_key != NULL) {
831         hangul->hanja_list = ibus_hangul_engine_lookup_hanja_table (hanja_key,
832                 lookup_method);
833         hangul->last_lookup_method = lookup_method;
834         g_free (hanja_key);
835     }
836 
837     ustring_delete (preedit);
838 
839     if (ibus_text != NULL)
840         g_object_unref (ibus_text);
841 }
842 
843 static void
ibus_hangul_engine_apply_hanja_list(IBusHangulEngine * hangul)844 ibus_hangul_engine_apply_hanja_list (IBusHangulEngine *hangul)
845 {
846     HanjaList* list = hangul->hanja_list;
847     if (list != NULL) {
848         int i, n;
849         n = hanja_list_get_size (list);
850 
851         ibus_lookup_table_clear (hangul->table);
852         for (i = 0; i < n; i++) {
853             const char* value = hanja_list_get_nth_value (list, i);
854             IBusText* text = ibus_text_new_from_string (value);
855             ibus_lookup_table_append_candidate (hangul->table, text);
856         }
857 
858         ibus_lookup_table_set_cursor_pos (hangul->table, 0);
859         ibus_hangul_engine_update_lookup_table_ui (hangul);
860         lookup_table_set_visible (hangul->table, TRUE);
861     }
862 }
863 
864 static void
ibus_hangul_engine_hide_lookup_table(IBusHangulEngine * hangul)865 ibus_hangul_engine_hide_lookup_table (IBusHangulEngine *hangul)
866 {
867     gboolean is_visible;
868     is_visible = lookup_table_is_visible (hangul->table);
869 
870     // Sending hide lookup table message when the lookup table
871     // is not visible results wrong behavior. So I have to check
872     // whether the table is visible or not before to hide.
873     if (is_visible) {
874         ibus_engine_hide_lookup_table ((IBusEngine *)hangul);
875         ibus_engine_hide_auxiliary_text ((IBusEngine *)hangul);
876         lookup_table_set_visible (hangul->table, FALSE);
877     }
878 
879     if (hangul->hanja_list != NULL) {
880         hanja_list_delete (hangul->hanja_list);
881         hangul->hanja_list = NULL;
882     }
883 }
884 
885 static void
ibus_hangul_engine_update_lookup_table(IBusHangulEngine * hangul)886 ibus_hangul_engine_update_lookup_table (IBusHangulEngine *hangul)
887 {
888     ibus_hangul_engine_update_hanja_list (hangul);
889 
890     if (hangul->hanja_list != NULL) {
891 	// We should redraw preedit text with IBUS_ENGINE_PREEDIT_CLEAR option
892 	// here to prevent committing it on focus out event incidentally.
893 	ibus_hangul_engine_update_preedit_text (hangul);
894         ibus_hangul_engine_apply_hanja_list (hangul);
895     } else {
896         ibus_hangul_engine_hide_lookup_table (hangul);
897     }
898 }
899 
900 static gboolean
ibus_hangul_engine_process_candidate_key_event(IBusHangulEngine * hangul,guint keyval,guint modifiers)901 ibus_hangul_engine_process_candidate_key_event (IBusHangulEngine    *hangul,
902                                                 guint                keyval,
903                                                 guint                modifiers)
904 {
905     if (keyval == IBUS_Escape) {
906         ibus_hangul_engine_hide_lookup_table (hangul);
907 	// When the lookup table is poped up, preedit string is
908 	// updated with IBUS_ENGINE_PREEDIT_CLEAR option.
909 	// So, when focus is out, the preedit text will not be committed.
910 	// To prevent this problem, we have to update preedit text here
911 	// with IBUS_ENGINE_PREEDIT_COMMIT option.
912 	ibus_hangul_engine_update_preedit_text (hangul);
913         return TRUE;
914     } else if (keyval == IBUS_Return) {
915         ibus_hangul_engine_commit_current_candidate (hangul);
916 
917         if (hangul->hanja_mode && ibus_hangul_engine_has_preedit (hangul)) {
918             ibus_hangul_engine_update_lookup_table (hangul);
919         } else {
920             ibus_hangul_engine_hide_lookup_table (hangul);
921         }
922         return TRUE;
923     } else if (keyval >= IBUS_1 && keyval <= IBUS_9) {
924         guint page_no;
925         guint page_size;
926         guint cursor_pos;
927 
928         page_size = ibus_lookup_table_get_page_size (hangul->table);
929         cursor_pos = ibus_lookup_table_get_cursor_pos (hangul->table);
930         page_no = cursor_pos / page_size;
931 
932         cursor_pos = page_no * page_size + (keyval - IBUS_1);
933         ibus_lookup_table_set_cursor_pos (hangul->table, cursor_pos);
934 
935         ibus_hangul_engine_commit_current_candidate (hangul);
936 
937         if (hangul->hanja_mode && ibus_hangul_engine_has_preedit (hangul)) {
938             ibus_hangul_engine_update_lookup_table (hangul);
939         } else {
940             ibus_hangul_engine_hide_lookup_table (hangul);
941         }
942         return TRUE;
943     } else if (keyval == IBUS_Page_Up) {
944         ibus_lookup_table_page_up (hangul->table);
945         ibus_hangul_engine_update_lookup_table_ui (hangul);
946         return TRUE;
947     } else if (keyval == IBUS_Page_Down) {
948         ibus_lookup_table_page_down (hangul->table);
949         ibus_hangul_engine_update_lookup_table_ui (hangul);
950         return TRUE;
951     } else {
952         if (lookup_table_orientation == 0) {
953             // horizontal
954             if (keyval == IBUS_Left) {
955                 ibus_lookup_table_cursor_up (hangul->table);
956                 ibus_hangul_engine_update_lookup_table_ui (hangul);
957                 return TRUE;
958             } else if (keyval == IBUS_Right) {
959                 ibus_lookup_table_cursor_down (hangul->table);
960                 ibus_hangul_engine_update_lookup_table_ui (hangul);
961                 return TRUE;
962             } else if (keyval == IBUS_Up) {
963                 ibus_lookup_table_page_up (hangul->table);
964                 ibus_hangul_engine_update_lookup_table_ui (hangul);
965                 return TRUE;
966             } else if (keyval == IBUS_Down) {
967                 ibus_lookup_table_page_down (hangul->table);
968                 ibus_hangul_engine_update_lookup_table_ui (hangul);
969                 return TRUE;
970             }
971         } else {
972             // vertical
973             if (keyval == IBUS_Left) {
974                 ibus_lookup_table_page_up (hangul->table);
975                 ibus_hangul_engine_update_lookup_table_ui (hangul);
976                 return TRUE;
977             } else if (keyval == IBUS_Right) {
978                 ibus_lookup_table_page_down (hangul->table);
979                 ibus_hangul_engine_update_lookup_table_ui (hangul);
980                 return TRUE;
981             } else if (keyval == IBUS_Up) {
982                 ibus_lookup_table_cursor_up (hangul->table);
983                 ibus_hangul_engine_update_lookup_table_ui (hangul);
984                 return TRUE;
985             } else if (keyval == IBUS_Down) {
986                 ibus_lookup_table_cursor_down (hangul->table);
987                 ibus_hangul_engine_update_lookup_table_ui (hangul);
988                 return TRUE;
989             }
990         }
991     }
992 
993     if (!hangul->hanja_mode) {
994         if (lookup_table_orientation == 0) {
995             // horizontal
996             if (keyval == IBUS_h) {
997                 ibus_lookup_table_cursor_up (hangul->table);
998                 ibus_hangul_engine_update_lookup_table_ui (hangul);
999                 return TRUE;
1000             } else if (keyval == IBUS_l) {
1001                 ibus_lookup_table_cursor_down (hangul->table);
1002                 ibus_hangul_engine_update_lookup_table_ui (hangul);
1003                 return TRUE;
1004             } else if (keyval == IBUS_k) {
1005                 ibus_lookup_table_page_up (hangul->table);
1006                 ibus_hangul_engine_update_lookup_table_ui (hangul);
1007                 return TRUE;
1008             } else if (keyval == IBUS_j) {
1009                 ibus_lookup_table_page_down (hangul->table);
1010                 ibus_hangul_engine_update_lookup_table_ui (hangul);
1011                 return TRUE;
1012             }
1013         } else {
1014             // vertical
1015             if (keyval == IBUS_h) {
1016                 ibus_lookup_table_page_up (hangul->table);
1017                 ibus_hangul_engine_update_lookup_table_ui (hangul);
1018                 return TRUE;
1019             } else if (keyval == IBUS_l) {
1020                 ibus_lookup_table_page_down (hangul->table);
1021                 ibus_hangul_engine_update_lookup_table_ui (hangul);
1022                 return TRUE;
1023             } else if (keyval == IBUS_k) {
1024                 ibus_lookup_table_cursor_up (hangul->table);
1025                 ibus_hangul_engine_update_lookup_table_ui (hangul);
1026                 return TRUE;
1027             } else if (keyval == IBUS_j) {
1028                 ibus_lookup_table_cursor_down (hangul->table);
1029                 ibus_hangul_engine_update_lookup_table_ui (hangul);
1030                 return TRUE;
1031             }
1032         }
1033     }
1034 
1035     return FALSE;
1036 }
1037 
1038 static gboolean
ibus_hangul_engine_process_key_event(IBusEngine * engine,guint keyval,guint keycode,guint modifiers)1039 ibus_hangul_engine_process_key_event (IBusEngine     *engine,
1040                                       guint           keyval,
1041                                       guint           keycode,
1042                                       guint           modifiers)
1043 {
1044     IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1045 
1046     guint mask;
1047     gboolean retval;
1048     const ucschar *str;
1049 
1050     if (modifiers & IBUS_RELEASE_MASK)
1051         return FALSE;
1052 
1053     // if we don't ignore shift keys, shift key will make flush the preedit
1054     // string. So you cannot input shift+key.
1055     // Let's think about these examples:
1056     //   dlTek (2 set)
1057     //   qhRdmaqkq (2 set)
1058     if (keyval == IBUS_Shift_L || keyval == IBUS_Shift_R)
1059         return FALSE;
1060 
1061     // On password mode, we ignore hotkeys
1062     if (hangul->input_purpose == IBUS_INPUT_PURPOSE_PASSWORD)
1063         return IBUS_ENGINE_CLASS (parent_class)->process_key_event (engine, keyval, keycode, modifiers);
1064 
1065     // If a hotkey has any modifiers, we ignore that modifier
1066     // keyval, or we cannot make the hanja key work.
1067     // Because when we get the modifier key alone, we commit the
1068     // current preedit string. So after that, even if we get the
1069     // right hanja key event, we don't have preedit string to be changed
1070     // to hanja word.
1071     // See this bug: http://code.google.com/p/ibus/issues/detail?id=1036
1072     if (hotkey_list_has_modifier(&switch_keys, keyval))
1073         return FALSE;
1074 
1075     if (hotkey_list_match(&switch_keys, keyval, modifiers)) {
1076         ibus_hangul_engine_switch_input_mode (hangul);
1077         return TRUE;
1078     }
1079 
1080     if (hotkey_list_match (&on_keys, keyval, modifiers)) {
1081         ibus_hangul_engine_set_input_mode (hangul, INPUT_MODE_HANGUL);
1082         return FALSE;
1083     }
1084 
1085     if (hangul->input_mode == INPUT_MODE_LATIN)
1086         return IBUS_ENGINE_CLASS (parent_class)->process_key_event (engine, keyval, keycode, modifiers);
1087 
1088     /* This feature is for vi* users.
1089      * On Esc, the input mode is changed to latin */
1090     if (hotkey_list_match (&off_keys, keyval, modifiers)) {
1091         ibus_hangul_engine_set_input_mode (hangul, INPUT_MODE_LATIN);
1092         /* If we return TRUE, then vi will not receive "ESC" key event. */
1093         return FALSE;
1094     }
1095 
1096     if (hotkey_list_has_modifier(&hanja_keys, keyval))
1097 	return FALSE;
1098 
1099     if (hotkey_list_match(&hanja_keys, keyval, modifiers)) {
1100         if (hangul->hanja_list == NULL) {
1101             ibus_hangul_engine_update_lookup_table (hangul);
1102         } else {
1103             ibus_hangul_engine_hide_lookup_table (hangul);
1104         }
1105         return TRUE;
1106     }
1107 
1108     if (hangul->hanja_list != NULL) {
1109         retval = ibus_hangul_engine_process_candidate_key_event (hangul,
1110                      keyval, modifiers);
1111         if (hangul->hanja_mode) {
1112             if (retval)
1113                 return TRUE;
1114         } else {
1115             return TRUE;
1116         }
1117     }
1118 
1119     // If we've got a key event with some modifiers, commit current
1120     // preedit string and ignore this key event.
1121     // So, if you want to add some key event handler, put it
1122     // before this code.
1123     // Ignore key event with control, alt, super or mod5
1124     mask = IBUS_CONTROL_MASK |
1125 	    IBUS_MOD1_MASK | IBUS_MOD3_MASK | IBUS_MOD4_MASK | IBUS_MOD5_MASK;
1126     if (modifiers & mask) {
1127         ibus_hangul_engine_flush (hangul);
1128         return FALSE;
1129     }
1130 
1131     if (keyval == IBUS_BackSpace) {
1132         retval = hangul_ic_backspace (hangul->context);
1133         if (!retval) {
1134             guint preedit_len = ustring_length (hangul->preedit);
1135             if (preedit_len > 0) {
1136                 ustring_erase (hangul->preedit, preedit_len - 1, 1);
1137                 retval = TRUE;
1138             }
1139         }
1140 
1141         ibus_hangul_engine_update_preedit_text (hangul);
1142 
1143         if (hangul->hanja_mode) {
1144             if (ibus_hangul_engine_has_preedit (hangul)) {
1145                 ibus_hangul_engine_update_lookup_table (hangul);
1146             } else {
1147                 ibus_hangul_engine_hide_lookup_table (hangul);
1148             }
1149         }
1150     } else {
1151 	// We need to normalize the keyval to US qwerty keylayout,
1152 	// because the korean input method is depend on the position of
1153 	// each key, not the character. We make the keyval from keycode
1154 	// as if the keyboard is US qwerty layout. Then we can assume the
1155 	// keyval represent the position of the each key.
1156 	// But if the hic is in transliteration mode, then we should not
1157 	// normalize the keyval.
1158 	bool is_transliteration_mode =
1159 		 hangul_ic_is_transliteration(hangul->context);
1160 	if (!is_transliteration_mode) {
1161 	    if (keymap != NULL)
1162 		keyval = ibus_keymap_lookup_keysym(keymap, keycode, modifiers);
1163 	}
1164 
1165         // ignore capslock
1166         if (modifiers & IBUS_LOCK_MASK) {
1167             if (keyval >= 'A' && keyval <= 'z') {
1168                 if (isupper(keyval))
1169                     keyval = tolower(keyval);
1170                 else
1171                     keyval = toupper(keyval);
1172             }
1173         }
1174         retval = hangul_ic_process (hangul->context, keyval);
1175 
1176         str = hangul_ic_get_commit_string (hangul->context);
1177         if (word_commit || hangul->hanja_mode) {
1178             const ucschar* hic_preedit;
1179 
1180             hic_preedit = hangul_ic_get_preedit_string (hangul->context);
1181             if (hic_preedit != NULL && hic_preedit[0] != 0) {
1182                 ustring_append_ucs4 (hangul->preedit, str, -1);
1183             } else {
1184                 IBusText *text;
1185                 const ucschar* preedit;
1186 
1187                 ustring_append_ucs4 (hangul->preedit, str, -1);
1188                 if (ustring_length (hangul->preedit) > 0) {
1189                     /* clear preedit text before commit */
1190                     ibus_hangul_engine_clear_preedit_text (hangul);
1191 
1192                     preedit = ustring_begin (hangul->preedit);
1193                     text = ibus_text_new_from_ucs4 ((gunichar*)preedit);
1194                     ibus_engine_commit_text (engine, text);
1195                 }
1196                 ustring_clear (hangul->preedit);
1197             }
1198         } else {
1199             if (str != NULL && str[0] != 0) {
1200                 IBusText *text;
1201 
1202                 /* clear preedit text before commit */
1203                 ibus_hangul_engine_clear_preedit_text (hangul);
1204 
1205                 text = ibus_text_new_from_ucs4 (str);
1206                 ibus_engine_commit_text (engine, text);
1207             }
1208         }
1209 
1210         ibus_hangul_engine_update_preedit_text (hangul);
1211 
1212         if (hangul->hanja_mode) {
1213             ibus_hangul_engine_update_lookup_table (hangul);
1214         }
1215 
1216         if (!retval)
1217             ibus_hangul_engine_flush (hangul);
1218     }
1219 
1220     /* We always return TRUE here even if we didn't use this event.
1221      * Instead, we forward the event to clients.
1222      *
1223      * Because IBus has a problem with sync mode.
1224      * I think it's limitations of IBus implementation.
1225      * We call several engine functions(updating preedit text and committing
1226      * text) inside this function.
1227      * But clients cannot receive the results of other calls until this
1228      * function ends. Clients only get one result from a remote call at a time
1229      * because clients may run on event loop.
1230      * Clients may process this event first and then get the results which
1231      * may change the preedit text or commit text.
1232      * So the event order is broken.
1233      * Call order:
1234      *      engine                          client
1235      *                                      call process_key_event
1236      *      begin process_key_event
1237      *        call commit_text
1238      *        call update_preedit_text
1239      *      return the event as unused
1240      *                                      receive the result of process_key_event
1241      *                                      receive the result of commit_text
1242      *                                      receive the result of update_preedit_text
1243      *
1244      * To solve this problem, we return TRUE as if we consumed this event.
1245      * After that, we forward this event to clients.
1246      * Then clients may get the events in correct order.
1247      * This approach is a kind of async processing.
1248      * Call order:
1249      *      engine                          client
1250      *                                      call process_key_event
1251      *      begin process_key_event
1252      *        call commit_text
1253      *        call update_preedit_text
1254      *        call forward_key_event
1255      *      return the event as used
1256      *                                      receive the result of process_key_event
1257      *                                      receive the result of commit_text
1258      *                                      receive the result of update_preedit_text
1259      *                                      receive the forwarded key event
1260      *
1261      * See: https://github.com/choehwanjin/ibus-hangul/issues/40
1262      */
1263     if (use_event_forwarding) {
1264         if (!retval) {
1265             ibus_engine_forward_key_event (engine, keyval, keycode, modifiers);
1266         }
1267 
1268         return TRUE;
1269     }
1270 
1271     return retval;
1272 }
1273 
1274 static void
ibus_hangul_engine_flush(IBusHangulEngine * hangul)1275 ibus_hangul_engine_flush (IBusHangulEngine *hangul)
1276 {
1277     const gunichar *str;
1278     IBusText *text;
1279 
1280     ibus_hangul_engine_hide_lookup_table (hangul);
1281 
1282     str = hangul_ic_flush (hangul->context);
1283 
1284     ustring_append_ucs4 (hangul->preedit, str, -1);
1285 
1286     if (ustring_length (hangul->preedit) != 0) {
1287         /* clear preedit text before commit */
1288         ibus_hangul_engine_clear_preedit_text (hangul);
1289 
1290 	str = ustring_begin (hangul->preedit);
1291 	text = ibus_text_new_from_ucs4 (str);
1292 
1293         g_debug("flush: %s", text->text);
1294 	ibus_engine_commit_text ((IBusEngine *) hangul, text);
1295 
1296 	ustring_clear(hangul->preedit);
1297     }
1298 
1299     ibus_hangul_engine_update_preedit_text (hangul);
1300 }
1301 
1302 static void
ibus_hangul_engine_focus_in(IBusEngine * engine)1303 ibus_hangul_engine_focus_in (IBusEngine *engine)
1304 {
1305     IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1306 
1307     if (hangul->input_mode == INPUT_MODE_HANGUL) {
1308         ibus_property_set_state (hangul->prop_hangul_mode, PROP_STATE_CHECKED);
1309     } else {
1310         ibus_property_set_state (hangul->prop_hangul_mode, PROP_STATE_UNCHECKED);
1311     }
1312 
1313     if (hangul->hanja_mode) {
1314         ibus_property_set_state (hangul->prop_hanja_mode, PROP_STATE_CHECKED);
1315     } else {
1316         ibus_property_set_state (hangul->prop_hanja_mode, PROP_STATE_UNCHECKED);
1317     }
1318 
1319     ibus_engine_register_properties (engine, hangul->prop_list);
1320 
1321     ibus_hangul_engine_update_preedit_text (hangul);
1322 
1323     if (hangul->hanja_list != NULL) {
1324         ibus_hangul_engine_update_lookup_table_ui (hangul);
1325     }
1326 
1327     IBUS_ENGINE_CLASS (parent_class)->focus_in (engine);
1328 }
1329 
1330 static void
ibus_hangul_engine_focus_out(IBusEngine * engine)1331 ibus_hangul_engine_focus_out (IBusEngine *engine)
1332 {
1333     IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1334 
1335     if (hangul->hanja_list == NULL) {
1336 	// ibus-hangul uses
1337 	// ibus_engine_update_preedit_text_with_mode() function which makes
1338 	// the preedit string committed automatically when the focus is out.
1339 	// So we don't need to commit the preedit here.
1340 	hangul_ic_reset (hangul->context);
1341     } else {
1342         ibus_engine_hide_lookup_table (engine);
1343         ibus_engine_hide_auxiliary_text (engine);
1344     }
1345 
1346     IBUS_ENGINE_CLASS (parent_class)->focus_out ((IBusEngine *) hangul);
1347 }
1348 
1349 static void
ibus_hangul_engine_reset(IBusEngine * engine)1350 ibus_hangul_engine_reset (IBusEngine *engine)
1351 {
1352     IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1353 
1354     ibus_hangul_engine_flush (hangul);
1355     IBUS_ENGINE_CLASS (parent_class)->reset (engine);
1356 }
1357 
1358 static void
ibus_hangul_engine_enable(IBusEngine * engine)1359 ibus_hangul_engine_enable (IBusEngine *engine)
1360 {
1361     IBUS_ENGINE_CLASS (parent_class)->enable (engine);
1362 
1363     ibus_engine_get_surrounding_text (engine, NULL, NULL, NULL);
1364 }
1365 
1366 static void
ibus_hangul_engine_disable(IBusEngine * engine)1367 ibus_hangul_engine_disable (IBusEngine *engine)
1368 {
1369     ibus_hangul_engine_focus_out (engine);
1370     IBUS_ENGINE_CLASS (parent_class)->disable (engine);
1371 }
1372 
1373 static void
ibus_hangul_engine_page_up(IBusEngine * engine)1374 ibus_hangul_engine_page_up (IBusEngine *engine)
1375 {
1376     IBUS_ENGINE_CLASS (parent_class)->page_up (engine);
1377 }
1378 
1379 static void
ibus_hangul_engine_page_down(IBusEngine * engine)1380 ibus_hangul_engine_page_down (IBusEngine *engine)
1381 {
1382     IBUS_ENGINE_CLASS (parent_class)->page_down (engine);
1383 }
1384 
1385 static void
ibus_hangul_engine_cursor_up(IBusEngine * engine)1386 ibus_hangul_engine_cursor_up (IBusEngine *engine)
1387 {
1388     IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1389 
1390     if (hangul->hanja_list != NULL) {
1391         ibus_lookup_table_cursor_up (hangul->table);
1392         ibus_hangul_engine_update_lookup_table_ui (hangul);
1393     }
1394 
1395     IBUS_ENGINE_CLASS (parent_class)->cursor_up (engine);
1396 }
1397 
1398 static void
ibus_hangul_engine_cursor_down(IBusEngine * engine)1399 ibus_hangul_engine_cursor_down (IBusEngine *engine)
1400 {
1401     IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1402 
1403     if (hangul->hanja_list != NULL) {
1404         ibus_lookup_table_cursor_down (hangul->table);
1405         ibus_hangul_engine_update_lookup_table_ui (hangul);
1406     }
1407 
1408     IBUS_ENGINE_CLASS (parent_class)->cursor_down (engine);
1409 }
1410 
1411 static void
ibus_hangul_engine_property_activate(IBusEngine * engine,const gchar * prop_name,guint prop_state)1412 ibus_hangul_engine_property_activate (IBusEngine    *engine,
1413                                       const gchar   *prop_name,
1414                                       guint          prop_state)
1415 {
1416     if (strcmp(prop_name, "setup") == 0) {
1417         GError *error = NULL;
1418         gchar *argv[2] = { NULL, };
1419 
1420         argv[0] = "ibus-setup-hangul";
1421         argv[1] = NULL;
1422         g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
1423     } else if (strcmp(prop_name, "InputMode") == 0) {
1424         IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1425 
1426         ibus_hangul_engine_switch_input_mode (hangul);
1427     } else if (strcmp(prop_name, "hanja_mode") == 0) {
1428         IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1429 
1430         hangul->hanja_mode = !hangul->hanja_mode;
1431         if (hangul->hanja_mode) {
1432             ibus_property_set_state (hangul->prop_hanja_mode,
1433                     PROP_STATE_CHECKED);
1434         } else {
1435             ibus_property_set_state (hangul->prop_hanja_mode,
1436                     PROP_STATE_UNCHECKED);
1437         }
1438 
1439         ibus_engine_update_property (engine, hangul->prop_hanja_mode);
1440         ibus_hangul_engine_flush (hangul);
1441     }
1442 }
1443 
1444 static gboolean
ibus_hangul_engine_has_preedit(IBusHangulEngine * hangul)1445 ibus_hangul_engine_has_preedit (IBusHangulEngine *hangul)
1446 {
1447     guint preedit_len;
1448     const ucschar *hic_preedit;
1449 
1450     hic_preedit = hangul_ic_get_preedit_string (hangul->context);
1451     if (hic_preedit[0] != 0)
1452         return TRUE;
1453 
1454     preedit_len = ustring_length (hangul->preedit);
1455     if (preedit_len > 0)
1456         return TRUE;
1457 
1458     return FALSE;
1459 }
1460 
1461 static void
ibus_hangul_engine_switch_input_mode(IBusHangulEngine * hangul)1462 ibus_hangul_engine_switch_input_mode (IBusHangulEngine *hangul)
1463 {
1464     int input_mode = hangul->input_mode + 1;
1465 
1466     if (input_mode >= INPUT_MODE_COUNT) {
1467         input_mode = INPUT_MODE_HANGUL;
1468     }
1469 
1470     ibus_hangul_engine_set_input_mode (hangul, input_mode);
1471 }
1472 
1473 static IBusText *
ibus_hangul_engine_get_input_mode_symbol(IBusHangulEngine * hangul,int input_mode)1474 ibus_hangul_engine_get_input_mode_symbol (IBusHangulEngine *hangul,
1475                                           int input_mode)
1476 {
1477     IBusText **symbols = hangul->input_mode_symbols;
1478 
1479     if (symbols[0] == NULL) {
1480         symbols[INPUT_MODE_HANGUL] = ibus_text_new_from_string ("한");
1481         g_object_ref_sink(symbols[INPUT_MODE_HANGUL]);
1482         symbols[INPUT_MODE_LATIN] = ibus_text_new_from_string ("EN");
1483         g_object_ref_sink(symbols[INPUT_MODE_LATIN]);
1484     }
1485 
1486     if (input_mode >= INPUT_MODE_COUNT)
1487         return symbols[INPUT_MODE_HANGUL];
1488 
1489     return symbols[input_mode];
1490 }
1491 
1492 static void
ibus_hangul_engine_set_input_mode(IBusHangulEngine * hangul,int input_mode)1493 ibus_hangul_engine_set_input_mode (IBusHangulEngine *hangul, int input_mode)
1494 {
1495     IBusText* symbol;
1496     IBusProperty* prop;
1497 
1498     ibus_hangul_engine_flush (hangul);
1499 
1500     if (disable_latin_mode) {
1501         return;
1502     }
1503 
1504     prop = hangul->prop_hangul_mode;
1505 
1506     hangul->input_mode = input_mode;
1507     g_debug("input_mode: %s", (input_mode == INPUT_MODE_HANGUL) ? "hangul" : "latin");
1508 
1509     symbol = ibus_hangul_engine_get_input_mode_symbol (hangul, input_mode);
1510     ibus_property_set_symbol(prop, symbol);
1511 
1512     if (hangul->input_mode == INPUT_MODE_HANGUL) {
1513         ibus_property_set_state (prop, PROP_STATE_CHECKED);
1514     } else {
1515         ibus_property_set_state (prop, PROP_STATE_UNCHECKED);
1516     }
1517 
1518     ibus_engine_update_property (IBUS_ENGINE (hangul), prop);
1519 }
1520 
1521 static bool
ibus_hangul_engine_on_transition(HangulInputContext * hic,ucschar c,const ucschar * preedit,void * data)1522 ibus_hangul_engine_on_transition (HangulInputContext     *hic,
1523                                   ucschar                 c,
1524                                   const ucschar          *preedit,
1525                                   void                   *data)
1526 {
1527     if (!auto_reorder) {
1528         if (hangul_is_choseong (c)) {
1529             if (hangul_ic_has_jungseong (hic) || hangul_ic_has_jongseong (hic))
1530                 return false;
1531         }
1532 
1533         if (hangul_is_jungseong (c)) {
1534             if (hangul_ic_has_jongseong (hic))
1535                 return false;
1536         }
1537     }
1538 
1539     return true;
1540 }
1541 
1542 static void
settings_changed(GSettings * settings,const gchar * key,gpointer user_data)1543 settings_changed (GSettings    *settings,
1544                   const gchar  *key,
1545                   gpointer      user_data)
1546 {
1547     IBusHangulEngine *hangul = (IBusHangulEngine *) user_data;
1548     GValue schema_value = G_VALUE_INIT;
1549     const gchar *schema_id;
1550     GVariant *value;
1551 
1552     g_return_if_fail (G_IS_SETTINGS (settings));
1553 
1554     g_value_init (&schema_value, G_TYPE_STRING);
1555     g_object_get_property (G_OBJECT (settings), "schema-id", &schema_value);
1556     schema_id = g_value_get_string (&schema_value);
1557     value = g_settings_get_value (settings, key);
1558     if (strcmp (schema_id, "org.freedesktop.ibus.engine.hangul") == 0) {
1559         if (strcmp(key, "hangul-keyboard") == 0) {
1560             const gchar *str = g_variant_get_string(value, NULL);
1561             g_string_assign (hangul_keyboard, str);
1562             hangul_ic_select_keyboard (hangul->context, hangul_keyboard->str);
1563         } else if (strcmp (key, "hanja-keys") == 0) {
1564             const gchar* str = g_variant_get_string(value, NULL);
1565 	    hotkey_list_set_from_string(&hanja_keys, str);
1566         } else if (strcmp (key, "word-commit") == 0) {
1567             word_commit = g_variant_get_boolean (value);
1568         } else if (strcmp (key, "auto-reorder") == 0) {
1569             auto_reorder = g_variant_get_boolean (value);
1570         } else if (strcmp (key, "switch-keys") == 0) {
1571             const gchar* str = g_variant_get_string(value, NULL);
1572 	    hotkey_list_set_from_string(&switch_keys, str);
1573         } else if (strcmp (key, "on-keys") == 0) {
1574             const gchar* str = g_variant_get_string(value, NULL);
1575 	    hotkey_list_set_from_string(&on_keys, str);
1576         } else if (strcmp (key, "off-keys") == 0) {
1577             const gchar* str = g_variant_get_string(value, NULL);
1578 	    hotkey_list_set_from_string(&off_keys, str);
1579         } else if (strcmp (key, "initial-input-mode") == 0) {
1580             const gchar* str = g_variant_get_string (value, NULL);
1581             if (strcmp(str, "latin") == 0) {
1582                 initial_input_mode = INPUT_MODE_LATIN;
1583             } else if (strcmp(str, "hangul") == 0) {
1584                 initial_input_mode = INPUT_MODE_HANGUL;
1585             }
1586         }
1587     } else if (strcmp (schema_id, "org.freedesktop.ibus.panel") == 0) {
1588         if (strcmp (key, "lookup-table-orientation") == 0) {
1589             lookup_table_orientation = g_variant_get_int32(value);
1590         }
1591     }
1592     g_variant_unref (value);
1593     g_value_unset (&schema_value);
1594 }
1595 
1596 static void
lookup_table_set_visible(IBusLookupTable * table,gboolean flag)1597 lookup_table_set_visible (IBusLookupTable *table, gboolean flag)
1598 {
1599     g_object_set_data (G_OBJECT(table), "visible", GUINT_TO_POINTER(flag));
1600 }
1601 
1602 static gboolean
lookup_table_is_visible(IBusLookupTable * table)1603 lookup_table_is_visible (IBusLookupTable *table)
1604 {
1605     gpointer res = g_object_get_data (G_OBJECT(table), "visible");
1606     return GPOINTER_TO_UINT(res);
1607 }
1608 
1609 static void
key_event_list_append(GArray * list,guint keyval,guint modifiers)1610 key_event_list_append(GArray* list, guint keyval, guint modifiers)
1611 {
1612     struct KeyEvent ev = { keyval, modifiers};
1613     g_array_append_val(list, ev);
1614 }
1615 
1616 static gboolean
key_event_list_match(GArray * list,guint keyval,guint modifiers)1617 key_event_list_match(GArray* list, guint keyval, guint modifiers)
1618 {
1619     guint i;
1620     guint mask;
1621 
1622     /* ignore capslock and numlock */
1623     mask = IBUS_SHIFT_MASK |
1624            IBUS_CONTROL_MASK |
1625            IBUS_MOD1_MASK |
1626            IBUS_MOD3_MASK |
1627            IBUS_MOD4_MASK |
1628            IBUS_MOD5_MASK;
1629 
1630     modifiers &= mask;
1631     for (i = 0; i < list->len; ++i) {
1632         struct KeyEvent* ev = &g_array_index(list, struct KeyEvent, i);
1633         if (ev->keyval == keyval && ev->modifiers == modifiers) {
1634             return TRUE;
1635         }
1636     }
1637 
1638     return FALSE;
1639 }
1640 
1641 static void
ibus_hangul_engine_candidate_clicked(IBusEngine * engine,guint index,guint button,guint state)1642 ibus_hangul_engine_candidate_clicked (IBusEngine     *engine,
1643                                       guint           index,
1644                                       guint           button,
1645                                       guint           state)
1646 {
1647     IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1648     if (hangul == NULL)
1649 	return;
1650 
1651     if (hangul->table == NULL)
1652 	return;
1653 
1654     ibus_lookup_table_set_cursor_pos (hangul->table, index);
1655     ibus_hangul_engine_commit_current_candidate (hangul);
1656 
1657     if (hangul->hanja_mode) {
1658 	ibus_hangul_engine_update_lookup_table (hangul);
1659     } else {
1660 	ibus_hangul_engine_hide_lookup_table (hangul);
1661     }
1662 }
1663 
1664 static void
ibus_hangul_engine_set_content_type(IBusEngine * engine,guint purpose,guint hints)1665 ibus_hangul_engine_set_content_type (IBusEngine     *engine,
1666                                      guint           purpose,
1667                                      guint           hints)
1668 {
1669     IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
1670     if (hangul == NULL)
1671         return;
1672 
1673     hangul->input_purpose = purpose;
1674 }
1675 
1676 static void
hotkey_list_init(HotkeyList * list)1677 hotkey_list_init(HotkeyList* list)
1678 {
1679     list->all_modifiers = 0;
1680     list->keys = g_array_sized_new(FALSE, TRUE, sizeof(struct KeyEvent), 4);
1681 }
1682 
1683 static void
hotkey_list_fini(HotkeyList * list)1684 hotkey_list_fini(HotkeyList* list)
1685 {
1686     g_array_free(list->keys, TRUE);
1687     list->keys = NULL;
1688 }
1689 
1690 static void
hotkey_list_append_from_string(HotkeyList * list,const char * str)1691 hotkey_list_append_from_string(HotkeyList *list, const char* str)
1692 {
1693     guint keyval = 0;
1694     guint modifiers = 0;
1695     gboolean res;
1696 
1697     res = ibus_key_event_from_string(str, &keyval, &modifiers);
1698     if (res) {
1699 	hotkey_list_append(list, keyval, modifiers);
1700     }
1701 }
1702 
1703 static void
hotkey_list_append(HotkeyList * list,guint keyval,guint modifiers)1704 hotkey_list_append(HotkeyList *list, guint keyval, guint modifiers)
1705 {
1706     list->all_modifiers |= modifiers;
1707     key_event_list_append(list->keys, keyval, modifiers);
1708 }
1709 
1710 static void
hotkey_list_set_from_string(HotkeyList * list,const char * str)1711 hotkey_list_set_from_string(HotkeyList *list, const char* str)
1712 {
1713     gchar** items = g_strsplit(str, ",", 0);
1714 
1715     list->all_modifiers = 0;
1716     g_array_set_size(list->keys, 0);
1717 
1718     if (items != NULL) {
1719         int i;
1720         for (i = 0; items[i] != NULL; ++i) {
1721 	    hotkey_list_append_from_string(list, items[i]);
1722         }
1723         g_strfreev(items);
1724     }
1725 }
1726 
1727 static gboolean
hotkey_list_match(HotkeyList * list,guint keyval,guint modifiers)1728 hotkey_list_match(HotkeyList* list, guint keyval, guint modifiers)
1729 {
1730     return key_event_list_match(list->keys, keyval, modifiers);
1731 }
1732 
1733 static gboolean
hotkey_list_has_modifier(HotkeyList * list,guint keyval)1734 hotkey_list_has_modifier(HotkeyList* list, guint keyval)
1735 {
1736     if (list->all_modifiers & IBUS_CONTROL_MASK) {
1737 	if (keyval == IBUS_Control_L || keyval == IBUS_Control_R)
1738 	    return TRUE;
1739     }
1740 
1741     if (list->all_modifiers & IBUS_MOD1_MASK) {
1742 	if (keyval == IBUS_Alt_L || keyval == IBUS_Alt_R)
1743 	    return TRUE;
1744     }
1745 
1746     if (list->all_modifiers & IBUS_SUPER_MASK) {
1747 	if (keyval == IBUS_Super_L || keyval == IBUS_Super_R)
1748 	    return TRUE;
1749     }
1750 
1751     if (list->all_modifiers & IBUS_HYPER_MASK) {
1752 	if (keyval == IBUS_Hyper_L || keyval == IBUS_Hyper_R)
1753 	    return TRUE;
1754     }
1755 
1756     if (list->all_modifiers & IBUS_META_MASK) {
1757 	if (keyval == IBUS_Meta_L || keyval == IBUS_Meta_R)
1758 	    return TRUE;
1759     }
1760 
1761     return FALSE;
1762 }
1763