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
28 public extern const string IBUS_VERSION;
29 public extern const string BINDIR;
31 class Panel : IBus.PanelService {
32 private IBus.Bus m_bus;
33 private IBus.Config m_config;
34 private Gtk.StatusIcon m_status_icon;
35 private Gtk.Menu m_ime_menu;
36 private Gtk.Menu m_sys_menu;
37 private IBus.EngineDesc[] m_engines = {};
38 private CandidatePanel m_candidate_panel;
39 private Switcher m_switcher;
40 private PropertyManager m_property_manager;
41 private GLib.Pid m_setup_pid = 0;
42 private Gtk.AboutDialog m_about_dialog;
43 private Gtk.CssProvider m_css_provider;
44 private const string ACCELERATOR_SWITCH_IME_FOREWARD = "<Control>space";
46 private uint m_switch_keysym = 0;
47 private Gdk.ModifierType m_switch_modifiers = 0;
49 public Panel(IBus.Bus bus) {
50 GLib.assert(bus.is_connected());
51 // Chain up base class constructor
52 GLib.Object(connection : bus.get_connection(),
53 object_path : "/org/freedesktop/IBus/Panel");
58 m_status_icon = new Gtk.StatusIcon();
59 m_status_icon.set_name("ibus-ui-gtk");
60 m_status_icon.set_title("IBus Panel");
61 m_status_icon.popup_menu.connect(status_icon_popup_menu_cb);
62 m_status_icon.activate.connect(status_icon_activate_cb);
63 m_status_icon.set_from_icon_name("ibus-keyboard");
65 m_candidate_panel = new CandidatePanel();
66 m_candidate_panel.page_up.connect((w) => this.page_up());
67 m_candidate_panel.page_down.connect((w) => this.page_down());
69 m_switcher = new Switcher();
70 bind_switch_shortcut();
72 m_property_manager = new PropertyManager();
73 m_property_manager.property_activate.connect((k, s) => {
74 property_activate(k, s);
81 unbind_switch_shortcut();
84 private void bind_switch_shortcut() {
85 var keybinding_manager = KeybindingManager.get_instance();
87 var accelerator = ACCELERATOR_SWITCH_IME_FOREWARD;
88 Gtk.accelerator_parse(accelerator,
89 out m_switch_keysym, out m_switch_modifiers);
91 // Map virtual modifiers to (i.e.Mod2, Mod3, ...)
92 const Gdk.ModifierType VIRTUAL_MODIFIERS = (
93 Gdk.ModifierType.SUPER_MASK |
94 Gdk.ModifierType.HYPER_MASK |
95 Gdk.ModifierType.META_MASK);
96 if ((m_switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
97 // workaround a bug in gdk vapi vala > 0.18
98 // https://bugzilla.gnome.org/show_bug.cgi?id=677559
100 Gdk.Keymap.get_default().map_virtual_modifiers(
101 ref m_switch_modifiers);
103 if ((m_switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
104 m_switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
105 if ((m_switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
106 m_switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
108 m_switch_modifiers &= ~VIRTUAL_MODIFIERS;
111 if (m_switch_keysym == 0 && m_switch_modifiers == 0) {
112 warning("Parse accelerator '%s' failed!", accelerator);
116 keybinding_manager.bind(m_switch_keysym, m_switch_modifiers,
117 (e) => handle_engine_switch(e, false));
119 // accelerator already has Shift mask
120 if ((m_switch_modifiers & Gdk.ModifierType.SHIFT_MASK) != 0)
123 keybinding_manager.bind(m_switch_keysym,
124 m_switch_modifiers | Gdk.ModifierType.SHIFT_MASK,
125 (e) => handle_engine_switch(e, true));
128 private void unbind_switch_shortcut() {
129 var keybinding_manager = KeybindingManager.get_instance();
131 if (m_switch_keysym == 0 && m_switch_modifiers == 0)
134 keybinding_manager.unbind(m_switch_keysym, m_switch_modifiers);
135 keybinding_manager.unbind(m_switch_keysym,
136 m_switch_modifiers | Gdk.ModifierType.SHIFT_MASK);
139 m_switch_modifiers = 0;
142 private void set_custom_font() {
143 Gdk.Display display = Gdk.Display.get_default();
144 Gdk.Screen screen = (display != null) ?
145 display.get_default_screen() : null;
147 if (screen == null) {
148 warning("Could not open display.");
152 bool use_custom_font = false;
153 GLib.Variant var_use_custom_font = m_config.get_value("panel",
156 if (var_use_custom_font != null) {
157 use_custom_font = var_use_custom_font.get_boolean();
160 if (m_css_provider != null) {
161 Gtk.StyleContext.remove_provider_for_screen(screen,
163 m_css_provider = null;
166 if (use_custom_font == false) {
170 string font_name = null;
171 GLib.Variant var_custom_font = m_config.get_value("panel",
173 if (var_custom_font != null) {
174 font_name = var_custom_font.dup_string();
177 if (font_name == null) {
178 warning("No config panel:custom_font.");
182 string data_format = "GtkLabel { font: %s; }";
183 string data = data_format.printf(font_name);
184 m_css_provider = new Gtk.CssProvider();
187 m_css_provider.load_from_data(data, -1);
188 } catch (GLib.Error e) {
189 warning("Failed css_provider_from_data: %s: %s", font_name,
194 Gtk.StyleContext.add_provider_for_screen(screen,
196 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
199 public void set_config(IBus.Config config) {
200 if (m_config != null) {
201 m_config.value_changed.disconnect(config_value_changed_cb);
202 m_config.watch(null, null);
207 if (m_config != null) {
208 m_config.value_changed.connect(config_value_changed_cb);
209 m_config.watch("general", "preload_engines");
210 m_config.watch("general", "engines_order");
211 m_config.watch("panel", "custom_font");
212 m_config.watch("panel", "use_custom_font");
213 update_engines(m_config.get_value("general", "preload_engines"),
214 m_config.get_value("general", "engines_order"));
216 update_engines(null, null);
222 private void switch_engine(int i, bool force = false) {
223 GLib.assert(i >= 0 && i < m_engines.length);
225 // Do not need switch
226 if (i == 0 && !force)
229 IBus.EngineDesc engine = m_engines[i];
231 if (!m_bus.set_global_engine(engine.get_name())) {
232 warning("Switch engine to %s failed.", engine.get_name());
236 string cmdline = "setxkbmap %s".printf(engine.get_layout());
238 if (!GLib.Process.spawn_command_line_sync(cmdline)) {
239 warning("Switch xkb layout to %s failed.",
240 engine.get_layout());
242 } catch (GLib.SpawnError e) {
243 warning("execute setxkblayout failed");
247 private void config_value_changed_cb(IBus.Config config,
251 if (section == "general" && name == "preload_engines") {
252 update_engines(variant, null);
256 if (section == "panel" && (name == "custom_font" ||
257 name == "use_custom_font")) {
263 private void handle_engine_switch(Gdk.Event event, bool revert) {
264 // Do not need switch IME
265 if (m_engines.length <= 1)
268 uint primary_modifiers =
269 KeybindingManager.get_primary_modifier(event.key.state);
271 bool pressed = KeybindingManager.primary_modifier_still_pressed(
272 event, primary_modifiers);
274 int i = revert ? m_engines.length - 1 : 1;
275 i = m_switcher.run(m_switch_keysym, m_switch_modifiers, event,
278 debug("switch cancelled");
280 GLib.assert(i < m_engines.length);
284 int i = revert ? m_engines.length - 1 : 1;
289 private void update_engines(GLib.Variant? var_engines,
290 GLib.Variant? var_order) {
291 string[] engine_names = null;
293 if (var_engines != null)
294 engine_names = var_engines.dup_strv();
295 if (engine_names == null || engine_names.length == 0)
296 engine_names = {"xkb:us::eng"};
298 string[] order_names =
299 (var_order != null) ? var_order.dup_strv() : null;
303 foreach (var name in order_names) {
304 if (name in engine_names)
308 foreach (var name in engine_names) {
314 var engines = m_bus.get_engines_by_names(names);
316 if (m_engines.length == 0) {
318 switch_engine(0, true);
320 var current_engine = m_engines[0];
323 for (i = 0; i < m_engines.length; i++) {
324 if (current_engine.get_name() == engines[i].get_name()) {
329 switch_engine(0, true);
334 private void show_setup_dialog() {
335 if (m_setup_pid != 0) {
336 if (Posix.kill(m_setup_pid, Posix.SIGUSR1) == 0)
341 string binary = GLib.Path.build_filename(BINDIR, "ibus-setup");
343 GLib.Process.spawn_async(null,
344 {binary, "ibus-setup"},
346 GLib.SpawnFlags.DO_NOT_REAP_CHILD,
349 } catch (GLib.SpawnError e) {
350 warning("Execute %s failed! %s", binary, e.message);
354 GLib.ChildWatch.add(m_setup_pid, (pid, state) => {
355 if (pid != m_setup_pid)
358 GLib.Process.close_pid(pid);
362 private void show_about_dialog() {
363 if (m_about_dialog == null) {
364 m_about_dialog = new Gtk.AboutDialog();
365 m_about_dialog.set_program_name("IBus");
366 m_about_dialog.set_version(IBUS_VERSION);
368 string copyright = _(
369 "Copyright (c) 2007-2012 Peng Huang\n" +
370 "Copyright (c) 2007-2010 Red Hat, Inc.\n");
372 m_about_dialog.set_copyright(copyright);
373 m_about_dialog.set_license("LGPL");
374 m_about_dialog.set_comments(_("IBus is an intelligent input bus for Linux/Unix."));
375 m_about_dialog.set_website("http://code.google.com/p/ibus");
376 m_about_dialog.set_authors({"Peng Huang <shawn.p.huang@gmail.com>"});
377 m_about_dialog.set_documenters({"Peng Huang <shawn.p.huang@gmail.com>"});
378 m_about_dialog.set_translator_credits(_("translator-credits"));
379 m_about_dialog.set_logo_icon_name("ibus");
380 m_about_dialog.set_icon_name("ibus");
383 if (!m_about_dialog.get_visible()) {
384 m_about_dialog.run();
385 m_about_dialog.hide();
387 m_about_dialog.present();
391 private void status_icon_popup_menu_cb(Gtk.StatusIcon status_icon,
393 uint activate_time) {
395 if (m_sys_menu == null) {
396 Gtk.ImageMenuItem item;
397 m_sys_menu = new Gtk.Menu();
399 item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.PREFERENCES, null);
400 item.activate.connect((i) => show_setup_dialog());
401 m_sys_menu.append(item);
403 item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.ABOUT, null);
404 item.activate.connect((i) => show_about_dialog());
405 m_sys_menu.append(item);
407 m_sys_menu.append(new SeparatorMenuItem());
409 item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.REFRESH, null);
410 item.set_label(_("Restart"));
411 item.activate.connect((i) => m_bus.exit(true));
412 m_sys_menu.append(item);
414 item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.QUIT, null);
415 item.activate.connect((i) => m_bus.exit(false));
416 m_sys_menu.append(item);
418 m_sys_menu.show_all();
421 m_sys_menu.popup(null,
423 m_status_icon.position_menu,
425 Gtk.get_current_event_time());
428 private void status_icon_activate_cb(Gtk.StatusIcon status_icon) {
429 m_ime_menu = new Gtk.Menu();
431 // Show properties and IME switching menu
432 m_property_manager.create_menu_items(m_ime_menu);
434 m_ime_menu.append(new SeparatorMenuItem());
437 Gtk.icon_size_lookup(Gtk.IconSize.MENU, out width, out height);
440 foreach (var engine in m_engines) {
441 var language = engine.get_language();
442 var longname = engine.get_longname();
443 var item = new Gtk.ImageMenuItem.with_label(
444 "%s - %s".printf (IBus.get_language_name(language), longname));
445 if (engine.get_icon() != "") {
446 var icon = new IconWidget(engine.get_icon(), width);
447 item.set_image(icon);
449 // Make a copy of engine to workaround a bug in vala.
450 // https://bugzilla.gnome.org/show_bug.cgi?id=628336
452 item.activate.connect((item) => {
453 for (int i = 0; i < m_engines.length; i++) {
454 if (e == m_engines[i]) {
460 m_ime_menu.add(item);
463 m_ime_menu.show_all();
465 // Do not take focuse to avoid some focus related issues.
466 m_ime_menu.set_take_focus(false);
467 m_ime_menu.popup(null,
469 m_status_icon.position_menu,
471 Gtk.get_current_event_time());
474 /* override virtual functions */
475 public override void set_cursor_location(int x, int y,
476 int width, int height) {
477 m_candidate_panel.set_cursor_location(x, y, width, height);
480 public override void focus_in(string input_context_path) {
483 public override void focus_out(string input_context_path) {
486 public override void register_properties(IBus.PropList props) {
487 m_property_manager.set_properties(props);
490 public override void update_property(IBus.Property prop) {
491 m_property_manager.update_property(prop);
494 public override void update_preedit_text(IBus.Text text,
498 m_candidate_panel.set_preedit_text(text, cursor_pos);
500 m_candidate_panel.set_preedit_text(null, 0);
503 public override void hide_preedit_text() {
504 m_candidate_panel.set_preedit_text(null, 0);
507 public override void update_auxiliary_text(IBus.Text text,
509 m_candidate_panel.set_auxiliary_text(visible ? text : null);
512 public override void hide_auxiliary_text() {
513 m_candidate_panel.set_auxiliary_text(null);
516 public override void update_lookup_table(IBus.LookupTable table,
518 m_candidate_panel.set_lookup_table(visible ? table : null);
521 public override void hide_lookup_table() {
522 m_candidate_panel.set_lookup_table(null);
525 public override void state_changed() {
526 var icon_name = "ibus-keyboard";
528 var engine = m_bus.get_global_engine();
530 icon_name = engine.get_icon();
532 if (icon_name[0] == '/')
533 m_status_icon.set_from_file(icon_name);
535 m_status_icon.set_from_icon_name(icon_name);
541 for (i = 0; i < m_engines.length; i++) {
542 if (m_engines[i].get_name() == engine.get_name())
546 // engine is first engine in m_engines.
550 // engine is not in m_engines.
551 if (i >= m_engines.length)
554 for (int j = i; j > 0; j--) {
555 m_engines[j] = m_engines[j - 1];
557 m_engines[0] = engine;
560 foreach(var desc in m_engines) {
561 names += desc.get_name();
563 if (m_config != null)
564 m_config.set_value("general",
566 new GLib.Variant.strv(names));