aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRobert Tari <robert@tari.in>2021-01-24 03:38:44 +0100
committerMike Gabriel <mike.gabriel@das-netzwerkteam.de>2021-01-27 17:10:40 +0100
commitb85daf94830a3ca39d59237fdb07bc33ede1c450 (patch)
tree8d704b1d8614759da71fbe42dca737c0e2261142 /src
parent9b8fb0492c8c05630644d840aef520f3c6b17829 (diff)
downloadayatana-indicator-keyboard-b85daf94830a3ca39d59237fdb07bc33ede1c450.tar.gz
ayatana-indicator-keyboard-b85daf94830a3ca39d59237fdb07bc33ede1c450.tar.bz2
ayatana-indicator-keyboard-b85daf94830a3ca39d59237fdb07bc33ede1c450.zip
100% re-write of the keyboard indicator in plain C.
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt28
-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/keyboard.c287
-rw-r--r--src/keyboard.h37
-rw-r--r--src/main.c40
-rw-r--r--src/main.vala1265
-rw-r--r--src/service.c412
-rw-r--r--src/service.h36
-rw-r--r--src/source.vala487
-rw-r--r--src/unity-greeter.vala26
-rw-r--r--src/unity-session.vala24
-rw-r--r--src/utils.c167
-rw-r--r--src/utils.h38
-rw-r--r--src/window-stack.vala37
19 files changed, 1045 insertions, 2444 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 00000000..3eb7caf9
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 2.8.12)
+set (SERVICE_LIB "ayatanaindicatorkeyboardservice")
+set (SERVICE_EXEC "ayatana-indicator-keyboard-service")
+
+add_definitions(-DG_LOG_DOMAIN="ayatana-indicator-keyboard")
+
+# handwritten sources
+set(SERVICE_MANUAL_SOURCES keyboard.c service.c utils.c)
+
+# generated sources
+set(SERVICE_GENERATED_SOURCES)
+
+# add the bin dir to our include path so the code can find the generated header files
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+# add warnings/coverage info on handwritten files but not the autogenerated ones...
+set_source_files_properties(${SERVICE_MANUAL_SOURCES} PROPERTIES COMPILE_FLAGS "${C_WARNING_ARGS} -g -std=c99")
+
+# the service library for tests to link against (basically, everything except main())
+add_library(${SERVICE_LIB} STATIC ${SERVICE_MANUAL_SOURCES} ${SERVICE_GENERATED_SOURCES})
+include_directories(${CMAKE_SOURCE_DIR})
+link_directories(${SERVICE_DEPS_LIBRARY_DIRS})
+
+# the executable: lib + main()
+add_executable (${SERVICE_EXEC} main.c)
+set_source_files_properties(${SERVICE_SOURCES} main.c PROPERTIES COMPILE_FLAGS "${C_WARNING_ARGS} -std=c99")
+target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES})
+install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR})
diff --git a/src/Makefile.am b/src/Makefile.am
deleted file mode 100644
index a0fc4bb5..00000000
--- a/src/Makefile.am
+++ /dev/null
@@ -1,55 +0,0 @@
-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
deleted file mode 100644
index 9824bc26..00000000
--- a/src/common.vala
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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
deleted file mode 100644
index a240f00b..00000000
--- a/src/ibus-menu.vala
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 2a380efd..00000000
--- a/src/ibus-panel.vala
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 2cfa52c6..00000000
--- a/src/indicator-menu.vala
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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
deleted file mode 100644
index af1628f6..00000000
--- a/src/keyboard-plugin.vala
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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/keyboard.c b/src/keyboard.c
new file mode 100644
index 00000000..1c0b22a0
--- /dev/null
+++ b/src/keyboard.c
@@ -0,0 +1,287 @@
+#include <X11/XKBlib.h>
+#include <libxklavier/xklavier.h>
+#include "keyboard.h"
+
+enum
+{
+ LAYOUT_CHANGED,
+ CONFIG_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint m_lSignals[LAST_SIGNAL];
+
+struct _KeyboardPrivate
+{
+ GPollFD cPollFD;
+ GSourceFuncs cSourceFuncs;
+ XklEngine *pEngine;
+ GHashTable *lLayouts;
+ Display *pDisplay;
+ guint nLayout;
+ gint nXkbEventType;
+ XklConfigRec *pConfigRec;
+};
+
+typedef KeyboardPrivate priv_t;
+
+G_DEFINE_TYPE_WITH_PRIVATE(Keyboard, keyboard, G_TYPE_OBJECT)
+
+typedef struct _Layout
+{
+ gchar *sId;
+ gchar *sLanguage;
+ gchar *sDescription;
+
+} Layout;
+
+typedef struct _LayoutParser
+{
+ const gchar *sLayout;
+ const gchar *sLanguage;
+ Keyboard *pKeyboard;
+
+} LayoutParser;
+
+typedef struct _Source
+{
+ GSource cSource;
+ Keyboard *pKeyboard;
+
+} Source;
+
+static gboolean onCheckEvent(Display *pDisplay, XEvent *pEvent, XPointer pData)
+{
+ gint *pXkbEventType = (gint*)pData;
+
+ if (pEvent->type == *pXkbEventType)
+ {
+ XkbEvent *pXkbEvent = (XkbEvent*)pEvent;
+
+ if (pXkbEvent->any.xkb_type == XkbStateNotify || pXkbEvent->any.xkb_type == XkbNamesNotify)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean onCheck(GSource *pSource)
+{
+ Keyboard *pKeyboard = ((Source*)pSource)->pKeyboard;
+ XEvent cEvent;
+ gboolean bEvent = XCheckIfEvent(pKeyboard->pPrivate->pDisplay, &cEvent, onCheckEvent, (XPointer)&pKeyboard->pPrivate->nXkbEventType);
+
+ if (bEvent)
+ {
+ XklConfigRec *pConfigRec = xkl_config_rec_new();
+ xkl_config_rec_get_from_server(pConfigRec, pKeyboard->pPrivate->pEngine);
+ gboolean bConfigChanged = FALSE;
+ gboolean bLayoutChanged = FALSE;
+
+ if (!xkl_config_rec_equals(pKeyboard->pPrivate->pConfigRec, pConfigRec))
+ {
+ if (g_strv_length(pKeyboard->pPrivate->pConfigRec->layouts) > g_strv_length(pConfigRec->layouts))
+ {
+ xkl_engine_lock_group(pKeyboard->pPrivate->pEngine, 0);
+ pKeyboard->pPrivate->nLayout = 0;
+ bLayoutChanged = TRUE;
+ }
+
+ xkl_config_rec_get_from_server(pKeyboard->pPrivate->pConfigRec, pKeyboard->pPrivate->pEngine);
+ bConfigChanged = TRUE;
+ }
+
+ g_object_unref(pConfigRec);
+ pConfigRec = NULL;
+
+ if (((XkbEvent*)&cEvent)->any.xkb_type == XkbStateNotify && !bConfigChanged && ((XkbEvent*)&cEvent)->state.group != pKeyboard->pPrivate->nLayout && ((XkbEvent*)&cEvent)->state.group < g_strv_length(pKeyboard->pPrivate->pConfigRec->layouts))
+ {
+ pKeyboard->pPrivate->nLayout = ((XkbEvent*)&cEvent)->state.group;
+ bLayoutChanged = TRUE;
+ }
+
+ if (bLayoutChanged)
+ {
+ g_signal_emit(pKeyboard, m_lSignals[LAYOUT_CHANGED], 0);
+ }
+
+ if (bConfigChanged)
+ {
+ g_signal_emit(pKeyboard, m_lSignals[CONFIG_CHANGED], 0);
+ }
+ }
+
+ return FALSE;
+}
+
+static void freeLayout(gpointer pData)
+{
+ Layout *pLayout = pData;
+
+ g_return_if_fail(pLayout != NULL);
+
+ g_free(pLayout->sId);
+ g_free(pLayout->sLanguage);
+ g_free(pLayout->sDescription);
+ g_slice_free(Layout, pLayout);
+}
+
+static void onParseLayouts(XklConfigRegistry *pRegistry, const XklConfigItem * pItem, gpointer pData)
+{
+ LayoutParser *pLayoutParser = (LayoutParser*)pData;
+ Layout *pLayout = g_slice_new0(Layout);
+
+ if (pLayoutParser->sLayout)
+ {
+ pLayout->sId = g_strjoin("+", pLayoutParser->sLayout, pItem->name, NULL);
+ pLayout->sLanguage = g_strdup(pLayoutParser->sLanguage);
+ pLayout->sDescription = g_strdup(pItem->description);
+ }
+ else
+ {
+ pLayout->sId = g_strdup(pItem->name);
+ pLayout->sLanguage = g_strdup(pItem->short_description);
+ pLayout->sDescription = g_strdup(pItem->description);
+ }
+
+ g_hash_table_replace(pLayoutParser->pKeyboard->pPrivate->lLayouts, pLayout->sId, pLayout);
+
+ if (pLayoutParser->sLayout == NULL)
+ {
+ LayoutParser cLayoutParser;
+ cLayoutParser.sLayout = pItem->name;
+ cLayoutParser.pKeyboard = pLayoutParser->pKeyboard;
+ cLayoutParser.sLanguage = pItem->short_description;
+
+ xkl_config_registry_foreach_layout_variant(pRegistry, pItem->name, onParseLayouts, &cLayoutParser);
+ }
+}
+
+void keyboard_AddSource(Keyboard *pKeyboard)
+{
+ XkbQueryExtension(pKeyboard->pPrivate->pDisplay, 0, &pKeyboard->pPrivate->nXkbEventType, 0, 0, 0);
+ XkbSelectEventDetails(pKeyboard->pPrivate->pDisplay, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);
+
+ pKeyboard->pPrivate->cPollFD.fd = ConnectionNumber(pKeyboard->pPrivate->pDisplay);
+ pKeyboard->pPrivate->cPollFD.events = G_IO_IN | G_IO_HUP | G_IO_ERR;
+ pKeyboard->pPrivate->cPollFD.revents = 0;
+ pKeyboard->pPrivate->cSourceFuncs.prepare = NULL;
+ pKeyboard->pPrivate->cSourceFuncs.check = onCheck;
+ pKeyboard->pPrivate->cSourceFuncs.dispatch = NULL;
+ pKeyboard->pPrivate->cSourceFuncs.finalize = NULL;
+
+ GSource *pSource = g_source_new(&pKeyboard->pPrivate->cSourceFuncs, sizeof(Source));
+ ((Source*)pSource)->pKeyboard = pKeyboard;
+ g_source_add_poll(pSource, &pKeyboard->pPrivate->cPollFD);
+ g_source_attach(pSource, NULL);
+}
+
+guint keyboard_GetNumLayouts(Keyboard *pKeyboard)
+{
+ return g_strv_length(pKeyboard->pPrivate->pConfigRec->layouts);
+}
+
+void keyboard_GetLayout(Keyboard *pKeyboard, gint nLayout, gchar **pLanguage, gchar **pDescription)
+{
+ if (nLayout == -1)
+ {
+ nLayout = pKeyboard->pPrivate->nLayout;
+ }
+
+ gchar *sLayout = pKeyboard->pPrivate->pConfigRec->layouts[nLayout];
+ gchar *sVariant = pKeyboard->pPrivate->pConfigRec->variants[nLayout];
+ gchar *sId;
+
+ if (strlen(sVariant))
+ {
+ sId = g_strconcat(sLayout, "+", sVariant, NULL);
+ }
+ else
+ {
+ sId = g_strdup(sLayout);
+ }
+
+ const Layout *pLayout;
+ g_hash_table_lookup_extended(pKeyboard->pPrivate->lLayouts, sId, NULL, (gpointer*)&pLayout);
+
+ if (pLanguage != NULL)
+ {
+ *pLanguage = g_strndup(pLayout->sLanguage, 2);
+ }
+
+ if (pDescription != NULL)
+ {
+ *pDescription = g_strdup(pLayout->sDescription);
+ }
+
+ g_free(sId);
+}
+
+void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout)
+{
+ xkl_engine_lock_group(pKeyboard->pPrivate->pEngine, nLayout);
+}
+
+static void onDispose(GObject *pObject)
+{
+ Keyboard *self = G_KEYBOARD(pObject);
+
+ if (self->pPrivate->lLayouts)
+ {
+ g_hash_table_destroy(self->pPrivate->lLayouts);
+ }
+
+ if (self->pPrivate->pConfigRec)
+ {
+ g_object_unref(self->pPrivate->pConfigRec);
+ self->pPrivate->pConfigRec = NULL;
+ }
+
+ G_OBJECT_CLASS(keyboard_parent_class)->dispose(pObject);
+}
+
+static void keyboard_class_init(KeyboardClass *klass)
+{
+ GObjectClass *pClass = G_OBJECT_CLASS(klass);
+ pClass->dispose = onDispose;
+ m_lSignals[LAYOUT_CHANGED] = g_signal_new(KEYBOARD_LAYOUT_CHANGED, G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+ m_lSignals[CONFIG_CHANGED] = g_signal_new(KEYBOARD_CONFIG_CHANGED, G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+}
+
+Keyboard* keyboard_new()
+{
+ GObject *pObject = g_object_new(G_TYPE_KEYBOARD, NULL);
+
+ return G_KEYBOARD(pObject);
+}
+
+static void keyboard_init(Keyboard *self)
+{
+ self->pPrivate = keyboard_get_instance_private(self);
+ self->pPrivate->pDisplay = XOpenDisplay(NULL);
+
+ g_assert(self->pPrivate->pDisplay);
+
+ self->pPrivate->pEngine = xkl_engine_get_instance(self->pPrivate->pDisplay);
+
+ g_assert(self->pPrivate->pEngine);
+
+ self->pPrivate->lLayouts = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, freeLayout);
+ XklConfigRegistry *pRegistry = xkl_config_registry_get_instance(self->pPrivate->pEngine);
+ xkl_config_registry_load(pRegistry, TRUE);
+
+ LayoutParser cLayoutParser;
+ cLayoutParser.sLayout = NULL;
+ cLayoutParser.pKeyboard = self;
+ cLayoutParser.sLanguage = NULL;
+ xkl_config_registry_foreach_layout(pRegistry, onParseLayouts, &cLayoutParser);
+
+ xkl_engine_start_listen(self->pPrivate->pEngine, XKLL_TRACK_KEYBOARD_STATE);
+
+ self->pPrivate->pConfigRec = xkl_config_rec_new();
+ xkl_config_rec_get_from_server(self->pPrivate->pConfigRec, self->pPrivate->pEngine);
+ XklState *pState = xkl_engine_get_current_state(self->pPrivate->pEngine);
+ self->pPrivate->nLayout = pState->group;
+}
diff --git a/src/keyboard.h b/src/keyboard.h
new file mode 100644
index 00000000..50e8bff0
--- /dev/null
+++ b/src/keyboard.h
@@ -0,0 +1,37 @@
+#ifndef __KEYBOARD_H__
+#define __KEYBOARD_H__
+
+G_BEGIN_DECLS
+
+#define KEYBOARD_LAYOUT_CHANGED "layout-changed"
+#define KEYBOARD_CONFIG_CHANGED "config-changed"
+
+#define G_KEYBOARD(o) (G_TYPE_CHECK_INSTANCE_CAST((o), G_TYPE_KEYBOARD, Keyboard))
+#define G_TYPE_KEYBOARD (keyboard_get_type())
+#define G_IS_KEYBOARD(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), G_TYPE_KEYBOARD))
+
+typedef struct _Keyboard Keyboard;
+typedef struct _KeyboardClass KeyboardClass;
+typedef struct _KeyboardPrivate KeyboardPrivate;
+
+struct _Keyboard
+{
+ GObject parent;
+ KeyboardPrivate *pPrivate;
+};
+
+struct _KeyboardClass
+{
+ GObjectClass parent_class;
+};
+
+GType keyboard_get_type(void);
+Keyboard* keyboard_new();
+void keyboard_AddSource(Keyboard *pKeyboard);
+guint keyboard_GetNumLayouts(Keyboard *pKeyboard);
+void keyboard_GetLayout(Keyboard *pKeyboard, gint nLayout, gchar **pLanguage, gchar **pDescription);
+void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout);
+
+G_END_DECLS
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 00000000..eb6a1b9a
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,40 @@
+#include <locale.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-unix.h>
+#include "service.h"
+
+static void onNameLost(gpointer instance G_GNUC_UNUSED, gpointer pLoop)
+{
+ g_message("exiting: service couldn't acquire or lost ownership of busname");
+ g_main_loop_quit((GMainLoop*)pLoop);
+}
+
+static gboolean onQuit(gpointer pData)
+{
+ GMainLoop *pLoop = (GMainLoop*)pData;
+ g_main_loop_quit(pLoop);
+
+ return G_SOURCE_REMOVE;
+}
+
+int main(int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED)
+{
+ setlocale(LC_ALL, "");
+ bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
+ textdomain(GETTEXT_PACKAGE);
+
+ IndicatorKeyboardService *pService = indicator_keyboard_service_new(NULL);
+ GMainLoop *pLoop = g_main_loop_new(NULL, FALSE);
+
+ g_signal_connect(pService, "name-lost", G_CALLBACK(onNameLost), pLoop);
+ g_unix_signal_add(SIGINT, onQuit, pLoop);
+
+ indicator_keyboard_service_AddKeyboardSource(pService);
+
+ g_main_loop_run(pLoop);
+ g_main_loop_unref(pLoop);
+ g_clear_object(&pService);
+
+ return 0;
+}
diff --git a/src/main.vala b/src/main.vala
deleted file mode 100644
index 9bb3eb1f..00000000
--- a/src/main.vala
+++ /dev/null
@@ -1,1265 +0,0 @@
-/*
- * 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/service.c b/src/service.c
new file mode 100644
index 00000000..5353fb24
--- /dev/null
+++ b/src/service.c
@@ -0,0 +1,412 @@
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "service.h"
+#include "utils.h"
+
+#define BUS_NAME "org.ayatana.indicator.keyboard"
+#define BUS_PATH "/org/ayatana/indicator/keyboard"
+
+static guint m_nSignal = 0;
+
+enum
+{
+ SECTION_HEADER = (1 << 0),
+ SECTION_LAYOUTS = (1 << 1),
+ SECTION_SETTINGS = (1 << 2)
+};
+
+enum
+{
+ PROFILE_PHONE,
+ PROFILE_DESKTOP,
+ PROFILE_GREETER,
+ N_PROFILES
+};
+
+static const char * const m_lMenuNames[N_PROFILES] =
+{
+ "phone",
+ "desktop",
+ "greeter"
+};
+
+struct ProfileMenuInfo
+{
+ GMenu *pMenu;
+ GMenu *pSubmenu;
+ guint nExportId;
+};
+
+struct _IndicatorKeyboardServicePrivate
+{
+ GCancellable *pCancellable;
+ guint nOwnId;
+ guint nActionsId;
+ GDBusConnection *pConnection;
+ gboolean bMenusBuilt;
+ struct ProfileMenuInfo lMenus[N_PROFILES];
+ GSimpleActionGroup *pActionGroup;
+ GSimpleAction *pHeaderAction;
+ GSimpleAction *pSettingsAction;
+ GSimpleAction *pLayoutAction;
+ GMenu *pLayoutSection;
+ Keyboard *pKeyboard;
+};
+
+typedef IndicatorKeyboardServicePrivate priv_t;
+
+G_DEFINE_TYPE_WITH_PRIVATE(IndicatorKeyboardService, indicator_keyboard_service, G_TYPE_OBJECT)
+
+static GVariant* createHeaderState(IndicatorKeyboardService *self)
+{
+ GVariantBuilder cBuilder;
+ g_variant_builder_init(&cBuilder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&cBuilder, "{sv}", "title", g_variant_new_string(_("Keyboard")));
+ g_variant_builder_add(&cBuilder, "{sv}", "visible", g_variant_new_boolean(TRUE));
+
+ gchar *sLanguage;
+ keyboard_GetLayout(self->pPrivate->pKeyboard, -1, &sLanguage, NULL);
+
+ gchar *sIcon = g_strconcat("ayatana-indicator-keyboard-", sLanguage, NULL);
+ g_free(sLanguage);
+
+ GIcon *pIcon = g_themed_icon_new_with_default_fallbacks(sIcon);
+ g_free(sIcon);
+ g_variant_builder_add(&cBuilder, "{sv}", "accessible-desc", g_variant_new_string(_("Current keyboard layout")));
+
+ if (pIcon)
+ {
+ GVariant *pSerialized = g_icon_serialize(pIcon);
+
+ if (pSerialized != NULL)
+ {
+ g_variant_builder_add(&cBuilder, "{sv}", "icon", pSerialized);
+ g_variant_unref(pSerialized);
+ }
+
+ g_object_unref(pIcon);
+ }
+
+ return g_variant_builder_end(&cBuilder);
+}
+
+static GMenuModel* createDesktopLayoutSection(IndicatorKeyboardService *self, int nProfile)
+{
+ self->pPrivate->pLayoutSection = g_menu_new();
+
+ guint nLayouts = keyboard_GetNumLayouts(self->pPrivate->pKeyboard);
+
+ for (guint nLayout = 0; nLayout < nLayouts; nLayout++)
+ {
+ gchar *sLanguage;
+ gchar *sDescription;
+ keyboard_GetLayout(self->pPrivate->pKeyboard, nLayout, &sLanguage, &sDescription);
+ GMenuItem *pItem = g_menu_item_new(sDescription, NULL);
+ g_free(sDescription);
+ g_menu_item_set_action_and_target_value(pItem, "indicator.layout", g_variant_new_byte(nLayout));
+ g_menu_item_set_attribute_value(pItem, "x-ayatana-layout", g_variant_new_byte(nLayout));
+ gchar *sIcon = g_strconcat("ayatana-indicator-keyboard-", sLanguage, NULL);
+ g_free(sLanguage);
+ GIcon *pIcon = g_themed_icon_new_with_default_fallbacks(sIcon);
+ g_free(sIcon);
+ GVariant *pSerialized = g_icon_serialize(pIcon);
+
+ if (pSerialized != NULL)
+ {
+ g_menu_item_set_attribute_value(pItem, G_MENU_ATTRIBUTE_ICON, pSerialized);
+ g_variant_unref(pSerialized);
+ }
+
+ g_object_unref(pIcon);
+
+ g_menu_append_item(self->pPrivate->pLayoutSection, pItem);
+ g_object_unref(pItem);
+ }
+
+ return G_MENU_MODEL(self->pPrivate->pLayoutSection);
+}
+
+static GMenuModel* createDesktopSettingsSection(IndicatorKeyboardService *self)
+{
+ GMenu * pMenu = g_menu_new();
+ g_menu_append(pMenu, _("Keyboard Settings..."), "indicator.settings");
+
+ return G_MENU_MODEL(pMenu);
+}
+
+static void rebuildSection(GMenu *pMenu, int nPos, GMenuModel *pModel)
+{
+ g_menu_remove(pMenu, nPos);
+ g_menu_insert_section(pMenu, nPos, NULL, pModel);
+ g_object_unref(pModel);
+}
+
+static void rebuildNow(IndicatorKeyboardService *self, guint nSections)
+{
+ struct ProfileMenuInfo *pInfoDesktop = &self->pPrivate->lMenus[PROFILE_DESKTOP];
+ struct ProfileMenuInfo *pInfoGreeter = &self->pPrivate->lMenus[PROFILE_GREETER];
+
+ if (nSections & SECTION_HEADER)
+ {
+ g_simple_action_set_state(self->pPrivate->pHeaderAction, createHeaderState(self));
+ }
+
+ if (!self->pPrivate->bMenusBuilt)
+ {
+ return;
+ }
+
+ if (nSections & SECTION_LAYOUTS)
+ {
+ rebuildSection(pInfoDesktop->pSubmenu, 0, createDesktopLayoutSection(self, PROFILE_DESKTOP));
+ rebuildSection(pInfoGreeter->pSubmenu, 0, createDesktopLayoutSection(self, PROFILE_GREETER));
+ }
+
+ if (nSections & SECTION_SETTINGS)
+ {
+ rebuildSection(pInfoDesktop->pSubmenu, 1, createDesktopSettingsSection(self));
+ }
+}
+
+static void createMenu(IndicatorKeyboardService *self, int nProfile)
+{
+ GMenu *pMenu;
+ GMenu *pSubmenu;
+ GMenuItem *pItem;
+ GMenuModel *lSections[16];
+ guint nSection = 0;
+
+ g_assert(0 <= nProfile && nProfile < N_PROFILES);
+ g_assert(self->pPrivate->lMenus[nProfile].pMenu == NULL);
+
+ // Build the sections
+ if (nProfile == PROFILE_PHONE)
+ {
+ lSections[nSection++] = createDesktopLayoutSection(self, nProfile);
+ lSections[nSection++] = createDesktopSettingsSection(self);
+ }
+ else if (nProfile == PROFILE_DESKTOP)
+ {
+ lSections[nSection++] = createDesktopLayoutSection(self, nProfile);
+ lSections[nSection++] = createDesktopSettingsSection(self);
+ }
+ else if (nProfile == PROFILE_GREETER)
+ {
+ lSections[nSection++] = createDesktopLayoutSection(self, nProfile);
+ }
+
+ // Add sections to the submenu
+ pSubmenu = g_menu_new();
+
+ for (guint i = 0; i < nSection; ++i)
+ {
+ g_menu_append_section(pSubmenu, NULL, lSections[i]);
+ g_object_unref(lSections[i]);
+ }
+
+ // Add submenu to the header
+ pItem = g_menu_item_new(NULL, "indicator._header");
+ g_menu_item_set_attribute(pItem, "x-ayatana-type", "s", "org.ayatana.indicator.root");
+ g_menu_item_set_submenu(pItem, G_MENU_MODEL(pSubmenu));
+ g_object_unref(pSubmenu);
+
+ // Add header to the menu
+ pMenu = g_menu_new();
+ g_menu_append_item(pMenu, pItem);
+ g_object_unref(pItem);
+
+ self->pPrivate->lMenus[nProfile].pMenu = pMenu;
+ self->pPrivate->lMenus[nProfile].pSubmenu = pSubmenu;
+}
+
+static void onLayoutChanged(Keyboard *pKeyboard, gpointer pData)
+{
+ IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData);
+ rebuildNow(self, SECTION_HEADER);
+}
+
+static void onConfigChanged(Keyboard *pKeyboard, gpointer pData)
+{
+ IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData);
+ rebuildNow(self, SECTION_LAYOUTS);
+}
+
+static void onLayoutSelected(GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
+{
+ IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData);
+ const guint8 nLayout = g_variant_get_byte(pVariant);
+ keyboard_SetLayout(self->pPrivate->pKeyboard, nLayout);
+}
+
+static void onSettings(GSimpleAction *pAction, GVariant *pVariant, gpointer pUserData)
+{
+ if (is_mate())
+ {
+ execute_command("mate-keyboard-properties");
+ }
+}
+
+static void initActions(IndicatorKeyboardService *self)
+{
+ GSimpleAction *pAction;
+ self->pPrivate->pActionGroup = g_simple_action_group_new();
+
+ pAction = g_simple_action_new_stateful("_header", NULL, createHeaderState(self));
+ g_action_map_add_action(G_ACTION_MAP(self->pPrivate->pActionGroup), G_ACTION(pAction));
+ self->pPrivate->pHeaderAction = pAction;
+
+ pAction = g_simple_action_new("layout", G_VARIANT_TYPE_BYTE);
+ g_action_map_add_action(G_ACTION_MAP(self->pPrivate->pActionGroup), G_ACTION(pAction));
+ self->pPrivate->pLayoutAction = pAction;
+ g_signal_connect(pAction, "activate", G_CALLBACK(onLayoutSelected), self);
+
+ pAction = g_simple_action_new("settings", NULL);
+ g_action_map_add_action(G_ACTION_MAP(self->pPrivate->pActionGroup), G_ACTION(pAction));
+ self->pPrivate->pSettingsAction = pAction;
+ g_signal_connect(pAction, "activate", G_CALLBACK(onSettings), self);
+}
+
+static void onBusAcquired(GDBusConnection *pConnection, const gchar *sName, gpointer pData)
+{
+ IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData);
+
+ g_debug("bus acquired: %s", sName);
+
+ self->pPrivate->pConnection = (GDBusConnection*)g_object_ref(G_OBJECT (pConnection));
+ guint nId;
+ GError *pError = NULL;
+
+ // Export the actions
+ if ((nId = g_dbus_connection_export_action_group(pConnection, BUS_PATH, G_ACTION_GROUP(self->pPrivate->pActionGroup), &pError)))
+ {
+ self->pPrivate->nActionsId = nId;
+ }
+ else
+ {
+ g_warning("cannot export action group: %s", pError->message);
+ g_clear_error(&pError);
+ }
+
+ GString *pPath = g_string_new(NULL);
+
+ // Export the menus
+ for (int nProfile = 0; nProfile < N_PROFILES; ++nProfile)
+ {
+ struct ProfileMenuInfo *pInfo = &self->pPrivate->lMenus[nProfile];
+
+ g_string_printf(pPath, "%s/%s", BUS_PATH, m_lMenuNames[nProfile]);
+
+ if ((nId = g_dbus_connection_export_menu_model(pConnection, pPath->str, G_MENU_MODEL(pInfo->pMenu), &pError)))
+ {
+ pInfo->nExportId = nId;
+ }
+ else
+ {
+ g_warning("cannot export %s menu: %s", pPath->str, pError->message);
+ g_clear_error (&pError);
+ }
+ }
+
+ g_string_free(pPath, TRUE);
+}
+
+static void unexport(IndicatorKeyboardService *self)
+{
+ // Unexport the menus
+ for (int nProfile = 0; nProfile < N_PROFILES; ++nProfile)
+ {
+ guint *nId = &self->pPrivate->lMenus[nProfile].nExportId;
+
+ if (*nId)
+ {
+ g_dbus_connection_unexport_menu_model(self->pPrivate->pConnection, *nId);
+ *nId = 0;
+ }
+ }
+
+ // Unexport the actions
+ if (self->pPrivate->nActionsId)
+ {
+ g_dbus_connection_unexport_action_group(self->pPrivate->pConnection, self->pPrivate->nActionsId);
+ self->pPrivate->nActionsId = 0;
+ }
+}
+
+static void onNameLost(GDBusConnection *pConnection, const gchar *sName, gpointer pData)
+{
+ IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData);
+
+ g_debug("%s %s name lost %s", G_STRLOC, G_STRFUNC, sName);
+
+ unexport(self);
+}
+
+static void onDispose(GObject *pObject)
+{
+ IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pObject);
+
+ if (self->pPrivate->pKeyboard != NULL)
+ {
+ g_object_unref(G_OBJECT(self->pPrivate->pKeyboard));
+ self->pPrivate->pKeyboard = NULL;
+ }
+
+ if (self->pPrivate->nOwnId)
+ {
+ g_bus_unown_name(self->pPrivate->nOwnId);
+ self->pPrivate->nOwnId = 0;
+ }
+
+ unexport(self);
+
+ if (self->pPrivate->pCancellable != NULL)
+ {
+ g_cancellable_cancel(self->pPrivate->pCancellable);
+ g_clear_object(&self->pPrivate->pCancellable);
+ }
+
+ g_clear_object (&self->pPrivate->pSettingsAction);
+ g_clear_object (&self->pPrivate->pLayoutAction);
+ g_clear_object (&self->pPrivate->pHeaderAction);
+ g_clear_object (&self->pPrivate->pActionGroup);
+ g_clear_object (&self->pPrivate->pConnection);
+
+ G_OBJECT_CLASS(indicator_keyboard_service_parent_class)->dispose(pObject);
+}
+
+static void indicator_keyboard_service_init(IndicatorKeyboardService *self)
+{
+ self->pPrivate = indicator_keyboard_service_get_instance_private(self);
+ self->pPrivate->pCancellable = g_cancellable_new();
+ self->pPrivate->pKeyboard = keyboard_new();
+ g_signal_connect(self->pPrivate->pKeyboard, KEYBOARD_LAYOUT_CHANGED, G_CALLBACK(onLayoutChanged), self);
+ g_signal_connect(self->pPrivate->pKeyboard, KEYBOARD_CONFIG_CHANGED, G_CALLBACK(onConfigChanged), self);
+ initActions(self);
+
+ for (int nProfile = 0; nProfile < N_PROFILES; ++nProfile)
+ {
+ createMenu(self, nProfile);
+ }
+
+ self->pPrivate->bMenusBuilt = TRUE;
+ self->pPrivate->nOwnId = g_bus_own_name(G_BUS_TYPE_SESSION, BUS_NAME, G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, onBusAcquired, NULL, onNameLost, self, NULL);
+}
+
+static void indicator_keyboard_service_class_init(IndicatorKeyboardServiceClass *klass)
+{
+ GObjectClass *pClass = G_OBJECT_CLASS(klass);
+ pClass->dispose = onDispose;
+ m_nSignal = g_signal_new("name-lost", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(IndicatorKeyboardServiceClass, pNameLost), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+}
+
+IndicatorKeyboardService *indicator_keyboard_service_new()
+{
+ GObject *pObject = g_object_new(INDICATOR_TYPE_KEYBOARD_SERVICE, NULL);
+
+ return INDICATOR_KEYBOARD_SERVICE(pObject);
+}
+
+void indicator_keyboard_service_AddKeyboardSource(IndicatorKeyboardService *self)
+{
+ keyboard_AddSource(self->pPrivate->pKeyboard);
+}
diff --git a/src/service.h b/src/service.h
new file mode 100644
index 00000000..9a758568
--- /dev/null
+++ b/src/service.h
@@ -0,0 +1,36 @@
+#ifndef __INDICATOR_KEYBOARD_SERVICE_H__
+#define __INDICATOR_KEYBOARD_SERVICE_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include "keyboard.h"
+
+G_BEGIN_DECLS
+
+#define INDICATOR_KEYBOARD_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST((o), INDICATOR_TYPE_KEYBOARD_SERVICE, IndicatorKeyboardService))
+#define INDICATOR_TYPE_KEYBOARD_SERVICE (indicator_keyboard_service_get_type())
+#define INDICATOR_IS_KEYBOARD_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), INDICATOR_TYPE_KEYBOARD_SERVICE))
+
+typedef struct _IndicatorKeyboardService IndicatorKeyboardService;
+typedef struct _IndicatorKeyboardServiceClass IndicatorKeyboardServiceClass;
+typedef struct _IndicatorKeyboardServicePrivate IndicatorKeyboardServicePrivate;
+
+struct _IndicatorKeyboardService
+{
+ GObject parent;
+ IndicatorKeyboardServicePrivate *pPrivate;
+};
+
+struct _IndicatorKeyboardServiceClass
+{
+ GObjectClass parent_class;
+ void (*pNameLost)(IndicatorKeyboardService *self);
+};
+
+GType indicator_keyboard_service_get_type(void);
+IndicatorKeyboardService* indicator_keyboard_service_new();
+void indicator_keyboard_service_AddKeyboardSource(IndicatorKeyboardService *pService);
+
+G_END_DECLS
+
+#endif /* __INDICATOR_KEYBOARD_SERVICE_H__ */
diff --git a/src/source.vala b/src/source.vala
deleted file mode 100644
index b7d7a971..00000000
--- a/src/source.vala
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 5ca398ec..00000000
--- a/src/unity-greeter.vala
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 15337c36..00000000
--- a/src/unity-session.vala
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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/utils.c b/src/utils.c
new file mode 100644
index 00000000..82f9cff0
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2021 Marius Gripsgard <marius@ubports.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+ */
+
+#include "utils.h"
+
+#include <string.h>
+
+#ifdef HAS_URLDISPATCHER
+# include <url-dispatcher.h>
+#endif
+
+// TODO: make case insensitive
+gboolean
+is_xdg_current_desktop (const gchar* desktop)
+{
+ const gchar *xdg_current_desktop;
+ gchar **desktop_names;
+ int i;
+
+ xdg_current_desktop = g_getenv ("XDG_CURRENT_DESKTOP");
+ if (xdg_current_desktop != NULL) {
+ desktop_names = g_strsplit (xdg_current_desktop, ":", 0);
+ for (i = 0; desktop_names[i]; ++i) {
+ if (!g_strcmp0 (desktop_names[i], desktop)) {
+ g_strfreev (desktop_names);
+ return TRUE;
+ }
+ }
+ g_strfreev (desktop_names);
+ }
+ return FALSE;
+}
+
+gboolean
+is_lomiri ()
+{
+ // For legacy reasons keep the MIR_SOCKET hack
+ return (g_getenv ("MIR_SOCKET") != NULL ||
+ is_xdg_current_desktop(DESKTOP_LOMIRI));
+}
+
+gboolean
+is_gnome ()
+{
+ return is_xdg_current_desktop(DESKTOP_GNOME);
+}
+
+gboolean
+is_unity ()
+{
+ return is_xdg_current_desktop(DESKTOP_UNITY);
+}
+
+gboolean
+is_mate ()
+{
+ return is_xdg_current_desktop(DESKTOP_MATE);
+}
+
+gboolean
+is_xfce ()
+{
+ return is_xdg_current_desktop(DESKTOP_XFCE);
+}
+
+gboolean
+is_pantheon ()
+{
+ return is_xdg_current_desktop(DESKTOP_PANTHEON);
+}
+
+// Bit of a hacky way? should use xdg open
+char *
+find_browser ()
+{
+ static char * browser_path = NULL;
+ char* tmp_browser_path;
+ gchar **browser_names;
+
+ int i;
+
+ if (browser_path == NULL)
+ {
+ browser_names = g_strsplit ("x-www-browser,google-chrome,firefox,chromium", ",", 0);
+
+ for (i = 0; browser_names[i]; ++i) {
+ tmp_browser_path = g_find_program_in_path (browser_names[i]);
+
+ if (tmp_browser_path) {
+ browser_path = g_strdup (tmp_browser_path);
+ g_free (tmp_browser_path);
+ g_strfreev (browser_names);
+ break;
+ }
+ }
+ }
+
+ return browser_path;
+}
+
+gboolean
+execute_command (const gchar * cmd)
+{
+ GError * err = NULL;
+
+ g_debug ("Issuing command '%s'", cmd);
+
+ if (!g_spawn_command_line_async (cmd, &err))
+ {
+ g_warning ("Unable to start %s: %s", cmd, err->message);
+ g_error_free (err);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+open_url (const gchar * url)
+{
+ char * browser = NULL;
+
+ if (is_lomiri())
+ {
+#ifdef HAS_URLDISPATCHER
+ url_dispatch_send("settings:///system/battery", NULL, NULL);
+ return TRUE;
+#else
+ g_warning("Built without url-dispatcher, is not able to open url");
+#endif
+ }
+
+ if (browser == NULL)
+ browser = find_browser();
+
+ if (browser != NULL)
+ return execute_command(g_strdup_printf("%s '%s'", browser, url));
+ else
+ return FALSE;
+
+}
+
+gboolean
+have_program (const gchar * program)
+{
+ gchar *path;
+ gboolean have;
+
+ path = g_find_program_in_path(program);
+ have = path != NULL;
+ g_free(path);
+
+ return have;
+}
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 00000000..52e491f2
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 Marius Gripsgard <marius@ubports.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+ */
+
+
+#pragma once
+
+#include <glib.h>
+
+#define DESKTOP_LOMIRI "Lomiri"
+#define DESKTOP_UNITY "Unity"
+#define DESKTOP_MATE "MATE"
+#define DESKTOP_GNOME "GNOME"
+#define DESKTOP_XFCE "XFCE"
+#define DESKTOP_PANTHEON "PANTHEON"
+
+gboolean is_lomiri();
+gboolean is_unity();
+gboolean is_gnome();
+gboolean is_mate();
+gboolean is_xfce();
+gboolean is_pantheon();
+
+gboolean execute_command(const gchar * cmd);
+gboolean open_url(const gchar * url);
+gboolean have_program(const gchar * program);
diff --git a/src/window-stack.vala b/src/window-stack.vala
deleted file mode 100644
index a943da6a..00000000
--- a/src/window-stack.vala
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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);
-}