aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMike Gabriel <mike.gabriel@das-netzwerkteam.de>2017-10-26 19:17:41 +0200
committerMike Gabriel <mike.gabriel@das-netzwerkteam.de>2017-10-26 19:28:18 +0200
commitd5277646dbe9a88d32c42187c9c3701a7c57a469 (patch)
tree21f8115516693217895e0242afb990ef28c14013 /src
parentbca2db6d064187ecfa2bd9668a88b1b745683e1d (diff)
downloadayatana-indicator-keyboard-d5277646dbe9a88d32c42187c9c3701a7c57a469.tar.gz
ayatana-indicator-keyboard-d5277646dbe9a88d32c42187c9c3701a7c57a469.tar.bz2
ayatana-indicator-keyboard-d5277646dbe9a88d32c42187c9c3701a7c57a469.zip
Fork from Ubuntu's indicator-keyboard.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am55
-rw-r--r--src/common.vala37
-rw-r--r--src/ibus-menu.vala313
-rw-r--r--src/ibus-panel.vala26
-rw-r--r--src/indicator-menu.vala151
-rw-r--r--src/keyboard-plugin.vala23
-rw-r--r--src/main.vala1265
-rw-r--r--src/source.vala487
-rw-r--r--src/unity-greeter.vala26
-rw-r--r--src/unity-session.vala24
-rw-r--r--src/window-stack.vala37
11 files changed, 2444 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..a0fc4bb5
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,55 @@
+pkglibexec_PROGRAMS = ayatana-indicator-keyboard-service
+
+AM_CFLAGS = -w -DGNOME_DESKTOP_USE_UNSTABLE_API
+AM_LDFLAGS = -lm
+AM_VALAFLAGS = --enable-experimental-non-null \
+ --metadatadir $(top_srcdir)/deps \
+ --vapidir $(top_srcdir)/deps
+
+ayatana_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
+ayatana_indicator_keyboard_service_VALAFLAGS = $(AM_VALAFLAGS) \
+ --pkg gee-1.0 \
+ --pkg posix \
+ --pkg pangocairo \
+ --pkg gtk+-3.0 \
+ --pkg GDesktopEnums-3.0 \
+ --pkg GnomeDesktop-3.0 \
+ --pkg Xkl-1.0 \
+ --pkg Gkbd-3.0 \
+ --pkg ibus-1.0 \
+ --pkg Fcitx-1.0 \
+ --pkg AccountsService-1.0 \
+ --pkg liblightdm-gobject-1
+ayatana_indicator_keyboard_service_CFLAGS = $(AM_CFLAGS) \
+ $(GEE_CFLAGS) \
+ $(PANGOCAIRO_CFLAGS) \
+ $(GTK_CFLAGS) \
+ $(GNOME_DESKTOP_CFLAGS) \
+ $(LIBXKLAVIER_CFLAGS) \
+ $(LIBGNOMEKBD_CFLAGS) \
+ $(IBUS_CFLAGS) \
+ $(FCITX_GCLIENT_CFLAGS) \
+ $(ACCOUNTSSERVICE_CFLAGS) \
+ $(LIGHTDM_CFLAGS) \
+ $(COVERAGE_CFLAGS)
+ayatana_indicator_keyboard_service_LDFLAGS = $(AM_LDFLAGS) \
+ $(GEE_LIBS) \
+ $(PANGOCAIRO_LIBS) \
+ $(GTK_LIBS) \
+ $(GNOME_DESKTOP_LIBS) \
+ $(LIBXKLAVIER_LIBS) \
+ $(LIBGNOMEKBD_LIBS) \
+ $(IBUS_LIBS) \
+ $(FCITX_GCLIENT_LIBS) \
+ $(ACCOUNTSSERVICE_LIBS) \
+ $(LIGHTDM_LIBS) \
+ $(COVERAGE_LDFLAGS)
diff --git a/src/common.vala b/src/common.vala
new file mode 100644
index 00000000..9824bc26
--- /dev/null
+++ b/src/common.vala
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 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>
+ */
+
+string? abbreviate (string? name) {
+ var index = 0;
+ unichar first;
+ unichar second;
+
+ if (name != null) {
+ if (((!) name).get_next_char (ref index, out first)) {
+ if (((!) name).get_next_char (ref index, out second)) {
+ return @"$((!) first.toupper ().to_string ())$((!) second.to_string ())";
+ } else {
+ return first.toupper ().to_string ();
+ }
+ } else {
+ return "";
+ }
+ } else {
+ return null;
+ }
+}
diff --git a/src/ibus-menu.vala b/src/ibus-menu.vala
new file mode 100644
index 00000000..a240f00b
--- /dev/null
+++ b/src/ibus-menu.vala
@@ -0,0 +1,313 @@
+/*
+ * 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.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++;
+
+ var name = @"-private-radio-$radio_counter";
+ var action = new SimpleAction.stateful (name, VariantType.STRING, new Variant.string (""));
+
+ action.change_state.connect ((value) => {
+ if (value != null) {
+ var key = ((!) value).get_string ();
+
+ if (radio_properties.has_key (key)) {
+ action.set_state ((!) value);
+ activate (radio_properties[key], IBus.PropState.CHECKED);
+ }
+ }
+ });
+
+ ((!) action_map).add_action (action);
+ names.add (name);
+
+ radio_name = name;
+ radio_action = action;
+ }
+
+ 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/src/ibus-panel.vala b/src/ibus-panel.vala
new file mode 100644
index 00000000..2a380efd
--- /dev/null
+++ b/src/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="org.ayatana.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/src/indicator-menu.vala b/src/indicator-menu.vala
new file mode 100644
index 00000000..2cfa52c6
--- /dev/null
+++ b/src/indicator-menu.vala
@@ -0,0 +1,151 @@
+/*
+ * 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 = 0,
+ DCONF = 1 << 0,
+ XKB = 1 << 1,
+ IBUS = 1 << 2,
+ SETTINGS = 1 << 3
+ }
+
+ 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) {
+ this.options = options;
+
+ indicator_menu = new Menu ();
+ sources_section = new Menu ();
+
+ if ((options & ~Options.DCONF) != Options.NONE) {
+ var submenu = new Menu ();
+
+ submenu.append_section (null, sources_section);
+
+ if (Options.IBUS in options) {
+ properties_section = new IBusMenu (action_map);
+ properties_section.activate.connect ((property, state) => { activate (property, state); });
+ submenu.append_section (null, properties_section);
+ }
+
+ if (Options.SETTINGS in options) {
+ 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", "org.ayatana.indicator.root");
+
+ /* We need special mouse actions on the lock screen. */
+ if (Options.DCONF in options) {
+ 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.append_item (indicator);
+ }
+ }
+
+ 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++) {
+ var visible = (sources[i].is_xkb && Options.XKB in options) ||
+ (sources[i].is_ibus && Options.IBUS in options);
+
+ if (visible) {
+ string action;
+
+ if (Options.DCONF in options) {
+ 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.IBUS in options) {
+ properties_section.set_properties (properties);
+ }
+ }
+
+ public void update_property (IBus.Property property) {
+ if (Options.IBUS in options) {
+ 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/src/keyboard-plugin.vala b/src/keyboard-plugin.vala
new file mode 100644
index 00000000..af1628f6
--- /dev/null
+++ b/src/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="org.ayatana.SettingsDaemon.Keyboard.Private")]
+public interface KeyboardPlugin : Object {
+
+ public abstract void activate_input_source (uint index) throws IOError;
+}
diff --git a/src/main.vala b/src/main.vala
new file mode 100644
index 00000000..9bb3eb1f
--- /dev/null
+++ b/src/main.vala
@@ -0,0 +1,1265 @@
+/*
+ * Copyright 2013 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 = "org.ayatana.indicator.keyboard")]
+public class Indicator.Keyboard.Service : Object {
+
+ private static const uint PROPERTIES_DELAY = 250;
+
+ private static Service service;
+
+ private bool force;
+ private bool use_gtk;
+
+ private MainLoop? loop;
+ private Settings indicator_settings;
+ private Settings source_settings;
+ private Settings per_window_settings;
+ private SList<Act.User> users;
+
+ private WindowStack? window_stack;
+ private Gee.HashMap<uint, Source>? 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 Fcitx.InputMethod? fcitx;
+ private bool fcitx_initialized;
+
+ private Source[]? sources;
+
+ private SimpleActionGroup? action_group;
+ private SimpleAction? indicator_action;
+ 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;
+
+ [DBus (visible = false)]
+ public Service (ref unowned string[] args) {
+ force = "--force" in args;
+ use_gtk = "--use-gtk" in args;
+
+ 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);
+ }
+
+ if (is_login_user ()) {
+ var name = Environment.get_variable ("UNITY_GREETER_DBUS_NAME");
+
+ if (name != null) {
+ Bus.watch_name (BusType.SESSION,
+ (!) name,
+ BusNameWatcherFlags.NONE,
+ handle_unity_greeter_name_appeared,
+ handle_unity_greeter_name_vanished);
+ }
+ } 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,
+ "org.ayatana.Unity",
+ BusNameWatcherFlags.NONE,
+ handle_unity_name_appeared,
+ handle_unity_name_vanished);
+
+ if (!is_fcitx_active ()) {
+ Bus.watch_name (BusType.SESSION,
+ "org.ayatana.Unity.WindowStack",
+ BusNameWatcherFlags.NONE,
+ handle_window_stack_name_appeared,
+ handle_window_stack_name_vanished);
+ }
+ }
+
+ indicator_settings = new Settings ("org.ayatana.indicator.keyboard");
+ indicator_settings.changed["visible"].connect (handle_changed_visible);
+
+ source_settings = new Settings ("org.gnome.desktop.input-sources");
+ source_settings.changed["current"].connect (handle_changed_current);
+ source_settings.changed["sources"].connect (handle_changed_sources);
+
+ per_window_settings = new Settings ("org.gnome.libgnomekbd.desktop");
+ per_window_settings.changed["group-per-window"].connect (handle_changed_group_per_window);
+
+ migrate_keyboard_layouts ();
+ update_window_sources ();
+ acquire_bus_name ();
+ }
+
+ [DBus (visible = false)]
+ private static bool is_login_user () {
+ return Environment.get_user_name () == "lightdm";
+ }
+
+ [DBus (visible = false)]
+ private static bool is_ibus_active () {
+ if (is_login_user ()) {
+ return false;
+ }
+
+ var module = Environment.get_variable ("GTK_IM_MODULE");
+ return module != null && (!) module == "ibus";
+ }
+
+ [DBus (visible = false)]
+ private static bool is_fcitx_active () {
+ if (is_login_user ()) {
+ return false;
+ }
+
+ var module = Environment.get_variable ("GTK_IM_MODULE");
+ return module != null && (!) module == "fcitx";
+ }
+
+ [DBus (visible = false)]
+ private IBus.Bus get_ibus () {
+ if (ibus == null) {
+ IBus.init ();
+
+ var proxy = new IBus.Bus ();
+
+ proxy.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 ();
+ }
+ });
+
+ ibus = proxy;
+ }
+
+ 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 {
+ var proxy = connection.get_proxy_sync<IBusPanel> (name, path);
+
+ proxy.properties_registered.connect ((variant) => {
+ var properties = new IBus.PropList ();
+ properties.deserialize (variant);
+
+ if (properties is IBus.PropList) {
+ handle_properties_registered ((!) (properties as IBus.PropList));
+ }
+ });
+ proxy.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));
+ }
+ });
+
+ ibus_panel = proxy;
+ } catch (IOError error) {
+ warning ("error: %s", error.message);
+ }
+ }
+
+ return ibus_panel;
+ }
+
+ [DBus (visible = false)]
+ private Fcitx.InputMethod? get_fcitx () {
+ if (!fcitx_initialized) {
+ fcitx_initialized = true;
+
+ if (is_fcitx_active ()) {
+ try {
+ var proxy = new Fcitx.InputMethod (BusType.SESSION, DBusProxyFlags.NONE, 0);
+ proxy.notify["current-im"].connect ((pspec) => { handle_changed_current ("current"); });
+ fcitx = proxy;
+ } catch (Error error) {
+ warning ("error: %s", error.message);
+ }
+ }
+ }
+
+ return fcitx;
+ }
+
+ [DBus (visible = false)]
+ public void up () {
+ if (loop == null) {
+ var main_loop = new MainLoop ();
+ loop = main_loop;
+ main_loop.run ();
+ }
+ }
+
+ [DBus (visible = false)]
+ public void down () {
+ if (loop != null) {
+ ((!) loop).quit ();
+ loop = null;
+ }
+ }
+
+ [DBus (visible = false)]
+ private void acquire_bus_name () {
+ Bus.own_name (BusType.SESSION,
+ "org.ayatana.indicator.keyboard",
+ BusNameOwnerFlags.ALLOW_REPLACEMENT | (force ? BusNameOwnerFlags.REPLACE : 0),
+ handle_bus_acquired,
+ null,
+ handle_name_lost);
+ }
+
+ [DBus (visible = false)]
+ private void update_greeter_user () {
+ if (greeter_user == null && unity_greeter != null) {
+ try {
+ greeter_user = ((!) unity_greeter).get_active_entry ();
+ } catch (IOError error) {
+ warning ("error: %s", error.message);
+ }
+ }
+
+ string? source = null;
+
+ if (greeter_user != null) {
+ var manager = Act.UserManager.get_default ();
+
+ if (manager.is_loaded) {
+ Act.User? user = manager.get_user ((!) greeter_user);
+
+ if (user != null && ((!) user).is_loaded) {
+ foreach (var outer in ((!) user).input_sources) {
+ foreach (var inner in (!) outer) {
+ unowned string key;
+ unowned string value;
+
+ ((!) inner).get ("{&s&s}", out key, out value);
+
+ if (key == "xkb") {
+ source = value;
+ break;
+ }
+ }
+
+ if (source != null) {
+ break;
+ }
+ }
+
+ if (source == null) {
+ var layouts = ((!) user).xkeyboard_layouts;
+
+ if (layouts.length <= 0) {
+ var user_list = LightDM.UserList.get_instance ();
+ LightDM.User? light_user = user_list.get_user_by_name ((!) greeter_user);
+
+ if (light_user != null) {
+ layouts = ((!) light_user).get_layouts ();
+ }
+ }
+
+ if (layouts.length > 0) {
+ source = layouts[0].replace (" ", "+").replace ("\t", "+");
+ }
+ }
+ }
+ }
+ }
+
+ if (source == null) {
+ LightDM.Layout? layout = LightDM.get_layout ();
+
+ if (layout != null) {
+ source = ((!) layout).name;
+
+ if (source != null) {
+ source = ((!) source).replace (" ", "+");
+ source = ((!) source).replace ("\t", "+");
+ }
+ }
+ }
+
+ if (source != null) {
+ var array = source_settings.get_value ("sources");
+
+ for (var i = 0; i < array.n_children (); i++) {
+ unowned string type;
+ unowned string name;
+
+ array.get_child (i, "(&s&s)", out type, out name);
+
+ if (type == "xkb" && name == (!) source) {
+ source_settings.set_uint ("current", i);
+ break;
+ }
+ }
+ }
+ }
+
+ [DBus (visible = false)]
+ private void handle_entry_selected (string entry_name) {
+ if (greeter_user == null || entry_name != (!) greeter_user) {
+ greeter_user = entry_name;
+
+ update_greeter_user ();
+ }
+ }
+
+ [DBus (visible = false)]
+ private void migrate_keyboard_layouts () {
+ if (is_login_user ()) {
+ lightdm_current = get_current ();
+
+ var manager = Act.UserManager.get_default ();
+
+ if (manager.is_loaded) {
+ users = manager.list_users ();
+
+ foreach (var user in users) {
+ if (user.is_loaded) {
+ migrate_input_sources ();
+ } else {
+ user.notify["is-loaded"].connect ((pspec) => {
+ if (user.is_loaded) {
+ migrate_input_sources ();
+ }
+ });
+ }
+ }
+ } else {
+ manager.notify["is-loaded"].connect ((pspec) => {
+ if (manager.is_loaded) {
+ users = manager.list_users ();
+
+ foreach (var user in users) {
+ if (user.is_loaded) {
+ migrate_input_sources ();
+ } else {
+ user.notify["is-loaded"].connect ((pspec) => {
+ if (user.is_loaded) {
+ migrate_input_sources ();
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+
+ var user_list = LightDM.UserList.get_instance ();
+
+ user_list.user_added.connect ((user) => { migrate_input_sources (); });
+ user_list.user_changed.connect ((user) => { migrate_input_sources (); });
+ user_list.user_removed.connect ((user) => { migrate_input_sources (); });
+
+ /* Force the loading of the user list. */
+ user_list.get_user_by_name ("");
+ } else {
+ if (!indicator_settings.get_boolean ("migrated")) {
+ var builder = new VariantBuilder (new VariantType ("a(ss)"));
+ var length = 0;
+
+ var layout_settings = new Settings ("org.gnome.libgnomekbd.keyboard");
+ var layouts = layout_settings.get_strv ("layouts");
+
+ foreach (var layout in layouts) {
+ var source = layout;
+ source = source.replace (" ", "+");
+ source = source.replace ("\t", "+");
+
+ builder.add ("(ss)", "xkb", source);
+ length++;
+ }
+
+ var engines = get_ibus ().list_active_engines ();
+
+ foreach (var engine in engines) {
+ if (length == 0 || engine.name.has_prefix ("xkb")) {
+ var source = "us";
+ string? layout = engine.get_layout ();
+ string? variant = engine.get_layout_variant ();
+
+ if (layout != null && ((!) layout).length == 0) {
+ layout = null;
+ }
+
+ if (variant != null && ((!) variant).length == 0) {
+ variant = null;
+ }
+
+ if (layout != null && variant != null) {
+ source = @"$((!) layout)+$((!) variant)";
+ } else if (layout != null) {
+ source = (!) layout;
+ }
+
+ builder.add ("(ss)", "xkb", source);
+ length++;
+ }
+
+ if (!engine.name.has_prefix ("xkb")) {
+ builder.add ("(ss)", "ibus", engine.name);
+ length++;
+ }
+ }
+
+ source_settings.set_value ("sources", builder.end ());
+ indicator_settings.set_boolean ("migrated", true);
+ }
+ }
+ }
+
+ [DBus (visible = false)]
+ private void migrate_input_sources () {
+ var list = new Gee.LinkedList<string> ();
+ var added = new Gee.HashSet<string> ();
+
+ foreach (var user in users) {
+ if (user.is_loaded) {
+ var done = false;
+
+ foreach (var outer in user.input_sources) {
+ foreach (var inner in (!) outer) {
+ unowned string key;
+ unowned string source;
+
+ ((!) inner).get ("{&s&s}", out key, out source);
+
+ if (key == "xkb") {
+ done = true;
+
+ if (!added.contains (source)) {
+ list.add (source);
+ added.add (source);
+ }
+ }
+ }
+ }
+
+ if (!done) {
+ var layouts = user.xkeyboard_layouts;
+ foreach (var layout in layouts) {
+ done = true;
+
+ var source = layout;
+ source = source.replace (" ", "+");
+ source = source.replace ("\t", "+");
+
+ if (!added.contains (source)) {
+ list.add (source);
+ added.add (source);
+ }
+ }
+ }
+
+ if (!done) {
+ var user_list = LightDM.UserList.get_instance ();
+ LightDM.User? light_user = user_list.get_user_by_name (user.user_name);
+
+ if (light_user != null) {
+ var layouts = ((!) light_user).get_layouts ();
+ foreach (var layout in layouts) {
+ done = true;
+
+ var source = layout;
+ source = source.replace (" ", "+");
+ source = source.replace ("\t", "+");
+
+ if (!added.contains (source)) {
+ list.add (source);
+ added.add (source);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ LightDM.Layout? layout = LightDM.get_layout ();
+
+ if (layout != null) {
+ string? source = ((!) layout).name;
+
+ if (source != null) {
+ source = ((!) source).replace (" ", "+");
+ source = ((!) source).replace ("\t", "+");
+
+ if (!added.contains ((!) source)) {
+ list.add ((!) source);
+ added.add ((!) source);
+ }
+ }
+ }
+
+ var builder = new VariantBuilder (new VariantType ("a(ss)"));
+
+ foreach (var name in list) {
+ builder.add ("(ss)", "xkb", name);
+ }
+
+ if (lightdm_current < list.size) {
+ source_settings.set_uint ("current", lightdm_current);
+ } else {
+ source_settings.set_uint ("current", list.size - 1);
+ }
+
+ source_settings.set_value ("sources", builder.end ());
+
+ update_greeter_user ();
+ }
+
+ [DBus (visible = false)]
+ private void update_login_layout () {
+ if (is_login_user ()) {
+ unowned List<LightDM.Layout> layouts = LightDM.get_layouts ();
+ var current = get_current ();
+
+ if (current < get_sources ().length) {
+ var source = get_sources ()[current];
+ string? name = null;
+
+ if (source.layout != null && source.variant != null) {
+ name = @"$((!) source.layout)\t$((!) source.variant)";
+ } else if (source.layout != null) {
+ name = source.layout;
+ }
+
+ if (name != null) {
+ foreach (var layout in layouts) {
+ if (layout.name == (!) name) {
+ LightDM.set_layout (layout);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ [DBus (visible = false)]
+ private void update_window_sources () {
+ if (window_stack != null) {
+ var group_per_window = per_window_settings.get_boolean ("group-per-window");
+
+ if (group_per_window != (window_sources != null)) {
+ if (group_per_window) {
+ focused_window_id = 0;
+
+ try {
+ var windows = ((!) window_stack).get_window_stack ();
+
+ foreach (var window in windows) {
+ if (window.focused) {
+ focused_window_id = window.window_id;
+ break;
+ }
+ }
+ } catch (IOError error) {
+ warning ("error: %s", error.message);
+ }
+
+ window_sources = new Gee.HashMap<uint, Source> ();
+ ((!) window_stack).window_destroyed.connect (handle_window_destroyed);
+ ((!) window_stack).focused_window_changed.connect (handle_focused_window_changed);
+ } else {
+ ((!) window_stack).focused_window_changed.disconnect (handle_focused_window_changed);
+ ((!) window_stack).window_destroyed.disconnect (handle_window_destroyed);
+ window_sources = null;
+ }
+ }
+ }
+ }
+
+ [DBus (visible = false)]
+ private void handle_changed_group_per_window (string key) {
+ update_window_sources ();
+ }
+
+ [DBus (visible = false)]
+ private void handle_window_destroyed (uint window_id, string app_id) {
+ ((!) window_sources).unset (window_id);
+ }
+
+ [DBus (visible = false)]
+ private void handle_focused_window_changed (uint window_id, string app_id, uint stage) {
+ var sources = get_sources ();
+ var old_current = get_current ();
+
+ if (old_current < sources.length) {
+ ((!) window_sources)[focused_window_id] = sources[old_current];
+ }
+
+ if (!(((!) window_sources).has_key (window_id))) {
+ var default_group = per_window_settings.get_int ("default-group");
+
+ if (default_group >= 0) {
+ for (var offset = 0; offset < sources.length; offset++) {
+ var current = (default_group + offset) % sources.length;
+ var source = sources[current];
+
+ if (source.is_xkb ||
+ (source.is_ibus && is_ibus_active ()) ||
+ (source.is_fcitx && is_fcitx_active ())) {
+ if (current != old_current) {
+ source_settings.set_uint ("current", current);
+ }
+
+ break;
+ }
+ }
+ }
+ } else {
+ var source = ((!) window_sources)[window_id];
+
+ for (var current = 0; current < sources.length; current++) {
+ if (sources[current] == source) {
+ if (current != old_current) {
+ source_settings.set_uint ("current", current);
+ }
+
+ break;
+ }
+ }
+ }
+
+ focused_window_id = window_id;
+ }
+
+ [DBus (visible = false)]
+ private uint get_current () {
+ if (is_fcitx_active () && get_fcitx () != null) {
+ string? engine = ((!) get_fcitx ()).current_im;
+
+ if (engine != null) {
+ var is_xkb = ((!) engine).has_prefix ("fcitx-keyboard-");
+ var type = is_xkb ? "xkb" : "fcitx";
+ var name = (!) engine;
+
+ if (is_xkb) {
+ name = name.substring ("fcitx-keyboard-".length);
+ var index = name.index_of ("-");
+ if (index >= 0) {
+ name.data[index] = '+';
+ }
+ }
+
+ var i = 0;
+
+ foreach (var pair in source_settings.get_value ("sources")) {
+ unowned string source_type;
+ unowned string source_name;
+
+ ((!) pair).get ("(&s&s)", out source_type, out source_name);
+
+ if (source_name == name && source_type == type) {
+ return i;
+ }
+
+ i++;
+ }
+ }
+ }
+
+ return source_settings.get_uint ("current");
+ }
+
+ [DBus (visible = false)]
+ private Source[] get_sources () {
+ if (sources == null) {
+ var array = source_settings.get_value ("sources");
+
+ sources = new Source[array.n_children ()];
+
+ for (var i = 0; i < ((!) sources).length; i++) {
+ sources[i] = new Source(array.get_child_value (i), use_gtk);
+ sources[i].show_subscript = false;
+ sources[i].subscript = 1;
+
+ for (var j = (int) i - 1; j >= 0; j--) {
+ if ((!) sources[j].short_name == (!) sources[i].short_name) {
+ sources[i].subscript = sources[j].subscript + 1;
+ sources[i].show_subscript = true;
+ sources[j].show_subscript = true;
+
+ 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 ();
+ }
+ }
+ }
+ }
+
+ return (!) sources;
+ }
+
+ [DBus (visible = false)]
+ 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;
+
+ 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", indicator_settings.get_value ("visible"));
+ if (name != null) {
+ var description = _ ("%s input source").printf ((!) name);
+ builder.add ("{sv}", "accessible-desc", new Variant.string (description));
+ }
+ if (icon != null) {
+ builder.add ("{sv}", "icon", ((!) icon).serialize ());
+ }
+
+ get_indicator_action ().set_state (builder.end ());
+ }
+
+ [DBus (visible = false)]
+ private SimpleAction get_indicator_action () {
+ if (indicator_action == null) {
+ var state = new Variant.parsed ("{ 'visible' : <false> }");
+ indicator_action = new SimpleAction.stateful ("indicator", null, state);
+ update_indicator_action ();
+ }
+
+ return (!) indicator_action;
+ }
+
+ [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 (new Variant.uint32 (get_current ()));
+ update_indicator_action ();
+ }
+ }
+
+ [DBus (visible = false)]
+ private Action get_active_action () {
+ if (active_action == null) {
+ var action = new SimpleAction.stateful ("active", VariantType.UINT32, new Variant.uint32 (get_current ()));
+ action.change_state.connect (handle_changed_active);
+ active_action = action;
+ }
+
+ return (!) active_action;
+ }
+
+ [DBus (visible = false)]
+ private void handle_middle_click (Variant? parameter) {
+ handle_scroll_wheel (new Variant.int32 (-1));
+ }
+
+ [DBus (visible = false)]
+ private void handle_scroll_wheel (Variant? parameter) {
+ if (parameter != null) {
+ var old_current = get_current ();
+ var sources = get_sources ();
+ var length = 0;
+
+ foreach (var source in sources) {
+ if (source.is_xkb ||
+ (source.is_ibus && is_ibus_active ()) ||
+ (source.is_fcitx && is_fcitx_active ())) {
+ length++;
+ }
+ }
+
+ if (length > 1) {
+ var current = old_current;
+ var offset = -((!) parameter).get_int32 () % length;
+ var jump = 1;
+
+ if (offset < 0) {
+ offset = -offset;
+ jump = sources.length - jump;
+ }
+
+ /*
+ * We need to cycle through offset valid input sources, skipping those that aren't
+ * valid for this session (i.e. skipping Fcitx ones if IBus is active and vice-versa.
+ * jump is the direction we need to cycle in, which is 1 if we want to cycle forward
+ * and -1 (mod sources.length) if we want to cycle backward.
+ */
+
+ for (; offset > 0; offset--) {
+ do {
+ current = (current + jump) % sources.length;
+ } while ((sources[current].is_ibus && !is_ibus_active ()) ||
+ (sources[current].is_fcitx && !is_fcitx_active ()));
+ }
+
+ if (current != old_current) {
+ source_settings.set_uint ("current", current);
+ }
+ }
+ }
+ }
+
+ [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 xkb_length = 0;
+
+ /* Figure out how many Xkb sources we have. */
+ foreach (var source in sources) {
+ if (source.is_xkb) {
+ xkb_length++;
+ }
+ }
+
+ if (xkb_length > 1) {
+ var active_action = get_active_action ();
+ var active = active_action.get_state ().get_uint32 ();
+ var offset = -((!) parameter).get_int32 () % xkb_length;
+
+ /* Make offset positive modulo xkb_length. */
+ if (offset < 0) {
+ offset += xkb_length;
+ }
+
+ /* We need to cycle through Xkb sources only. */
+ while (offset > 0) {
+ do {
+ active = (active + 1) % sources.length;
+ } while (!sources[active].is_xkb);
+
+ 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 ();
+
+ /*
+ * 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.add_action (action);
+
+ action = new SimpleAction ("scroll", VariantType.INT32);
+ action.activate.connect (handle_scroll_wheel);
+ 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.add_action (action);
+
+ action = new SimpleAction ("chart", null);
+ action.activate.connect (handle_activate_chart);
+ group.add_action (action);
+
+ action = new SimpleAction ("settings", null);
+ action.activate.connect (handle_activate_settings);
+ group.add_action (action);
+
+ return group;
+ }
+
+ [DBus (visible = false)]
+ public SimpleActionGroup get_action_group () {
+ if (action_group == null) {
+ action_group = create_action_group (get_indicator_action ());
+ }
+
+ return (!) action_group;
+ }
+
+ [DBus (visible = false)]
+ public IndicatorMenu get_desktop_menu () {
+ if (desktop_menu == null) {
+ var options = IndicatorMenu.Options.DCONF;
+
+ if (!is_fcitx_active ()) {
+ options |= IndicatorMenu.Options.XKB | IndicatorMenu.Options.SETTINGS;
+
+ if (is_ibus_active ()) {
+ options |= IndicatorMenu.Options.IBUS;
+ }
+ }
+
+ var menu = new IndicatorMenu (get_action_group (), options);
+
+ menu.set_sources (get_sources ());
+ 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);
+ }
+ }
+ });
+
+ desktop_menu = menu;
+ }
+
+ return (!) desktop_menu;
+ }
+
+ [DBus (visible = false)]
+ public IndicatorMenu get_desktop_greeter_menu () {
+ if (desktop_greeter_menu == null) {
+ var options = IndicatorMenu.Options.DCONF |
+ IndicatorMenu.Options.XKB;
+
+ var menu = new IndicatorMenu (get_action_group (), options);
+ menu.set_sources (get_sources ());
+ desktop_greeter_menu = menu;
+ }
+
+ return (!) desktop_greeter_menu;
+ }
+
+ [DBus (visible = false)]
+ public IndicatorMenu get_desktop_lockscreen_menu () {
+ if (desktop_lockscreen_menu == null) {
+ var options = IndicatorMenu.Options.XKB;
+
+ var menu = new IndicatorMenu (get_action_group (), options);
+ menu.set_sources (get_sources ());
+ desktop_lockscreen_menu = menu;
+ }
+
+ return (!) desktop_lockscreen_menu;
+ }
+
+ [DBus (visible = false)]
+ private void handle_changed_visible (string key) {
+ update_indicator_action ();
+ }
+
+ [DBus (visible = false)]
+ private void handle_changed_current (string key) {
+ update_indicator_action ();
+ update_active_action ();
+ update_login_layout ();
+ }
+
+ [DBus (visible = false)]
+ private void handle_changed_sources (string key) {
+ sources = null;
+
+ 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 ();
+ }
+
+ [DBus (visible = false)]
+ private void handle_activate_map (Variant? parameter) {
+ try {
+ Process.spawn_command_line_async ("gucharmap");
+ } catch (SpawnError error) {
+ warning ("error: %s", error.message);
+ }
+ }
+
+ [DBus (visible = false)]
+ private void handle_activate_chart (Variant? parameter) {
+ string? layout = "us";
+ string? variant = null;
+
+ var sources = get_sources ();
+ var current = get_current ();
+
+ if (current < sources.length) {
+ layout = sources[current].layout;
+ variant = sources[current].variant;
+ }
+
+ var has_layout = layout != null && ((!) layout).get_char () != '\0';
+ var has_variant = variant != null && ((!) variant).get_char () != '\0';
+
+ try {
+ string command;
+
+ if (has_layout && has_variant) {
+ command = @"gkbd-keyboard-display -l \"$((!) layout)\t$((!) variant)\"";
+ } else if (has_layout) {
+ command = @"gkbd-keyboard-display -l $((!) layout)";
+ } else {
+ command = @"gkbd-keyboard-display -l us";
+ }
+
+ Process.spawn_command_line_async (command);
+ } catch (SpawnError error) {
+ warning ("error: %s", error.message);
+ }
+ }
+
+ [DBus (visible = false)]
+ private void handle_activate_settings (Variant? parameter) {
+ try {
+ Process.spawn_command_line_async ("unity-control-center region layouts");
+ } catch (SpawnError error) {
+ warning ("error: %s", error.message);
+ }
+ }
+
+ [DBus (visible = false)]
+ private void handle_unity_greeter_name_appeared (DBusConnection connection, string name, string name_owner) {
+ try {
+ var greeter = Bus.get_proxy_sync<UnityGreeter> (BusType.SESSION, name, "/list");
+ greeter.entry_selected.connect (handle_entry_selected);
+ unity_greeter = greeter;
+ } catch (IOError error) {
+ warning ("error: %s", error.message);
+ }
+ }
+
+ [DBus (visible = false)]
+ private void handle_unity_greeter_name_vanished (DBusConnection connection, string name) {
+ unity_greeter = null;
+ }
+
+ [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 {
+ var session = Bus.get_proxy_sync<UnitySession> (BusType.SESSION, name, "/org/ayatana/Unity/Session");
+
+ session.locked.connect (() => {
+ var sources = get_sources ();
+
+ if (sources.length > 0) {
+ var current = get_current ();
+
+ if (current < sources.length && !sources[current].is_xkb) {
+ for (var i = 0; i < sources.length; i++) {
+ if (sources[i].is_xkb) {
+ get_active_action ().change_state (new Variant.uint32 (i));
+ break;
+ }
+ }
+ }
+ }
+ });
+ session.unlocked.connect (() => {
+ get_active_action ().change_state (new Variant.uint32 (get_current ()));
+ });
+
+ unity_session = session;
+ } 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, "/org/ayatana/Unity/WindowStack");
+ update_window_sources ();
+ } catch (IOError error) {
+ warning ("error: %s", error.message);
+ }
+ }
+
+ [DBus (visible = false)]
+ private void handle_window_stack_name_vanished (DBusConnection connection, string name) {
+ window_stack = null;
+ }
+
+ [DBus (visible = false)]
+ private void handle_bus_acquired (DBusConnection connection, string name) {
+ try {
+ connection.export_action_group ("/org/ayatana/indicator/keyboard", get_action_group ());
+ connection.export_menu_model ("/org/ayatana/indicator/keyboard/desktop", get_desktop_menu ());
+ connection.export_menu_model ("/org/ayatana/indicator/keyboard/desktop_greeter", get_desktop_greeter_menu ());
+ connection.export_menu_model ("/org/ayatana/indicator/keyboard/desktop_lockscreen", get_desktop_lockscreen_menu ());
+ } catch (Error error) {
+ warning ("error: %s", error.message);
+ }
+ }
+
+ [DBus (visible = false)]
+ private void handle_name_lost (DBusConnection? connection, string name) {
+ down ();
+ }
+
+ [DBus (visible = false)]
+ public static int main (string[] args) {
+ Service.service = new Service (ref args);
+
+ Posix.signal (Posix.SIGTERM, (code) => {
+ Service.service.down ();
+ });
+
+ Service.service.up ();
+
+ return 0;
+ }
+}
diff --git a/src/source.vala b/src/source.vala
new file mode 100644
index 00000000..b7d7a971
--- /dev/null
+++ b/src/source.vala
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2013 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.Source : Object {
+
+ private static Gnome.XkbInfo? xkb_info;
+ private static IBus.Bus? ibus_bus;
+ private static Fcitx.InputMethod? fcitx_proxy;
+
+ private string? xkb;
+ private string? ibus;
+ private string? fcitx;
+
+ private string? _name;
+ private string? _short_name;
+ private string? _layout;
+ private string? _variant;
+ private Icon? _icon;
+ private uint _subscript;
+ private bool _show_subscript;
+ private bool _use_gtk;
+
+ public string? name {
+ get { if (_name == null) { _name = _get_name (); } return _name; }
+ }
+
+ public string? short_name {
+ get { if (_short_name == null) { _short_name = _get_short_name (); } return _short_name; }
+ }
+
+ public string? layout {
+ get { if (_layout == null) { _layout = _get_layout (); } return _layout; }
+ }
+
+ public string? variant {
+ get { if (_variant == null) { _variant = _get_variant (); } return _variant; }
+ }
+
+ public Icon? icon {
+ get { if (_icon == null) { _icon = _get_icon (); } return _icon; }
+ set { _icon = value; }
+ }
+
+ public uint subscript {
+ get { return _subscript; }
+ set { _subscript = value; icon = null; }
+ }
+
+ public bool show_subscript {
+ get { return _show_subscript; }
+ set { _show_subscript = value; icon = null; }
+ }
+
+ public bool use_gtk {
+ get { return _use_gtk; }
+ construct set { _use_gtk = value; icon = null; }
+ }
+
+ public bool is_xkb {
+ get { return xkb != null; }
+ }
+
+ public bool is_ibus {
+ get { return ibus != null; }
+ }
+
+ public bool is_fcitx {
+ get { return fcitx != null; }
+ }
+
+ public Source (Variant variant, bool use_gtk = false) {
+ Object (use_gtk: use_gtk);
+
+ if (variant.is_of_type (new VariantType ("(ss)"))) {
+ unowned string type;
+ unowned string name;
+
+ variant.get ("(&s&s)", out type, out name);
+
+ if (type == "xkb") {
+ xkb = name;
+ } else if (type == "ibus") {
+ ibus = name;
+ } else if (type == "fcitx") {
+ fcitx = name;
+ }
+ } else if (variant.is_of_type (new VariantType ("a{ss}"))) {
+ foreach (var pair in variant) {
+ unowned string key;
+ unowned string value;
+
+ ((!) pair).get ("{&s&s}", out key, out value);
+
+ if (key == "xkb") {
+ xkb = value;
+ } else if (key == "ibus") {
+ ibus = value;
+ } else if (key == "fcitx") {
+ fcitx = value;
+ }
+ }
+ }
+ }
+
+ private static Gnome.XkbInfo get_xkb_info () {
+ if (xkb_info == null) {
+ xkb_info = new Gnome.XkbInfo ();
+ }
+
+ return (!) xkb_info;
+ }
+
+ private static IBus.Bus get_ibus_bus () {
+ if (ibus_bus == null) {
+ IBus.init ();
+ ibus_bus = new IBus.Bus ();
+ }
+
+ return (!) ibus_bus;
+ }
+
+ private static Fcitx.InputMethod get_fcitx_proxy () throws Error {
+ if (fcitx_proxy == null) {
+ fcitx_proxy = new Fcitx.InputMethod (BusType.SESSION, DBusProxyFlags.NONE, 0);
+ }
+
+ return (!) fcitx_proxy;
+ }
+
+ private IBus.EngineDesc? get_engine () {
+ IBus.EngineDesc? engine = null;
+
+ if (ibus != null) {
+ var names = new string[2];
+ names[0] = (!) ibus;
+
+ var engines = get_ibus_bus ().get_engines_by_names (names);
+
+ if (engines.length > 0) {
+ engine = engines[0];
+ }
+ }
+
+ return engine;
+ }
+
+ protected virtual string? _get_name () {
+ string? name = null;
+
+ if (xkb != null) {
+ string? display_name = null;
+ string? layout = null;
+
+ get_xkb_info ().get_layout_info ((!) xkb, out display_name, null, out layout, null);
+
+ var has_display_name = display_name != null && ((!) display_name).get_char () != '\0';
+ var has_layout = layout != null && ((!) layout).get_char () != '\0';
+
+ if (has_display_name) {
+ name = display_name;
+ } else if (has_layout) {
+ string? language = Xkl.get_language_name ((!) layout);
+ string? country = Xkl.get_country_name ((!) layout);
+ var has_language = language != null && ((!) language).get_char () != '\0';
+ var has_country = country != null && ((!) country).get_char () != '\0';
+
+ if (has_language && has_country) {
+ name = @"$((!) language) ($((!) country))";
+ } else if (has_language) {
+ name = language;
+ } else if (has_country) {
+ name = country;
+ }
+ }
+
+ if (name == null || ((!) name).get_char () == '\0') {
+ name = xkb;
+ }
+ } else if (ibus != null) {
+ var engine = get_engine ();
+
+ if (engine != null) {
+ string? language = ((!) engine).get_language ();
+ string? display_name = ((!) engine).get_longname ();
+ var has_language = language != null && ((!) language).get_char () != '\0';
+ var has_display_name = display_name != null && ((!) display_name).get_char () != '\0';
+
+ if (has_language) {
+ language = Xkl.get_language_name ((!) language);
+ has_language = language != null && ((!) language).get_char () != '\0';
+ }
+
+ if (has_language && has_display_name) {
+ name = @"$((!) language) ($((!) display_name))";
+ } else if (has_language) {
+ name = language;
+ } else if (has_display_name) {
+ name = display_name;
+ }
+ }
+
+ if (name == null || ((!) name).get_char () == '\0') {
+ name = ibus;
+ }
+ } else if (fcitx != null) {
+ try {
+ var input_methods = get_fcitx_proxy ().get_imlist_nofree ();
+
+ for (var i = 0; i < input_methods.length; i++) {
+ if (input_methods.get (i).unique_name == (!) fcitx) {
+ name = input_methods.get (i).name;
+ break;
+ }
+ }
+ } catch (Error error) {
+ warning ("error: %s", error.message);
+ }
+
+ if (name == null || ((!) name).get_char () == '\0') {
+ name = fcitx;
+ }
+ }
+
+ return name;
+ }
+
+ protected virtual string? _get_short_name () {
+ string? short_name = null;
+
+ if (xkb != null) {
+ get_xkb_info ().get_layout_info ((!) xkb, null, out short_name, null, null);
+
+ if (short_name == null || ((!) short_name).get_char () == '\0') {
+ short_name = xkb;
+ }
+ } else if (ibus != null) {
+ var engine = get_engine ();
+
+ if (engine != null) {
+ short_name = ((!) engine).get_name ();
+ }
+
+ if (short_name == null || ((!) short_name).get_char () == '\0') {
+ short_name = ibus;
+ }
+ } else if (fcitx != null) {
+ try {
+ var input_methods = get_fcitx_proxy ().get_imlist_nofree ();
+
+ for (var i = 0; i < input_methods.length; i++) {
+ if (input_methods.get (i).unique_name == (!) fcitx) {
+ short_name = input_methods.get (i).langcode;
+ break;
+ }
+ }
+ } catch (Error error) {
+ warning ("error: %s", error.message);
+ }
+
+ if (short_name == null || ((!) short_name).get_char () == '\0') {
+ short_name = fcitx;
+ }
+ }
+
+ return abbreviate (short_name);
+ }
+
+ protected virtual string? _get_layout () {
+ string? layout = null;
+
+ if (xkb != null) {
+ get_xkb_info ().get_layout_info ((!) xkb, null, null, out layout, null);
+ }
+
+ var has_layout = layout != null && ((!) layout).get_char () != '\0';
+
+ if (!has_layout) {
+ var engine = get_engine ();
+
+ if (engine != null) {
+ layout = ((!) engine).get_layout ();
+ }
+ }
+
+ if (layout == null || ((!) layout).get_char () == '\0') {
+ layout = xkb;
+ }
+
+ return layout;
+ }
+
+ protected virtual string? _get_variant () {
+ string? variant = null;
+
+ if (xkb != null) {
+ get_xkb_info ().get_layout_info ((!) xkb, null, null, null, out variant);
+ }
+
+ var has_variant = variant != null && ((!) variant).get_char () != '\0';
+
+ if (!has_variant) {
+ var engine = get_engine ();
+
+ if (engine != null) {
+ variant = ((!) engine).get_layout_variant ();
+ }
+ }
+
+ if (variant == null || ((!) variant).get_char () == '\0') {
+ variant = null;
+ }
+
+ return variant;
+ }
+
+ private Gtk.StyleContext? get_style_context () {
+ Gtk.StyleContext? context = null;
+
+ if (_use_gtk) {
+ Gdk.Screen? screen = Gdk.Screen.get_default ();
+
+ if (screen != null) {
+ var style_context = new Gtk.StyleContext ();
+ style_context.set_screen ((!) screen);
+
+ var path = new Gtk.WidgetPath ();
+ path.append_type (typeof (Gtk.MenuItem));
+ style_context.set_path (path);
+
+ context = style_context;
+ }
+ }
+
+ return context;
+ }
+
+ protected virtual Icon? create_icon () {
+ Icon? icon = null;
+
+ var style = get_style_context ();
+
+ if (style != null) {
+ const int W = 22;
+ const int H = 22;
+ const int w = 20;
+ const int h = 20;
+ const double R = 2.0;
+ const double TEXT_SIZE = 12.0;
+ const double SUBSCRIPT_SIZE = 8.0;
+
+ Pango.FontDescription description;
+ var colour = ((!) style).get_color (Gtk.StateFlags.NORMAL);
+ colour = { 0.5, 0.5, 0.5, 1.0 };
+ ((!) style).get (Gtk.StateFlags.NORMAL, Gtk.STYLE_PROPERTY_FONT, out description);
+
+ var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, W, H);
+ var context = new Cairo.Context (surface);
+
+ context.translate (0.5 * (W - w), 0.5 * (H - h));
+
+ context.new_sub_path ();
+ context.arc (R, R, R, Math.PI, -0.5 * Math.PI);
+ context.arc (w - R, R, R, -0.5 * Math.PI, 0);
+ context.arc (w - R, h - R, R, 0, 0.5 * Math.PI);
+ context.arc (R, h - R, R, 0.5 * Math.PI, Math.PI);
+ context.close_path ();
+
+ context.set_source_rgba (colour.red, colour.green, colour.blue, colour.alpha);
+ context.fill ();
+ context.set_operator (Cairo.Operator.CLEAR);
+
+ if (short_name != null) {
+ var text_layout = Pango.cairo_create_layout (context);
+ text_layout.set_alignment (Pango.Alignment.CENTER);
+ description.set_absolute_size (Pango.units_from_double (TEXT_SIZE));
+ text_layout.set_font_description (description);
+ text_layout.set_text ((!) short_name, -1);
+ Pango.cairo_update_layout (context, text_layout);
+ int text_width;
+ int text_height;
+ text_layout.get_pixel_size (out text_width, out text_height);
+
+ if (_show_subscript) {
+ var subscript_layout = Pango.cairo_create_layout (context);
+ subscript_layout.set_alignment (Pango.Alignment.CENTER);
+ description.set_absolute_size (Pango.units_from_double (SUBSCRIPT_SIZE));
+ subscript_layout.set_font_description (description);
+ subscript_layout.set_text (@"$_subscript", -1);
+ Pango.cairo_update_layout (context, subscript_layout);
+ int subscript_width;
+ int subscript_height;
+ subscript_layout.get_pixel_size (out subscript_width, out subscript_height);
+
+ context.save ();
+ context.translate ((w - (text_width + subscript_width)) / 2, (h - text_height) / 2);
+ Pango.cairo_layout_path (context, text_layout);
+ context.fill ();
+ context.restore ();
+
+ context.save ();
+ context.translate ((w + (text_width - subscript_width)) / 2, (h + text_height) / 2 - subscript_height);
+ Pango.cairo_layout_path (context, subscript_layout);
+ context.fill ();
+ context.restore ();
+ } else {
+ context.save ();
+ context.translate ((w - text_width) / 2, (h - text_height) / 2);
+ Pango.cairo_layout_path (context, text_layout);
+ context.fill ();
+ context.restore ();
+ }
+ }
+
+ var buffer = new ByteArray ();
+
+ surface.write_to_png_stream ((data) => {
+ buffer.append (data);
+ return Cairo.Status.SUCCESS;
+ });
+
+ icon = new BytesIcon (ByteArray.free_to_bytes ((owned) buffer));
+ }
+
+ return icon;
+ }
+
+ private Icon? _get_icon () {
+ Icon? icon = null;
+
+ var engine = get_engine ();
+
+ if (engine != null) {
+ string? icon_name = ((!) engine).get_icon ();
+ var has_icon_name = icon_name != null && ((!) icon_name).get_char () != '\0';
+
+ if (has_icon_name) {
+ try {
+ icon = Icon.new_for_string ((!) icon_name);
+ } catch (Error error) {
+ warning ("error: %s", error.message);
+ }
+ }
+ }
+
+ if (icon == null && short_name != null) {
+ string icon_name;
+
+ if (_show_subscript) {
+ icon_name = @"indicator-keyboard-$((!) short_name)-$_subscript";
+ } else {
+ icon_name = @"indicator-keyboard-$((!) short_name)";
+ }
+
+ if (_use_gtk) {
+ var icon_theme = Gtk.IconTheme.get_default ();
+ Gtk.IconInfo? icon_info = icon_theme.lookup_icon (icon_name, 22, 0);
+
+ if (icon_info != null) {
+ icon = new ThemedIcon (icon_name);
+ }
+ } else {
+ icon = new ThemedIcon (icon_name);
+ }
+ }
+
+ if (icon == null) {
+ icon = create_icon ();
+ }
+
+ return icon;
+ }
+}
diff --git a/src/unity-greeter.vala b/src/unity-greeter.vala
new file mode 100644
index 00000000..5ca398ec
--- /dev/null
+++ b/src/unity-greeter.vala
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 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="org.ayatana.UnityGreeter.List")]
+public interface UnityGreeter : Object {
+
+ public abstract string get_active_entry () throws IOError;
+ public abstract void set_active_entry (string entry_name) throws IOError;
+
+ public signal void entry_selected (string entry_name);
+}
diff --git a/src/unity-session.vala b/src/unity-session.vala
new file mode 100644
index 00000000..15337c36
--- /dev/null
+++ b/src/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="org.ayatana.Unity.Session")]
+public interface UnitySession : Object {
+
+ public signal void locked ();
+ public signal void unlocked ();
+}
diff --git a/src/window-stack.vala b/src/window-stack.vala
new file mode 100644
index 00000000..a943da6a
--- /dev/null
+++ b/src/window-stack.vala
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 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 struct WindowInfo {
+
+ public uint window_id;
+ public string app_id;
+ public bool focused;
+ public uint stage;
+}
+
+[DBus (name="org.ayatana.Unity.WindowStack")]
+public interface WindowStack : Object {
+
+ public abstract string get_app_id_from_pid (uint pid) throws IOError;
+ public abstract string[] get_window_properties (uint window_id, string app_id, string[] property_names) throws IOError;
+ public abstract WindowInfo[] get_window_stack () throws IOError;
+
+ public signal void focused_window_changed (uint window_id, string app_id, uint stage);
+ public signal void window_created (uint window_id, string app_id);
+ public signal void window_destroyed (uint window_id, string app_id);
+}