Launch the second ibus engine too to reduce the launching time.
[platform/upstream/ibus.git] / ui / gtk3 / panel.vala
1 /* vim:set et sts=4 sw=4:
2  *
3  * ibus - The Input Bus
4  *
5  * Copyright(c) 2011 Peng Huang <shawn.p.huang@gmail.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or(at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20  * Boston, MA  02111-1307  USA
21  */
22
23 class Panel : IBus.PanelService {
24     private class Keybinding {
25         public Keybinding(uint keysym,
26                           Gdk.ModifierType modifiers,
27                           bool reverse) {
28             this.keysym = keysym;
29             this.modifiers = modifiers;
30             this.reverse = reverse;
31         }
32
33         public uint keysym { get; set; }
34         public Gdk.ModifierType modifiers { get; set; }
35         public bool reverse { get; set; }
36     }
37
38     private IBus.Bus m_bus;
39     private IBus.Config m_config;
40     private Gtk.StatusIcon m_status_icon;
41     private Gtk.Menu m_ime_menu;
42     private Gtk.Menu m_sys_menu;
43     private IBus.EngineDesc[] m_engines = {};
44     private CandidatePanel m_candidate_panel;
45     private Switcher m_switcher;
46     private PropertyManager m_property_manager;
47     private GLib.Pid m_setup_pid = 0;
48     private Gtk.AboutDialog m_about_dialog;
49     private Gtk.CssProvider m_css_provider;
50     private int m_switcher_delay_time = 400;
51     private bool m_use_system_keyboard_layout = false;
52     private const string ACCELERATOR_SWITCH_IME_FOREWARD = "<Control>space";
53
54     private GLib.List<Keybinding> m_keybindings = new GLib.List<Keybinding>();
55
56     public Panel(IBus.Bus bus) {
57         GLib.assert(bus.is_connected());
58         // Chain up base class constructor
59         GLib.Object(connection : bus.get_connection(),
60                     object_path : "/org/freedesktop/IBus/Panel");
61
62         m_bus = bus;
63
64         // init ui
65         m_status_icon = new Gtk.StatusIcon();
66         m_status_icon.set_name("ibus-ui-gtk");
67         m_status_icon.set_title("IBus Panel");
68         m_status_icon.popup_menu.connect(status_icon_popup_menu_cb);
69         m_status_icon.activate.connect(status_icon_activate_cb);
70         m_status_icon.set_from_icon_name("ibus-keyboard");
71
72         m_candidate_panel = new CandidatePanel();
73         m_candidate_panel.page_up.connect((w) => this.page_up());
74         m_candidate_panel.page_down.connect((w) => this.page_down());
75
76         m_switcher = new Switcher();
77         // The initial shortcut is "<Control>space"
78         bind_switch_shortcut(null);
79
80         if (m_switcher_delay_time >= 0) {
81             m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
82         }
83
84         m_property_manager = new PropertyManager();
85         m_property_manager.property_activate.connect((k, s) => {
86             property_activate(k, s);
87         });
88
89         state_changed();
90     }
91
92     ~Panel() {
93         unbind_switch_shortcut();
94     }
95
96     private void keybinding_manager_bind(KeybindingManager keybinding_manager,
97                                          string?           accelerator) {
98         uint switch_keysym = 0;
99         Gdk.ModifierType switch_modifiers = 0;
100         Gdk.ModifierType reverse_modifier = Gdk.ModifierType.SHIFT_MASK;
101         Keybinding keybinding;
102
103         Gtk.accelerator_parse(accelerator,
104                 out switch_keysym, out switch_modifiers);
105
106         // Map virtual modifiers to (i.e. Mod2, Mod3, ...)
107         const Gdk.ModifierType VIRTUAL_MODIFIERS = (
108                 Gdk.ModifierType.SUPER_MASK |
109                 Gdk.ModifierType.HYPER_MASK |
110                 Gdk.ModifierType.META_MASK);
111         if ((switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
112         // workaround a bug in gdk vapi vala > 0.18
113         // https://bugzilla.gnome.org/show_bug.cgi?id=677559
114 #if VALA_0_18
115             Gdk.Keymap.get_default().map_virtual_modifiers(
116                     ref switch_modifiers);
117 #else
118             if ((switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
119                 switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
120             if ((switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
121                 switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
122 #endif
123             switch_modifiers &= ~VIRTUAL_MODIFIERS;
124         }
125
126         if (switch_keysym == 0 && switch_modifiers == 0) {
127             warning("Parse accelerator '%s' failed!", accelerator);
128             return;
129         }
130
131         keybinding = new Keybinding(switch_keysym,
132                                     switch_modifiers,
133                                     false);
134         m_keybindings.append(keybinding);
135
136         keybinding_manager.bind(switch_keysym, switch_modifiers,
137                 (e) => handle_engine_switch(e, false));
138
139         // accelerator already has Shift mask
140         if ((switch_modifiers & reverse_modifier) != 0) {
141             return;
142         }
143
144         switch_modifiers |= reverse_modifier;
145
146         keybinding = new Keybinding(switch_keysym,
147                                     switch_modifiers,
148                                     true);
149         m_keybindings.append(keybinding);
150
151         keybinding_manager.bind(switch_keysym, switch_modifiers,
152                 (e) => handle_engine_switch(e, true));
153     }
154
155     private void bind_switch_shortcut(Variant? variant) {
156         string[] accelerators = {};
157         Variant var_trigger = variant;
158
159         if (var_trigger == null && m_config != null) {
160             var_trigger = m_config.get_value("general/hotkey",
161                                              "triggers");
162         }
163
164         if (var_trigger != null) {
165             accelerators = var_trigger.dup_strv();
166         } else {
167             accelerators += ACCELERATOR_SWITCH_IME_FOREWARD;
168         }
169
170         var keybinding_manager = KeybindingManager.get_instance();
171
172         foreach (var accelerator in accelerators) {
173             keybinding_manager_bind(keybinding_manager, accelerator);
174         }
175     }
176
177     private void unbind_switch_shortcut() {
178         var keybinding_manager = KeybindingManager.get_instance();
179
180         unowned GLib.List<Keybinding> keybindings = m_keybindings;
181
182         while (keybindings != null) {
183             Keybinding keybinding = keybindings.data;
184
185             keybinding_manager.unbind(keybinding.keysym,
186                                       keybinding.modifiers);
187             keybindings = keybindings.next;
188         }
189
190         m_keybindings = null;
191     }
192
193     private void set_custom_font() {
194         Gdk.Display display = Gdk.Display.get_default();
195         Gdk.Screen screen = (display != null) ?
196                 display.get_default_screen() : null;
197
198         if (screen == null) {
199             warning("Could not open display.");
200             return;
201         }
202
203         bool use_custom_font = false;
204         GLib.Variant var_use_custom_font = m_config.get_value("panel",
205                                                               "use_custom_font");
206
207         if (var_use_custom_font != null) {
208             use_custom_font = var_use_custom_font.get_boolean();
209         }
210
211         if (m_css_provider != null) {
212             Gtk.StyleContext.remove_provider_for_screen(screen,
213                                                         m_css_provider);
214             m_css_provider = null;
215         }
216
217         if (use_custom_font == false) {
218             return;
219         }
220
221         string font_name = null;
222         GLib.Variant var_custom_font = m_config.get_value("panel",
223                                                           "custom_font");
224         if (var_custom_font != null) {
225             font_name = var_custom_font.dup_string();
226         }
227
228         if (font_name == null) {
229             warning("No config panel:custom_font.");
230             return;
231         }
232
233         string data_format = "GtkLabel { font: %s; }";
234         string data = data_format.printf(font_name);
235         m_css_provider = new Gtk.CssProvider();
236
237         try {
238             m_css_provider.load_from_data(data, -1);
239         } catch (GLib.Error e) {
240             warning("Failed css_provider_from_data: %s: %s", font_name,
241                                                              e.message);
242             return;
243         }
244
245         Gtk.StyleContext.add_provider_for_screen(screen,
246                                                  m_css_provider,
247                                                  Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
248     }
249
250     private void set_switcher_delay_time(Variant? variant) {
251         Variant var_switcher_delay_time = variant;
252
253         if (var_switcher_delay_time == null) {
254             var_switcher_delay_time = m_config.get_value("general",
255                                                          "switcher-delay-time");
256         }
257
258         if (var_switcher_delay_time == null) {
259             return;
260         }
261
262         m_switcher_delay_time = var_switcher_delay_time.get_int32();
263
264         if (m_switcher_delay_time >= 0) {
265             m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
266         }
267     }
268
269     private void set_use_system_keyboard_layout(Variant? variant) {
270         Variant var_use_system_kbd_layout = variant;
271
272         if (var_use_system_kbd_layout == null) {
273             var_use_system_kbd_layout = m_config.get_value(
274                     "general",
275                     "use_system_keyboard_layout");
276         }
277
278         if (var_use_system_kbd_layout == null) {
279             return;
280         }
281
282         m_use_system_keyboard_layout = var_use_system_kbd_layout.get_boolean();
283     }
284
285     public void set_config(IBus.Config config) {
286         if (m_config != null) {
287             m_config.value_changed.disconnect(config_value_changed_cb);
288             m_config.watch(null, null);
289             m_config = null;
290         }
291
292         m_config = config;
293         if (m_config != null) {
294             m_config.value_changed.connect(config_value_changed_cb);
295             m_config.watch("general", "preload_engines");
296             m_config.watch("general", "engines_order");
297             m_config.watch("general", "switcher_delay_time");
298             m_config.watch("general", "use_system_keyboard_layout");
299             m_config.watch("general/hotkey", "triggers");
300             m_config.watch("panel", "custom_font");
301             m_config.watch("panel", "use_custom_font");
302             // Update m_use_system_keyboard_layout before update_engines()
303             // is called.
304             set_use_system_keyboard_layout(null);
305             update_engines(m_config.get_value("general", "preload_engines"),
306                            m_config.get_value("general", "engines_order"));
307             unbind_switch_shortcut();
308             bind_switch_shortcut(null);
309             set_switcher_delay_time(null);
310         } else {
311             update_engines(null, null);
312         }
313
314         set_custom_font();
315     }
316
317     private void exec_setxkbmap(IBus.EngineDesc engine) {
318         string layout = engine.get_layout();
319         string variant = engine.get_layout_variant();
320         string option = engine.get_layout_option();
321         string standard_error = null;
322         int exit_status = 0;
323         string[] args = { "setxkbmap" };
324
325         if (layout != null && layout != "" && layout != "default") {
326             args += "-layout";
327             args += layout;
328         }
329         if (variant != null && variant != "" && variant != "default") {
330             args += "-variant";
331             args += variant;
332         }
333         if (option != null && option != "" && option != "default") {
334             /*TODO: Need to get the session XKB options */
335             args += "-option";
336             args += "-option";
337             args += option;
338         }
339
340         if (args.length == 1) {
341             return;
342         }
343
344         try {
345             if (!GLib.Process.spawn_sync(null, args, null,
346                                          GLib.SpawnFlags.SEARCH_PATH,
347                                          null, null,
348                                          out standard_error,
349                                          out exit_status)) {
350                 warning("Switch xkb layout to %s failed.",
351                         engine.get_layout());
352             }
353         } catch (GLib.SpawnError e) {
354             warning("Execute setxkbmap failed: %s", e.message);
355         }
356
357         if (exit_status != 0) {
358             warning("Execute setxkbmap failed: %s", standard_error ?? "(null)");
359         }
360     }
361
362     private void switch_engine(int i, bool force = false) {
363         GLib.assert(i >= 0 && i < m_engines.length);
364
365         // Do not need switch
366         if (i == 0 && !force)
367             return;
368
369         IBus.EngineDesc engine = m_engines[i];
370
371         if (!m_bus.set_global_engine(engine.get_name())) {
372             warning("Switch engine to %s failed.", engine.get_name());
373             return;
374         }
375         // set xkb layout
376         if (!m_use_system_keyboard_layout) {
377             exec_setxkbmap(engine);
378         }
379     }
380
381     private void config_value_changed_cb(IBus.Config config,
382                                          string section,
383                                          string name,
384                                          Variant variant) {
385         if (section == "general" && name == "preload_engines") {
386             update_engines(variant, null);
387             return;
388         }
389
390         if (section == "general/hotkey" && name == "triggers") {
391             unbind_switch_shortcut();
392             bind_switch_shortcut(variant);
393             return;
394         }
395
396         if (section == "panel" && (name == "custom_font" ||
397                                    name == "use_custom_font")) {
398             set_custom_font();
399             return;
400         }
401
402         if (section == "general" && name == "switcher_delay_time") {
403             set_switcher_delay_time(variant);
404             return;
405         }
406
407         if (section == "general" && name == "use_system_keyboard_layout") {
408             set_use_system_keyboard_layout(variant);
409             return;
410         }
411     }
412
413     private void handle_engine_switch(Gdk.Event event, bool revert) {
414         // Do not need switch IME
415         if (m_engines.length <= 1)
416             return;
417
418         uint keyval = event.key.keyval;
419         uint modifiers = KeybindingManager.MODIFIER_FILTER & event.key.state;
420
421         uint primary_modifiers =
422             KeybindingManager.get_primary_modifier(event.key.state);
423
424         bool pressed = KeybindingManager.primary_modifier_still_pressed(
425                 event, primary_modifiers);
426
427         if (revert) {
428             modifiers &= ~Gdk.ModifierType.SHIFT_MASK;
429         }
430
431         if (pressed && m_switcher_delay_time >= 0) {
432             int i = revert ? m_engines.length - 1 : 1;
433             i = m_switcher.run(keyval, modifiers, event, m_engines, i);
434             if (i < 0) {
435                 debug("switch cancelled");
436             } else {
437                 GLib.assert(i < m_engines.length);
438                 switch_engine(i);
439             }
440         } else {
441             int i = revert ? m_engines.length - 1 : 1;
442             switch_engine(i);
443         }
444     }
445
446     private void run_preload_engines(IBus.EngineDesc[] engines, int index) {
447         string[] names = {};
448
449         if (engines.length <= index) {
450             return;
451         }
452
453         names += engines[index].get_name();
454         m_bus.preload_engines_async(names, -1, null);
455     }
456
457     private void update_engines(GLib.Variant? var_engines,
458                                 GLib.Variant? var_order) {
459         string[] engine_names = null;
460
461         if (var_engines != null)
462             engine_names = var_engines.dup_strv();
463         if (engine_names == null || engine_names.length == 0)
464             engine_names = {"xkb:us::eng"};
465
466         string[] order_names =
467             (var_order != null) ? var_order.dup_strv() : null;
468
469         string[] names = {};
470
471         foreach (var name in order_names) {
472             if (name in engine_names)
473                 names += name;
474         }
475
476         foreach (var name in engine_names) {
477             if (name in names)
478                 continue;
479             names += name;
480         }
481
482         var engines = m_bus.get_engines_by_names(names);
483
484         if (m_engines.length == 0) {
485             m_engines = engines;
486             switch_engine(0, true);
487             run_preload_engines(engines, 1);
488         } else {
489             var current_engine = m_engines[0];
490             m_engines = engines;
491             int i;
492             for (i = 0; i < m_engines.length; i++) {
493                 if (current_engine.get_name() == engines[i].get_name()) {
494                     switch_engine(i);
495                     if (i != 0) {
496                         run_preload_engines(engines, 0);
497                     } else {
498                         run_preload_engines(engines, 1);
499                     }
500                     return;
501                 }
502             }
503             switch_engine(0, true);
504             run_preload_engines(engines, 1);
505         }
506
507     }
508
509     private void show_setup_dialog() {
510         if (m_setup_pid != 0) {
511             if (Posix.kill(m_setup_pid, Posix.SIGUSR1) == 0)
512                 return;
513             m_setup_pid = 0;
514         }
515
516         string binary = GLib.Path.build_filename(Config.BINDIR, "ibus-setup");
517         try {
518             GLib.Process.spawn_async(null,
519                                      {binary, "ibus-setup"},
520                                      null,
521                                      GLib.SpawnFlags.DO_NOT_REAP_CHILD,
522                                      null,
523                                      out m_setup_pid);
524         } catch (GLib.SpawnError e) {
525             warning("Execute %s failed! %s", binary, e.message);
526             m_setup_pid = 0;
527         }
528
529         GLib.ChildWatch.add(m_setup_pid, (pid, state) => {
530             if (pid != m_setup_pid)
531                 return;
532             m_setup_pid = 0;
533             GLib.Process.close_pid(pid);
534         });
535     }
536
537     private void show_about_dialog() {
538         if (m_about_dialog == null) {
539             m_about_dialog = new Gtk.AboutDialog();
540             m_about_dialog.set_program_name("IBus");
541             m_about_dialog.set_version(Config.PACKAGE_VERSION);
542
543             string copyright = _(
544                 "Copyright (c) 2007-2012 Peng Huang\n" +
545                 "Copyright (c) 2007-2010 Red Hat, Inc.\n");
546
547             m_about_dialog.set_copyright(copyright);
548             m_about_dialog.set_license("LGPL");
549             m_about_dialog.set_comments(_("IBus is an intelligent input bus for Linux/Unix."));
550             m_about_dialog.set_website("http://code.google.com/p/ibus");
551             m_about_dialog.set_authors({"Peng Huang <shawn.p.huang@gmail.com>"});
552             m_about_dialog.set_documenters({"Peng Huang <shawn.p.huang@gmail.com>"});
553             m_about_dialog.set_translator_credits(_("translator-credits"));
554             m_about_dialog.set_logo_icon_name("ibus");
555             m_about_dialog.set_icon_name("ibus");
556         }
557
558         if (!m_about_dialog.get_visible()) {
559             m_about_dialog.run();
560             m_about_dialog.hide();
561         } else {
562             m_about_dialog.present();
563         }
564     }
565
566     private void status_icon_popup_menu_cb(Gtk.StatusIcon status_icon,
567                                            uint button,
568                                            uint activate_time) {
569         // Show system menu
570         if (m_sys_menu == null) {
571             Gtk.ImageMenuItem item;
572             m_sys_menu = new Gtk.Menu();
573
574             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.PREFERENCES, null);
575             item.activate.connect((i) => show_setup_dialog());
576             m_sys_menu.append(item);
577
578             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.ABOUT, null);
579             item.activate.connect((i) => show_about_dialog());
580             m_sys_menu.append(item);
581
582             m_sys_menu.append(new Gtk.SeparatorMenuItem());
583
584             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.REFRESH, null);
585             item.set_label(_("Restart"));
586             item.activate.connect((i) => m_bus.exit(true));
587             m_sys_menu.append(item);
588
589             item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.QUIT, null);
590             item.activate.connect((i) => m_bus.exit(false));
591             m_sys_menu.append(item);
592
593             m_sys_menu.show_all();
594         }
595
596         m_sys_menu.popup(null,
597                          null,
598                          m_status_icon.position_menu,
599                          0,
600                          Gtk.get_current_event_time());
601     }
602
603     private void status_icon_activate_cb(Gtk.StatusIcon status_icon) {
604         m_ime_menu = new Gtk.Menu();
605
606         // Show properties and IME switching menu
607         m_property_manager.create_menu_items(m_ime_menu);
608
609         m_ime_menu.append(new Gtk.SeparatorMenuItem());
610
611         // Append IMEs
612         foreach (var engine in m_engines) {
613             var language = engine.get_language();
614             var longname = engine.get_longname();
615             var item = new Gtk.ImageMenuItem.with_label(
616                 "%s - %s".printf (IBus.get_language_name(language), longname));
617             if (engine.get_icon() != "") {
618                 var icon = new IconWidget(engine.get_icon(), Gtk.IconSize.MENU);
619                  item.set_image(icon);
620             }
621             // Make a copy of engine to workaround a bug in vala.
622             // https://bugzilla.gnome.org/show_bug.cgi?id=628336
623             var e = engine;
624             item.activate.connect((item) => {
625                 for (int i = 0; i < m_engines.length; i++) {
626                     if (e == m_engines[i]) {
627                         switch_engine(i);
628                         break;
629                     }
630                 }
631             });
632             m_ime_menu.add(item);
633         }
634
635         m_ime_menu.show_all();
636
637         // Do not take focuse to avoid some focus related issues.
638         m_ime_menu.set_take_focus(false);
639         m_ime_menu.popup(null,
640                          null,
641                          m_status_icon.position_menu,
642                          0,
643                          Gtk.get_current_event_time());
644     }
645
646     /* override virtual functions */
647     public override void set_cursor_location(int x, int y,
648                                              int width, int height) {
649         m_candidate_panel.set_cursor_location(x, y, width, height);
650     }
651
652     public override void focus_in(string input_context_path) {
653     }
654
655     public override void focus_out(string input_context_path) {
656     }
657
658     public override void register_properties(IBus.PropList props) {
659         m_property_manager.set_properties(props);
660     }
661
662     public override void update_property(IBus.Property prop) {
663         m_property_manager.update_property(prop);
664     }
665
666     public override void update_preedit_text(IBus.Text text,
667                                              uint cursor_pos,
668                                              bool visible) {
669         if (visible)
670             m_candidate_panel.set_preedit_text(text, cursor_pos);
671         else
672             m_candidate_panel.set_preedit_text(null, 0);
673     }
674
675     public override void hide_preedit_text() {
676         m_candidate_panel.set_preedit_text(null, 0);
677     }
678
679     public override void update_auxiliary_text(IBus.Text text,
680                                                bool visible) {
681         m_candidate_panel.set_auxiliary_text(visible ? text : null);
682     }
683
684     public override void hide_auxiliary_text() {
685         m_candidate_panel.set_auxiliary_text(null);
686     }
687
688     public override void update_lookup_table(IBus.LookupTable table,
689                                              bool visible) {
690         m_candidate_panel.set_lookup_table(visible ? table : null);
691     }
692
693     public override void hide_lookup_table() {
694         m_candidate_panel.set_lookup_table(null);
695     }
696
697     public override void state_changed() {
698         var icon_name = "ibus-keyboard";
699
700         var engine = m_bus.get_global_engine();
701         if (engine != null)
702             icon_name = engine.get_icon();
703
704         if (icon_name[0] == '/')
705             m_status_icon.set_from_file(icon_name);
706         else {
707             var theme = Gtk.IconTheme.get_default();
708             if (theme.lookup_icon(icon_name, 48, 0) != null) {
709                 m_status_icon.set_from_icon_name(icon_name);
710             } else {
711                 m_status_icon.set_from_icon_name("ibus-engine");
712             }
713         }
714
715         if (engine == null)
716             return;
717
718         int i;
719         for (i = 0; i < m_engines.length; i++) {
720             if (m_engines[i].get_name() == engine.get_name())
721                 break;
722         }
723
724         // engine is first engine in m_engines.
725         if (i == 0)
726             return;
727
728         // engine is not in m_engines.
729         if (i >= m_engines.length)
730             return;
731
732         for (int j = i; j > 0; j--) {
733             m_engines[j] = m_engines[j - 1];
734         }
735         m_engines[0] = engine;
736
737         string[] names = {};
738         foreach(var desc in m_engines) {
739             names += desc.get_name();
740         }
741         if (m_config != null)
742             m_config.set_value("general",
743                                "engines_order",
744                                new GLib.Variant.strv(names));
745     }
746 }