aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am16
-rw-r--r--lib/ibus-menu.vala317
-rw-r--r--lib/ibus-panel.vala26
-rw-r--r--lib/indicator-menu.vala144
-rw-r--r--lib/keyboard-plugin.vala23
-rw-r--r--lib/main.vala423
-rw-r--r--lib/source.vala21
-rw-r--r--lib/unity-session.vala24
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 ();
+}