1 /* vim:set et sts=4 sw=4:
5 * Copyright(c) 2011 Peng Huang <shawn.p.huang@gmail.com>
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.
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.
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
23 class Panel : IBus.PanelService {
24 private class Keybinding {
25 public Keybinding(uint keysym,
26 Gdk.ModifierType modifiers,
29 this.modifiers = modifiers;
30 this.reverse = reverse;
33 public uint keysym { get; set; }
34 public Gdk.ModifierType modifiers { get; set; }
35 public bool reverse { get; set; }
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 = "<Super>space";
54 private GLib.List<Keybinding> m_keybindings = new GLib.List<Keybinding>();
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");
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");
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());
76 m_switcher = new Switcher();
77 // The initial shortcut is "<Super>space"
78 bind_switch_shortcut(null);
80 if (m_switcher_delay_time >= 0) {
81 m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
84 m_property_manager = new PropertyManager();
85 m_property_manager.property_activate.connect((k, s) => {
86 property_activate(k, s);
93 unbind_switch_shortcut();
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;
103 Gtk.accelerator_parse(accelerator,
104 out switch_keysym, out switch_modifiers);
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
115 Gdk.Keymap.get_default().map_virtual_modifiers(
116 ref switch_modifiers);
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;
123 switch_modifiers &= ~VIRTUAL_MODIFIERS;
126 if (switch_keysym == 0 && switch_modifiers == 0) {
127 warning("Parse accelerator '%s' failed!", accelerator);
131 keybinding = new Keybinding(switch_keysym,
134 m_keybindings.append(keybinding);
136 keybinding_manager.bind(switch_keysym, switch_modifiers,
137 (e) => handle_engine_switch(e, false));
139 // accelerator already has Shift mask
140 if ((switch_modifiers & reverse_modifier) != 0) {
144 switch_modifiers |= reverse_modifier;
146 keybinding = new Keybinding(switch_keysym,
149 m_keybindings.append(keybinding);
151 keybinding_manager.bind(switch_keysym, switch_modifiers,
152 (e) => handle_engine_switch(e, true));
155 private void bind_switch_shortcut(Variant? variant) {
156 string[] accelerators = {};
157 Variant var_trigger = variant;
159 if (var_trigger == null && m_config != null) {
160 var_trigger = m_config.get_value("general/hotkey",
164 if (var_trigger != null) {
165 accelerators = var_trigger.dup_strv();
167 accelerators += ACCELERATOR_SWITCH_IME_FOREWARD;
170 var keybinding_manager = KeybindingManager.get_instance();
172 foreach (var accelerator in accelerators) {
173 keybinding_manager_bind(keybinding_manager, accelerator);
177 private void unbind_switch_shortcut() {
178 var keybinding_manager = KeybindingManager.get_instance();
180 unowned GLib.List<Keybinding> keybindings = m_keybindings;
182 while (keybindings != null) {
183 Keybinding keybinding = keybindings.data;
185 keybinding_manager.unbind(keybinding.keysym,
186 keybinding.modifiers);
187 keybindings = keybindings.next;
190 m_keybindings = null;
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;
198 if (screen == null) {
199 warning("Could not open display.");
203 bool use_custom_font = false;
204 GLib.Variant var_use_custom_font = m_config.get_value("panel",
207 if (var_use_custom_font != null) {
208 use_custom_font = var_use_custom_font.get_boolean();
211 if (m_css_provider != null) {
212 Gtk.StyleContext.remove_provider_for_screen(screen,
214 m_css_provider = null;
217 if (use_custom_font == false) {
221 string font_name = null;
222 GLib.Variant var_custom_font = m_config.get_value("panel",
224 if (var_custom_font != null) {
225 font_name = var_custom_font.dup_string();
228 if (font_name == null) {
229 warning("No config panel:custom_font.");
233 string data_format = "GtkLabel { font: %s; }";
234 string data = data_format.printf(font_name);
235 m_css_provider = new Gtk.CssProvider();
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,
245 Gtk.StyleContext.add_provider_for_screen(screen,
247 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
250 private void set_switcher_delay_time(Variant? variant) {
251 Variant var_switcher_delay_time = variant;
253 if (var_switcher_delay_time == null) {
254 var_switcher_delay_time = m_config.get_value("general",
255 "switcher-delay-time");
258 if (var_switcher_delay_time == null) {
262 m_switcher_delay_time = var_switcher_delay_time.get_int32();
264 if (m_switcher_delay_time >= 0) {
265 m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
269 private void set_use_system_keyboard_layout(Variant? variant) {
270 Variant var_use_system_kbd_layout = variant;
272 if (var_use_system_kbd_layout == null) {
273 var_use_system_kbd_layout = m_config.get_value(
275 "use_system_keyboard_layout");
278 if (var_use_system_kbd_layout == null) {
282 m_use_system_keyboard_layout = var_use_system_kbd_layout.get_boolean();
285 private void set_embed_preedit_text(Variant? variant) {
286 Variant var_embed_preedit = variant;
288 if (var_embed_preedit == null) {
289 var_embed_preedit = m_config.get_value("general",
290 "embed_preedit_text");
293 if (var_embed_preedit == null) {
297 m_bus.set_ibus_property("EmbedPreeditText",
301 public void set_config(IBus.Config config) {
302 if (m_config != null) {
303 m_config.value_changed.disconnect(config_value_changed_cb);
304 m_config.watch(null, null);
309 if (m_config != null) {
310 m_config.value_changed.connect(config_value_changed_cb);
311 m_config.watch("general", "preload_engines");
312 m_config.watch("general", "embed_preedit_text");
313 m_config.watch("general", "engines_order");
314 m_config.watch("general", "switcher_delay_time");
315 m_config.watch("general", "use_system_keyboard_layout");
316 m_config.watch("general/hotkey", "triggers");
317 m_config.watch("panel", "custom_font");
318 m_config.watch("panel", "use_custom_font");
319 // Update m_use_system_keyboard_layout before update_engines()
321 set_use_system_keyboard_layout(null);
322 update_engines(m_config.get_value("general", "preload_engines"),
323 m_config.get_value("general", "engines_order"));
324 unbind_switch_shortcut();
325 bind_switch_shortcut(null);
326 set_switcher_delay_time(null);
327 set_embed_preedit_text(null);
329 update_engines(null, null);
335 private void exec_setxkbmap(IBus.EngineDesc engine) {
336 string layout = engine.get_layout();
337 string variant = engine.get_layout_variant();
338 string option = engine.get_layout_option();
339 string standard_error = null;
341 string[] args = { "setxkbmap" };
343 if (layout != null && layout != "" && layout != "default") {
347 if (variant != null && variant != "" && variant != "default") {
351 if (option != null && option != "" && option != "default") {
352 /*TODO: Need to get the session XKB options */
358 if (args.length == 1) {
363 if (!GLib.Process.spawn_sync(null, args, null,
364 GLib.SpawnFlags.SEARCH_PATH,
368 warning("Switch xkb layout to %s failed.",
369 engine.get_layout());
371 } catch (GLib.SpawnError e) {
372 warning("Execute setxkbmap failed: %s", e.message);
375 if (exit_status != 0) {
376 warning("Execute setxkbmap failed: %s", standard_error ?? "(null)");
380 private void switch_engine(int i, bool force = false) {
381 GLib.assert(i >= 0 && i < m_engines.length);
383 // Do not need switch
384 if (i == 0 && !force)
387 IBus.EngineDesc engine = m_engines[i];
389 if (!m_bus.set_global_engine(engine.get_name())) {
390 warning("Switch engine to %s failed.", engine.get_name());
394 if (!m_use_system_keyboard_layout) {
395 exec_setxkbmap(engine);
399 private void config_value_changed_cb(IBus.Config config,
403 if (section == "general" && name == "preload_engines") {
404 update_engines(variant, null);
408 if (section == "general/hotkey" && name == "triggers") {
409 unbind_switch_shortcut();
410 bind_switch_shortcut(variant);
414 if (section == "panel" && (name == "custom_font" ||
415 name == "use_custom_font")) {
420 if (section == "general" && name == "switcher_delay_time") {
421 set_switcher_delay_time(variant);
425 if (section == "general" && name == "use_system_keyboard_layout") {
426 set_use_system_keyboard_layout(variant);
430 if (section == "general" && name == "embed_preedit_text") {
431 set_embed_preedit_text(variant);
436 private void handle_engine_switch(Gdk.Event event, bool revert) {
437 // Do not need switch IME
438 if (m_engines.length <= 1)
441 uint keyval = event.key.keyval;
442 uint modifiers = KeybindingManager.MODIFIER_FILTER & event.key.state;
444 uint primary_modifiers =
445 KeybindingManager.get_primary_modifier(event.key.state);
447 bool pressed = KeybindingManager.primary_modifier_still_pressed(
448 event, primary_modifiers);
451 modifiers &= ~Gdk.ModifierType.SHIFT_MASK;
454 if (pressed && m_switcher_delay_time >= 0) {
455 int i = revert ? m_engines.length - 1 : 1;
456 i = m_switcher.run(keyval, modifiers, event, m_engines, i);
458 debug("switch cancelled");
460 GLib.assert(i < m_engines.length);
464 int i = revert ? m_engines.length - 1 : 1;
469 private void run_preload_engines(IBus.EngineDesc[] engines, int index) {
472 if (engines.length <= index) {
476 names += engines[index].get_name();
477 m_bus.preload_engines_async(names, -1, null);
480 private void update_engines(GLib.Variant? var_engines,
481 GLib.Variant? var_order) {
482 string[] engine_names = null;
484 if (var_engines != null)
485 engine_names = var_engines.dup_strv();
486 if (engine_names == null || engine_names.length == 0)
487 engine_names = {"xkb:us::eng"};
489 string[] order_names =
490 (var_order != null) ? var_order.dup_strv() : null;
494 foreach (var name in order_names) {
495 if (name in engine_names)
499 foreach (var name in engine_names) {
505 var engines = m_bus.get_engines_by_names(names);
507 if (m_engines.length == 0) {
509 switch_engine(0, true);
510 run_preload_engines(engines, 1);
512 var current_engine = m_engines[0];
515 for (i = 0; i < m_engines.length; i++) {
516 if (current_engine.get_name() == engines[i].get_name()) {
519 run_preload_engines(engines, 0);
521 run_preload_engines(engines, 1);
526 switch_engine(0, true);
527 run_preload_engines(engines, 1);
532 private void show_setup_dialog() {
533 if (m_setup_pid != 0) {
534 if (Posix.kill(m_setup_pid, Posix.SIGUSR1) == 0)
539 string binary = GLib.Path.build_filename(Config.BINDIR, "ibus-setup");
541 GLib.Process.spawn_async(null,
542 {binary, "ibus-setup"},
544 GLib.SpawnFlags.DO_NOT_REAP_CHILD,
547 } catch (GLib.SpawnError e) {
548 warning("Execute %s failed! %s", binary, e.message);
552 GLib.ChildWatch.add(m_setup_pid, (pid, state) => {
553 if (pid != m_setup_pid)
556 GLib.Process.close_pid(pid);
560 private void show_about_dialog() {
561 if (m_about_dialog == null) {
562 m_about_dialog = new Gtk.AboutDialog();
563 m_about_dialog.set_program_name("IBus");
564 m_about_dialog.set_version(Config.PACKAGE_VERSION);
566 string copyright = _(
567 "Copyright (c) 2007-2012 Peng Huang\n" +
568 "Copyright (c) 2007-2010 Red Hat, Inc.\n");
570 m_about_dialog.set_copyright(copyright);
571 m_about_dialog.set_license("LGPL");
572 m_about_dialog.set_comments(_("IBus is an intelligent input bus for Linux/Unix."));
573 m_about_dialog.set_website("http://code.google.com/p/ibus");
574 m_about_dialog.set_authors({"Peng Huang <shawn.p.huang@gmail.com>"});
575 m_about_dialog.set_documenters({"Peng Huang <shawn.p.huang@gmail.com>"});
576 m_about_dialog.set_translator_credits(_("translator-credits"));
577 m_about_dialog.set_logo_icon_name("ibus");
578 m_about_dialog.set_icon_name("ibus");
581 if (!m_about_dialog.get_visible()) {
582 m_about_dialog.run();
583 m_about_dialog.hide();
585 m_about_dialog.present();
589 private void status_icon_popup_menu_cb(Gtk.StatusIcon status_icon,
591 uint activate_time) {
593 if (m_sys_menu == null) {
594 Gtk.ImageMenuItem item;
595 m_sys_menu = new Gtk.Menu();
597 item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.PREFERENCES, null);
598 item.activate.connect((i) => show_setup_dialog());
599 m_sys_menu.append(item);
601 item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.ABOUT, null);
602 item.activate.connect((i) => show_about_dialog());
603 m_sys_menu.append(item);
605 m_sys_menu.append(new Gtk.SeparatorMenuItem());
607 item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.REFRESH, null);
608 item.set_label(_("Restart"));
609 item.activate.connect((i) => m_bus.exit(true));
610 m_sys_menu.append(item);
612 item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.QUIT, null);
613 item.activate.connect((i) => m_bus.exit(false));
614 m_sys_menu.append(item);
616 m_sys_menu.show_all();
619 m_sys_menu.popup(null,
621 m_status_icon.position_menu,
623 Gtk.get_current_event_time());
626 private void status_icon_activate_cb(Gtk.StatusIcon status_icon) {
627 m_ime_menu = new Gtk.Menu();
629 // Show properties and IME switching menu
630 m_property_manager.create_menu_items(m_ime_menu);
632 m_ime_menu.append(new Gtk.SeparatorMenuItem());
635 foreach (var engine in m_engines) {
636 var language = engine.get_language();
637 var longname = engine.get_longname();
638 var item = new Gtk.ImageMenuItem.with_label(
639 "%s - %s".printf (IBus.get_language_name(language), longname));
640 if (engine.get_icon() != "") {
641 var icon = new IconWidget(engine.get_icon(), Gtk.IconSize.MENU);
642 item.set_image(icon);
644 // Make a copy of engine to workaround a bug in vala.
645 // https://bugzilla.gnome.org/show_bug.cgi?id=628336
647 item.activate.connect((item) => {
648 for (int i = 0; i < m_engines.length; i++) {
649 if (e == m_engines[i]) {
655 m_ime_menu.add(item);
658 m_ime_menu.show_all();
660 // Do not take focuse to avoid some focus related issues.
661 m_ime_menu.set_take_focus(false);
662 m_ime_menu.popup(null,
664 m_status_icon.position_menu,
666 Gtk.get_current_event_time());
669 /* override virtual functions */
670 public override void set_cursor_location(int x, int y,
671 int width, int height) {
672 m_candidate_panel.set_cursor_location(x, y, width, height);
675 public override void focus_in(string input_context_path) {
678 public override void focus_out(string input_context_path) {
681 public override void register_properties(IBus.PropList props) {
682 m_property_manager.set_properties(props);
685 public override void update_property(IBus.Property prop) {
686 m_property_manager.update_property(prop);
689 public override void update_preedit_text(IBus.Text text,
693 m_candidate_panel.set_preedit_text(text, cursor_pos);
695 m_candidate_panel.set_preedit_text(null, 0);
698 public override void hide_preedit_text() {
699 m_candidate_panel.set_preedit_text(null, 0);
702 public override void update_auxiliary_text(IBus.Text text,
704 m_candidate_panel.set_auxiliary_text(visible ? text : null);
707 public override void hide_auxiliary_text() {
708 m_candidate_panel.set_auxiliary_text(null);
711 public override void update_lookup_table(IBus.LookupTable table,
713 m_candidate_panel.set_lookup_table(visible ? table : null);
716 public override void hide_lookup_table() {
717 m_candidate_panel.set_lookup_table(null);
720 public override void state_changed() {
721 var icon_name = "ibus-keyboard";
723 var engine = m_bus.get_global_engine();
725 icon_name = engine.get_icon();
727 if (icon_name[0] == '/')
728 m_status_icon.set_from_file(icon_name);
730 var theme = Gtk.IconTheme.get_default();
731 if (theme.lookup_icon(icon_name, 48, 0) != null) {
732 m_status_icon.set_from_icon_name(icon_name);
734 m_status_icon.set_from_icon_name("ibus-engine");
742 for (i = 0; i < m_engines.length; i++) {
743 if (m_engines[i].get_name() == engine.get_name())
747 // engine is first engine in m_engines.
751 // engine is not in m_engines.
752 if (i >= m_engines.length)
755 for (int j = i; j > 0; j--) {
756 m_engines[j] = m_engines[j - 1];
758 m_engines[0] = engine;
761 foreach(var desc in m_engines) {
762 names += desc.get_name();
764 if (m_config != null)
765 m_config.set_value("general",
767 new GLib.Variant.strv(names));