diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 16 | ||||
-rw-r--r-- | lib/ibus-menu.vala | 317 | ||||
-rw-r--r-- | lib/ibus-panel.vala | 26 | ||||
-rw-r--r-- | lib/indicator-menu.vala | 144 | ||||
-rw-r--r-- | lib/keyboard-plugin.vala | 23 | ||||
-rw-r--r-- | lib/main.vala | 423 | ||||
-rw-r--r-- | lib/source.vala | 21 | ||||
-rw-r--r-- | lib/unity-session.vala | 24 |
8 files changed, 898 insertions, 96 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 1d23aac8..c57725c1 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -2,13 +2,19 @@ pkglibexec_PROGRAMS = indicator-keyboard-service AM_CFLAGS = -w -DGNOME_DESKTOP_USE_UNSTABLE_API AM_LDFLAGS = -lm -AM_VALAFLAGS = --metadatadir $(top_srcdir)/deps \ +AM_VALAFLAGS = --enable-experimental-non-null \ + --metadatadir $(top_srcdir)/deps \ --vapidir $(top_srcdir)/deps -indicator_keyboard_service_SOURCES = main.vala \ - source.vala \ - common.vala \ - window-stack.vala \ +indicator_keyboard_service_SOURCES = main.vala \ + source.vala \ + common.vala \ + ibus-menu.vala \ + ibus-panel.vala \ + indicator-menu.vala \ + keyboard-plugin.vala \ + window-stack.vala \ + unity-session.vala \ unity-greeter.vala indicator_keyboard_service_VALAFLAGS = $(AM_VALAFLAGS) \ --pkg gee-1.0 \ diff --git a/lib/ibus-menu.vala b/lib/ibus-menu.vala new file mode 100644 index 00000000..c0862ac3 --- /dev/null +++ b/lib/ibus-menu.vala @@ -0,0 +1,317 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: William Hua <william.hua@canonical.com> + */ + +public class Indicator.Keyboard.IBusMenu : MenuModel { + + private static uint radio_counter = 0; + + private IBus.PropList? properties; + + private Menu menu; + private ActionMap? action_map; + + private string? radio_name; + private SimpleAction? radio_action; + private Gee.HashMap<string, IBus.Property> radio_properties; + + /* A list of the action names this menu registers. */ + private Gee.LinkedList<string> names; + + public IBusMenu (ActionMap? action_map = null, IBus.PropList? properties = null) { + menu = new Menu (); + + menu.items_changed.connect ((position, removed, added) => { + items_changed (position, removed, added); + }); + + names = new Gee.LinkedList<string> (); + set_action_map (action_map); + set_properties (properties); + } + + ~IBusMenu () { + remove_actions (); + } + + public signal void activate (IBus.Property property, IBus.PropState state); + + private string get_action_name (string key) { + string name; + + if (!action_name_is_valid (key)) { + var builder = new StringBuilder.sized (key.length + 1); + + unichar letter = 0; + int index = 0; + + while (key.get_next_char (ref index, out letter)) { + if (letter == '-' || letter == '.' || letter.isalnum ()) { + builder.append_unichar (letter); + } else { + builder.append_c ('-'); + } + } + + name = @"ibus-$(builder.str)"; + } else { + name = @"ibus-$key"; + } + + /* Find an unused action name using a counter. */ + if (action_map != null && (Action?) ((!) action_map).lookup_action (name) != null) { + var i = 0; + var unique_name = @"$name-$i"; + + while ((Action?) ((!) action_map).lookup_action (unique_name) != null) { + i++; + unique_name = @"$name-$i"; + } + + name = unique_name; + } + + return name; + } + + private string? get_label (IBus.Property property) { + string? label = null; + + if ((IBus.Text?) property.label != null) { + label = property.label.text; + } + + if (label == null && (IBus.Text?) property.symbol != null) { + label = property.symbol.text; + } + + return label; + } + + private void append_normal_property (IBus.Property property) { + if (property.prop_type == IBus.PropType.NORMAL) { + if ((string?) property.key != null) { + var name = get_action_name (property.key); + + if (action_map != null) { + var action = new SimpleAction (name, null); + action.activate.connect ((parameter) => { activate (property, property.state); }); + ((!) action_map).add_action (action); + names.add (name); + } + + menu.append (get_label (property), property.sensitive ? @"indicator.$name" : "-private-disabled"); + } + } + } + + private void append_toggle_property (IBus.Property property) { + if (property.prop_type == IBus.PropType.TOGGLE) { + if ((string?) property.key != null) { + var name = get_action_name (property.key); + + if (action_map != null) { + var state = new Variant.boolean (property.state == IBus.PropState.CHECKED); + var action = new SimpleAction.stateful (name, null, state); + + action.activate.connect ((parameter) => { + action.change_state (new Variant.boolean (!action.get_state ().get_boolean ())); + }); + + action.change_state.connect ((value) => { + if (value != null) { + action.set_state ((!) value); + activate (property, ((!) value).get_boolean () ? IBus.PropState.CHECKED : IBus.PropState.UNCHECKED); + } + }); + + ((!) action_map).add_action (action); + names.add (name); + } + + menu.append (get_label (property), property.sensitive ? @"indicator.$name" : "-private-disabled"); + } + } + } + + private void append_radio_property (IBus.Property property) { + if (property.prop_type == IBus.PropType.RADIO) { + if ((string?) property.key != null) { + /* Create a single action for all radio properties. */ + if (action_map != null && radio_name == null) { + radio_counter++; + radio_name = @"-private-radio-$radio_counter"; + radio_action = new SimpleAction.stateful ((!) radio_name, VariantType.STRING, new Variant.string ("")); + + ((!) radio_action).activate.connect ((parameter) => { + ((!) radio_action).change_state (parameter); + }); + + ((!) radio_action).change_state.connect ((value) => { + if (value != null) { + var key = ((!) value).get_string (); + + if (radio_properties.has_key (key)) { + ((!) radio_action).set_state ((!) value); + activate (radio_properties[key], IBus.PropState.CHECKED); + } + } + }); + + ((!) action_map).add_action ((!) radio_action); + names.add ((!) radio_name); + } + + radio_properties[property.key] = property; + + if (property.state == IBus.PropState.CHECKED) { + ((!) radio_action).change_state (new Variant.string (property.key)); + } + + var item = new MenuItem (get_label (property), "-private-disabled"); + + if (property.sensitive) { + item.set_action_and_target_value (@"indicator.$((!) radio_name)", new Variant.string (property.key)); + } + + menu.append_item (item); + } + } + } + + private void append_menu_property (IBus.Property property) { + if (property.prop_type == IBus.PropType.MENU) { + var submenu = new IBusMenu (action_map, ((!) property).sub_props); + submenu.activate.connect ((property, state) => { activate (property, state); }); + menu.append_submenu (get_label (property), submenu); + } + } + + private void append_property (IBus.Property? property) { + if (property != null && ((!) property).visible) { + switch (((!) property).prop_type) { + case IBus.PropType.NORMAL: + append_normal_property ((!) property); + break; + + case IBus.PropType.TOGGLE: + append_toggle_property ((!) property); + break; + + case IBus.PropType.RADIO: + append_radio_property ((!) property); + break; + + case IBus.PropType.MENU: + append_menu_property ((!) property); + break; + + case IBus.PropType.SEPARATOR: + break; + } + } + } + + private void update_menu () { + /* Break reference cycle between action map and submenus. */ + for (var i = 0; i < menu.get_n_items (); i++) { + var submenu = menu.get_item_link (i, Menu.LINK_SUBMENU) as IBusMenu; + + if (submenu != null) { + ((!) submenu).remove_actions (); + } + } + + menu.remove_all (); + + if (properties != null) { + for (var i = 0; i < ((!) properties).properties.length; i++) { + append_property (((!) properties).get (i)); + } + } + } + + private void remove_actions () { + radio_action = null; + radio_name = null; + + if (action_map != null) { + foreach (var name in names) { + ((!) action_map).remove_action (name); + } + } + + names.clear (); + } + + public void set_action_map (ActionMap? action_map) { + if (action_map != this.action_map) { + remove_actions (); + this.action_map = action_map; + update_menu (); + } + } + + public void set_properties (IBus.PropList? properties) { + if (properties != this.properties) { + remove_actions (); + radio_properties = new Gee.HashMap<string, IBus.Property> (); + this.properties = properties; + update_menu (); + } + } + + public void update_property (IBus.Property property) { + remove_actions (); + radio_properties = new Gee.HashMap<string, IBus.Property> (); + update_menu (); + } + + /* Forward all menu model calls to our internal menu. */ + + public override Variant get_item_attribute_value (int item_index, string attribute, VariantType? expected_type) { + return menu.get_item_attribute_value (item_index, attribute, expected_type); + } + + public override void get_item_attributes (int item_index, out HashTable<string, Variant>? attributes) { + menu.get_item_attributes (item_index, out attributes); + } + + public override MenuModel get_item_link (int item_index, string link) { + return menu.get_item_link (item_index, link); + } + + public override void get_item_links (int item_index, out HashTable<string, MenuModel>? links) { + menu.get_item_links (item_index, out links); + } + + public override int get_n_items () { + return menu.get_n_items (); + } + + public override bool is_mutable () { + return menu.is_mutable (); + } + + public override MenuAttributeIter iterate_item_attributes (int item_index) { + return menu.iterate_item_attributes (item_index); + } + + public override MenuLinkIter iterate_item_links (int item_index) { + return menu.iterate_item_links (item_index); + } +} diff --git a/lib/ibus-panel.vala b/lib/ibus-panel.vala new file mode 100644 index 00000000..460b1eac --- /dev/null +++ b/lib/ibus-panel.vala @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: William Hua <william.hua@canonical.com> + */ + +[DBus (name="com.canonical.IBus.Panel.Private")] +public interface IBusPanel : Object { + + public abstract void activate_property (string name, uint state) throws IOError; + + public signal void properties_registered (Variant variant); + public signal void property_updated (Variant variant); +} diff --git a/lib/indicator-menu.vala b/lib/indicator-menu.vala new file mode 100644 index 00000000..8e5661e2 --- /dev/null +++ b/lib/indicator-menu.vala @@ -0,0 +1,144 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: William Hua <william.hua@canonical.com> + */ + +public class Indicator.Keyboard.IndicatorMenu : MenuModel { + + public enum Options { + NONE = 0x0, + DCONF = 0x1, + IBUS = 0x2, + SETTINGS = 0x4 + } + + private Options options; + + private Menu indicator_menu; + private Menu sources_section; + private IBusMenu properties_section; + + public IndicatorMenu (ActionMap? action_map = null, Options options = Options.NONE) { + var submenu = new Menu (); + + sources_section = new Menu (); + submenu.append_section (null, sources_section); + + if ((options & Options.IBUS) != Options.NONE) { + properties_section = new IBusMenu (action_map); + properties_section.activate.connect ((property, state) => { activate (property, state); }); + submenu.append_section (null, properties_section); + } + + if ((options & Options.SETTINGS) != Options.NONE) { + var settings_section = new Menu (); + settings_section.append (_ ("Character Map"), "indicator.map"); + settings_section.append (_ ("Keyboard Layout Chart"), "indicator.chart"); + settings_section.append (_ ("Text Entry Settings..."), "indicator.settings"); + submenu.append_section (null, settings_section); + } + + var indicator = new MenuItem.submenu (null, submenu); + indicator.set_detailed_action ("indicator.indicator"); + indicator.set_attribute ("x-canonical-type", "s", "com.canonical.indicator.root"); + + /* We need special mouse actions on the lock screen. */ + if ((options & Options.DCONF) != Options.NONE) { + indicator.set_attribute ("x-canonical-secondary-action", "s", "indicator.next"); + indicator.set_attribute ("x-canonical-scroll-action", "s", "indicator.scroll"); + } else { + indicator.set_attribute ("x-canonical-secondary-action", "s", "indicator.locked_next"); + indicator.set_attribute ("x-canonical-scroll-action", "s", "indicator.locked_scroll"); + } + + indicator_menu = new Menu (); + indicator_menu.append_item (indicator); + + this.options = options; + } + + public signal void activate (IBus.Property property, IBus.PropState state); + + public void set_sources (Source[] sources) { + sources_section.remove_all (); + + for (var i = 0; i < sources.length; i++) { + if (!sources[i].is_ibus || (options & Options.IBUS) != Options.NONE) { + string action; + + if ((options & Options.DCONF) != Options.NONE) { + action = "indicator.current"; + } else { + action = "indicator.active"; + } + + var item = new MenuItem (sources[i].name, action); + + item.set_attribute (Menu.ATTRIBUTE_TARGET, "u", i); + + if (sources[i].icon != null) { + item.set_icon ((!) sources[i].icon); + } + + sources_section.append_item (item); + } + } + } + + public void set_properties (IBus.PropList properties) { + if ((options & Options.IBUS) != Options.NONE) { + properties_section.set_properties (properties); + } + } + + public void update_property (IBus.Property property) { + if ((options & Options.IBUS) != Options.NONE) { + properties_section.update_property (property); + } + } + + public override bool is_mutable () { + return indicator_menu.is_mutable (); + } + + public override int get_n_items () { + return indicator_menu.get_n_items (); + } + + public override void get_item_attributes (int item_index, out HashTable<string, Variant>? attributes) { + indicator_menu.get_item_attributes (item_index, out attributes); + } + + public override void get_item_links (int item_index, out HashTable<string, MenuModel>? links) { + indicator_menu.get_item_links (item_index, out links); + } + + public override Variant get_item_attribute_value (int item_index, string attribute, VariantType? expected_type) { + return indicator_menu.get_item_attribute_value (item_index, attribute, expected_type); + } + + public override MenuModel get_item_link (int item_index, string link) { + return indicator_menu.get_item_link (item_index, link); + } + + public override MenuAttributeIter iterate_item_attributes (int item_index) { + return indicator_menu.iterate_item_attributes (item_index); + } + + public override MenuLinkIter iterate_item_links (int item_index) { + return indicator_menu.iterate_item_links (item_index); + } +} diff --git a/lib/keyboard-plugin.vala b/lib/keyboard-plugin.vala new file mode 100644 index 00000000..5ee8ea24 --- /dev/null +++ b/lib/keyboard-plugin.vala @@ -0,0 +1,23 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: William Hua <william.hua@canonical.com> + */ + +[DBus (name="com.canonical.SettingsDaemon.Keyboard.Private")] +public interface KeyboardPlugin : Object { + + public abstract void activate_input_source (uint index) throws IOError; +} diff --git a/lib/main.vala b/lib/main.vala index 64dc86a5..8d39d757 100644 --- a/lib/main.vala +++ b/lib/main.vala @@ -19,8 +19,9 @@ [DBus (name = "com.canonical.indicator.keyboard")] public class Indicator.Keyboard.Service : Object { + private static const uint PROPERTIES_DELAY = 250; + private static Service service; - private static IBus.Bus? ibus; private bool force; private bool use_gtk; @@ -35,13 +36,22 @@ public class Indicator.Keyboard.Service : Object { private Gee.HashMap<uint, uint>? window_sources; private uint focused_window_id; + private IBus.Bus? ibus; + private IBusPanel? ibus_panel; + private ulong ibus_connected_id; + private uint panel_timeout; + private Source[]? sources; private SimpleActionGroup? action_group; private SimpleAction? indicator_action; - private MenuModel? menu_model; - private Menu? sources_menu; + private SimpleAction? active_action; + private IndicatorMenu? desktop_menu; + private IndicatorMenu? desktop_greeter_menu; + private IndicatorMenu? desktop_lockscreen_menu; + private KeyboardPlugin? keyboard_plugin; + private UnitySession? unity_session; private UnityGreeter? unity_greeter; private string? greeter_user; private uint lightdm_current; @@ -53,6 +63,34 @@ public class Indicator.Keyboard.Service : Object { if (use_gtk) { use_gtk = Gtk.init_check (ref args); + + Gtk.IconTheme? icon_theme = Gtk.IconTheme.get_default (); + + if (icon_theme != null) { + ((!) icon_theme).changed.connect (() => { + if (sources != null) { + foreach (var source in (!) sources) { + source.icon = null; + } + } + + if (desktop_menu != null) { + get_desktop_menu ().set_sources (get_sources ()); + } + + if (desktop_greeter_menu != null) { + get_desktop_greeter_menu ().set_sources (get_sources ()); + } + + if (desktop_lockscreen_menu != null) { + get_desktop_lockscreen_menu ().set_sources (get_sources ()); + } + + if (indicator_action != null) { + update_indicator_action (); + } + }); + } } else { Gdk.init (ref args); } @@ -69,6 +107,18 @@ public class Indicator.Keyboard.Service : Object { } } else { Bus.watch_name (BusType.SESSION, + "org.gnome.SettingsDaemon.Keyboard", + BusNameWatcherFlags.NONE, + handle_keyboard_name_appeared, + handle_keyboard_name_vanished); + + Bus.watch_name (BusType.SESSION, + "com.canonical.Unity", + BusNameWatcherFlags.NONE, + handle_unity_name_appeared, + handle_unity_name_vanished); + + Bus.watch_name (BusType.SESSION, "com.canonical.Unity.WindowStack", BusNameWatcherFlags.NONE, handle_window_stack_name_appeared, @@ -96,16 +146,70 @@ public class Indicator.Keyboard.Service : Object { } [DBus (visible = false)] - private static IBus.Bus get_ibus () { + private IBus.Bus get_ibus () { if (ibus == null) { IBus.init (); ibus = new IBus.Bus (); + ((!) ibus).connected.connect (() => { + if (desktop_menu != null) { + get_desktop_menu ().set_sources (get_sources ()); + } + + if (desktop_greeter_menu != null) { + get_desktop_greeter_menu ().set_sources (get_sources ()); + } + + if (desktop_lockscreen_menu != null) { + get_desktop_lockscreen_menu ().set_sources (get_sources ()); + } + + if (indicator_action != null) { + update_indicator_action (); + } + }); } return (!) ibus; } [DBus (visible = false)] + private IBusPanel? get_ibus_panel () { + if (ibus_panel == null && get_ibus ().is_connected ()) { + var connection = get_ibus ().get_connection (); + var name = "org.freedesktop.IBus.Panel"; + var path = "/org/freedesktop/IBus/Panel"; + + try { + ibus_panel = connection.get_proxy_sync (name, path); + + ((!) ibus_panel).properties_registered.connect ((variant) => { + var properties = new IBus.PropList (); + properties.deserialize (variant); + + if (properties is IBus.PropList) { + handle_properties_registered ((!) (properties as IBus.PropList)); + } + }); + ((!) ibus_panel).property_updated.connect ((variant) => { + var type = IBus.PropType.NORMAL; + var state = IBus.PropState.INCONSISTENT; + var text = new IBus.Text.from_static_string (""); + var property = new IBus.Property ("", type, text, null, text, false, false, state, null); + property.deserialize (variant); + + if (property is IBus.Property) { + handle_property_updated ((!) (property as IBus.Property)); + } + }); + } catch (IOError error) { + warning ("error: %s", error.message); + } + } + + return ibus_panel; + } + + [DBus (visible = false)] public void up () { if (loop == null) { loop = new MainLoop (); @@ -210,7 +314,7 @@ public class Indicator.Keyboard.Service : Object { if (source != null) { var array = source_settings.get_value ("sources"); - for (int i = 0; i < array.n_children (); i++) { + for (var i = 0; i < array.n_children (); i++) { unowned string type; unowned string name; @@ -507,16 +611,22 @@ public class Indicator.Keyboard.Service : Object { [DBus (visible = false)] private void handle_focused_window_changed (uint window_id, string app_id, uint stage) { - ((!) window_sources)[focused_window_id] = source_settings.get_uint ("current"); + var old_current = source_settings.get_uint ("current"); + + ((!) window_sources)[focused_window_id] = old_current; if (!(((!) window_sources).has_key (window_id))) { var default_group = per_window_settings.get_int ("default-group"); - if (default_group >= 0) { + if (default_group >= 0 && default_group != old_current) { source_settings.set_uint ("current", (uint) default_group); } } else { - source_settings.set_uint ("current", ((!) window_sources)[window_id]); + var current = ((!) window_sources)[window_id]; + + if (current != old_current) { + source_settings.set_uint ("current", current); + } } focused_window_id = window_id; @@ -543,6 +653,15 @@ public class Indicator.Keyboard.Service : Object { break; } } + + if (ibus_connected_id == 0 && sources[i].is_ibus) { + ibus_connected_id = get_ibus ().connected.connect (() => { get_ibus_panel (); }); + get_ibus ().disconnected.connect (() => { ibus_panel = null; }); + + if (get_ibus ().is_connected ()) { + get_ibus_panel (); + } + } } } @@ -550,21 +669,39 @@ public class Indicator.Keyboard.Service : Object { } [DBus (visible = false)] - private void update_indicator_action () { - var visible = indicator_settings.get_boolean ("visible"); - var current = source_settings.get_uint ("current"); - var sources = get_sources (); + private void handle_properties_registered (IBus.PropList list) { + if (panel_timeout > 0) { + GLib.Source.remove (panel_timeout); + panel_timeout = 0; + } + + panel_timeout = Timeout.add (PROPERTIES_DELAY, () => { + get_desktop_menu ().set_properties (list); + panel_timeout = 0; + return false; + }); + } + [DBus (visible = false)] + private void handle_property_updated (IBus.Property property) { + get_desktop_menu ().update_property (property); + } + + [DBus (visible = false)] + private void update_indicator_action () { Icon? icon = null; string? name = null; - if (current < sources.length) { - icon = sources[current].icon; - name = sources[current].name; + var sources = get_sources (); + var active = get_active_action ().get_state ().get_uint32 (); + + if (active < sources.length) { + icon = sources[active].icon; + name = sources[active].name; } var builder = new VariantBuilder (new VariantType ("a{sv}")); - builder.add ("{sv}", "visible", new Variant.boolean (visible)); + builder.add ("{sv}", "visible", indicator_settings.get_value ("visible")); if (name != null) { var description = _ ("%s input source").printf ((!) name); builder.add ("{sv}", "accessible-desc", new Variant.string (description)); @@ -588,6 +725,42 @@ public class Indicator.Keyboard.Service : Object { } [DBus (visible = false)] + private void handle_changed_active (Variant? value) { + if (value != null) { + ((!) active_action).set_state ((!) value); + update_indicator_action (); + + if (keyboard_plugin != null) { + try { + ((!) keyboard_plugin).activate_input_source (((!) value).get_uint32 ()); + } catch (IOError error) { + warning ("error: %s", error.message); + } + } + } + } + + [DBus (visible = false)] + private void update_active_action () { + if (active_action != null) { + ((!) active_action).set_state (source_settings.get_value ("current")); + update_indicator_action (); + } + } + + [DBus (visible = false)] + private Action get_active_action () { + if (active_action == null) { + var current = source_settings.get_value ("current"); + active_action = new SimpleAction.stateful ("active", VariantType.UINT32, current); + ((!) active_action).activate.connect ((parameter) => { ((!) active_action).change_state (parameter); }); + ((!) active_action).change_state.connect (handle_changed_active); + } + + return (!) active_action; + } + + [DBus (visible = false)] private void handle_middle_click (Variant? parameter) { handle_scroll_wheel (new Variant.int32 (-1)); } @@ -600,38 +773,98 @@ public class Indicator.Keyboard.Service : Object { var length = (int) sources.n_children (); if (length > 0) { - var offset = parameter.get_int32 () % length; + var offset = ((!) parameter).get_int32 () % length; source_settings.set_uint ("current", (current + (length - offset)) % length); } } } [DBus (visible = false)] + private void handle_middle_click_when_locked (Variant? parameter) { + handle_scroll_wheel_when_locked (new Variant.int32 (-1)); + } + + [DBus (visible = false)] + private void handle_scroll_wheel_when_locked (Variant? parameter) { + if (parameter != null) { + var sources = get_sources (); + var non_ibus_length = 0; + + /* Figure out how many non-IBus sources we have. */ + foreach (var source in sources) { + if (!source.is_ibus) { + non_ibus_length++; + } + } + + if (non_ibus_length > 1) { + var active_action = get_active_action (); + var active = active_action.state.get_uint32 (); + var offset = -((!) parameter).get_int32 () % non_ibus_length; + + /* Make offset positive modulo non_ibus_length. */ + if (offset < 0) { + offset += non_ibus_length; + } + + /* We need to cycle through non-IBus sources only. */ + while (offset > 0) { + do { + active = (active + 1) % sources.length; + } while (sources[active].is_ibus); + + offset--; + } + + active_action.change_state (new Variant.uint32 (active)); + } + } + } + + [DBus (visible = false)] protected virtual SimpleActionGroup create_action_group (Action root_action) { var group = new SimpleActionGroup (); - group.insert (root_action); - group.insert (source_settings.create_action ("current")); + /* + * The 'current' action reflects the current setting in + * GSettings and the 'active' action only exists to set the + * active input source without persisting it. + * + * The lock screen menu uses the 'active' action while the + * other menus instead persist the current input source. + */ + + group.add_action (root_action); + group.add_action (get_active_action ()); + group.add_action (source_settings.create_action ("current")); var action = new SimpleAction ("next", null); action.activate.connect (handle_middle_click); - group.insert (action); + group.add_action (action); action = new SimpleAction ("scroll", VariantType.INT32); action.activate.connect (handle_scroll_wheel); - group.insert (action); + group.add_action (action); + + action = new SimpleAction ("locked_next", null); + action.activate.connect (handle_middle_click_when_locked); + group.add_action (action); + + action = new SimpleAction ("locked_scroll", VariantType.INT32); + action.activate.connect (handle_scroll_wheel_when_locked); + group.add_action (action); action = new SimpleAction ("map", null); action.activate.connect (handle_activate_map); - group.insert (action); + group.add_action (action); action = new SimpleAction ("chart", null); action.activate.connect (handle_activate_chart); - group.insert (action); + group.add_action (action); action = new SimpleAction ("settings", null); action.activate.connect (handle_activate_settings); - group.insert (action); + group.add_action (action); return group; } @@ -646,74 +879,52 @@ public class Indicator.Keyboard.Service : Object { } [DBus (visible = false)] - private void update_sources_menu () { - if (sources_menu != null) { - var menu = get_sources_menu (); - - while (menu.get_n_items () > 0) - menu.remove (0); - - var sources = get_sources (); - - for (var i = 0; i < sources.length; i++) { - var item = new MenuItem (sources[i].name, "indicator.current"); - item.set_attribute (Menu.ATTRIBUTE_TARGET, "u", i); - - var icon = sources[i].icon; - if (icon != null) { - item.set_icon ((!) icon); + public IndicatorMenu get_desktop_menu () { + if (desktop_menu == null) { + var options = IndicatorMenu.Options.DCONF + | IndicatorMenu.Options.IBUS + | IndicatorMenu.Options.SETTINGS; + + desktop_menu = new IndicatorMenu (get_action_group (), options); + ((!) desktop_menu).set_sources (get_sources ()); + ((!) desktop_menu).activate.connect ((property, state) => { + var panel = get_ibus_panel (); + + if (panel != null) { + try { + ((!) panel).activate_property (property.key, state); + } catch (IOError error) { + warning ("error: %s", error.message); + } } - - menu.append_item (item); - } - } else { - get_sources_menu (); - } - } - - [DBus (visible = false)] - private Menu get_sources_menu () { - if (sources_menu == null) { - sources_menu = new Menu (); - update_sources_menu (); + }); } - return (!) sources_menu; + return (!) desktop_menu; } [DBus (visible = false)] - protected virtual MenuModel create_menu_model (MenuModel section_menu) { - var menu = new Menu (); - - var submenu = new Menu (); + public IndicatorMenu get_desktop_greeter_menu () { + if (desktop_greeter_menu == null) { + var options = IndicatorMenu.Options.DCONF; - submenu.append_section (null, section_menu); - - if (!is_login_user ()) { - var section = new Menu (); - section.append (_ ("Character Map"), "indicator.map"); - section.append (_ ("Keyboard Layout Chart"), "indicator.chart"); - section.append (_ ("Text Entry Settings..."), "indicator.settings"); - submenu.append_section (null, section); + desktop_greeter_menu = new IndicatorMenu (get_action_group (), options); + ((!) desktop_greeter_menu).set_sources (get_sources ()); } - var indicator = new MenuItem.submenu ("x", submenu); - indicator.set_attribute ("x-canonical-type", "s", "com.canonical.indicator.root"); - indicator.set_attribute ("x-canonical-secondary-action", "s", "indicator.next"); - indicator.set_attribute ("x-canonical-scroll-action", "s", "indicator.scroll"); - indicator.set_detailed_action ("indicator.indicator"); - menu.append_item (indicator); - - return menu; + return (!) desktop_greeter_menu; } [DBus (visible = false)] - public MenuModel get_menu_model () { - if (menu_model == null) { - menu_model = create_menu_model (get_sources_menu ()); + public IndicatorMenu get_desktop_lockscreen_menu () { + if (desktop_lockscreen_menu == null) { + var options = IndicatorMenu.Options.NONE; + + desktop_lockscreen_menu = new IndicatorMenu (get_action_group (), options); + ((!) desktop_lockscreen_menu).set_sources (get_sources ()); } - return (!) menu_model; + return (!) desktop_lockscreen_menu; } [DBus (visible = false)] @@ -724,6 +935,7 @@ public class Indicator.Keyboard.Service : Object { [DBus (visible = false)] private void handle_changed_current (string key) { update_indicator_action (); + update_active_action (); update_login_layout (); } @@ -731,7 +943,9 @@ public class Indicator.Keyboard.Service : Object { private void handle_changed_sources (string key) { sources = null; - update_sources_menu (); + get_desktop_menu ().set_sources (get_sources ()); + get_desktop_greeter_menu ().set_sources (get_sources ()); + get_desktop_lockscreen_menu ().set_sources (get_sources ()); update_indicator_action (); update_login_layout (); } @@ -803,6 +1017,53 @@ public class Indicator.Keyboard.Service : Object { } [DBus (visible = false)] + private void handle_keyboard_name_appeared (DBusConnection connection, string name, string name_owner) { + try { + keyboard_plugin = Bus.get_proxy_sync (BusType.SESSION, name, "/org/gnome/SettingsDaemon/Keyboard"); + } catch (IOError error) { + warning ("error: %s", error.message); + } + } + + [DBus (visible = false)] + private void handle_keyboard_name_vanished (DBusConnection connection, string name) { + keyboard_plugin = null; + } + + [DBus (visible = false)] + private void handle_unity_name_appeared (DBusConnection connection, string name, string name_owner) { + try { + unity_session = Bus.get_proxy_sync (BusType.SESSION, name, "/com/canonical/Unity/Session"); + ((!) unity_session).locked.connect (() => { + var sources = get_sources (); + + if (sources.length > 0) { + var current = source_settings.get_uint ("current"); + + if (current < sources.length && sources[current].is_ibus) { + for (var i = 0; i < sources.length; i++) { + if (!sources[i].is_ibus) { + get_active_action ().change_state (new Variant.uint32 (i)); + break; + } + } + } + } + }); + ((!) unity_session).unlocked.connect (() => { + get_active_action ().change_state (source_settings.get_value ("current")); + }); + } catch (IOError error) { + warning ("error: %s", error.message); + } + } + + [DBus (visible = false)] + private void handle_unity_name_vanished (DBusConnection connection, string name) { + unity_session = null; + } + + [DBus (visible = false)] private void handle_window_stack_name_appeared (DBusConnection connection, string name, string name_owner) { try { window_stack = Bus.get_proxy_sync (BusType.SESSION, name, "/com/canonical/Unity/WindowStack"); @@ -821,7 +1082,9 @@ public class Indicator.Keyboard.Service : Object { private void handle_bus_acquired (DBusConnection connection, string name) { try { connection.export_action_group ("/com/canonical/indicator/keyboard", get_action_group ()); - connection.export_menu_model ("/com/canonical/indicator/keyboard/desktop", get_menu_model ()); + connection.export_menu_model ("/com/canonical/indicator/keyboard/desktop", get_desktop_menu ()); + connection.export_menu_model ("/com/canonical/indicator/keyboard/desktop_greeter", get_desktop_greeter_menu ()); + connection.export_menu_model ("/com/canonical/indicator/keyboard/desktop_lockscreen", get_desktop_lockscreen_menu ()); } catch (Error error) { warning ("error: %s", error.message); } diff --git a/lib/source.vala b/lib/source.vala index 3e38a86e..5fe7157d 100644 --- a/lib/source.vala +++ b/lib/source.vala @@ -51,7 +51,7 @@ public class Indicator.Keyboard.Source : Object { public Icon? icon { get { if (_icon == null) { _icon = _get_icon (); } return _icon; } - private set { _icon = value; } + set { _icon = value; } } public uint subscript { @@ -69,6 +69,14 @@ public class Indicator.Keyboard.Source : Object { construct set { _use_gtk = value; icon = null; } } + public bool is_xkb { + get { return xkb != null; } + } + + public bool is_ibus { + get { return ibus != null; } + } + public Source (Variant variant, bool use_gtk = false) { Object (use_gtk: use_gtk); @@ -416,16 +424,7 @@ public class Indicator.Keyboard.Source : Object { Gtk.IconInfo? icon_info = icon_theme.lookup_icon (icon_name, 22, 0); if (icon_info != null) { - string? file_name = ((!) icon_info).get_filename (); - var has_file_name = file_name != null && ((!) file_name).get_char () != '\0'; - - if (has_file_name) { - try { - icon = Icon.new_for_string ((!) file_name); - } catch (Error error) { - warning ("error: %s", error.message); - } - } + icon = new ThemedIcon (icon_name); } } else { icon = new ThemedIcon (icon_name); diff --git a/lib/unity-session.vala b/lib/unity-session.vala new file mode 100644 index 00000000..04bf3349 --- /dev/null +++ b/lib/unity-session.vala @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: William Hua <william.hua@canonical.com> + */ + +[DBus (name="com.canonical.Unity.Session")] +public interface UnitySession : Object { + + public signal void locked (); + public signal void unlocked (); +} |