aboutsummaryrefslogtreecommitdiff
path: root/src/service.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/service.c')
-rw-r--r--src/service.c1182
1 files changed, 1182 insertions, 0 deletions
diff --git a/src/service.c b/src/service.c
new file mode 100644
index 0000000..74acd89
--- /dev/null
+++ b/src/service.c
@@ -0,0 +1,1182 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.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 <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "backend.h"
+#include "service.h"
+
+#define BUS_NAME "com.canonical.indicator.session"
+#define BUS_PATH "/com/canonical/indicator/session"
+
+#define ICON_DEFAULT "system-devices-panel"
+#define ICON_INFO "system-devices-panel-information"
+#define ICON_ALERT "system-devices-panel-alert"
+
+G_DEFINE_TYPE (IndicatorSessionService,
+ indicator_session_service,
+ G_TYPE_OBJECT)
+
+/* signals enum */
+enum
+{
+ NAME_LOST,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum
+{
+ PROP_0,
+ PROP_MAX_USERS,
+ PROP_LAST
+};
+
+static GParamSpec * properties[PROP_LAST];
+
+enum
+{
+ SECTION_HEADER = (1<<0),
+ SECTION_ADMIN = (1<<1),
+ SECTION_SETTINGS = (1<<2),
+ SECTION_SWITCH = (1<<3),
+ SECTION_SESSION = (1<<4)
+};
+
+enum
+{
+ PROFILE_DESKTOP,
+ PROFILE_GREETER,
+ N_PROFILES
+};
+
+static const char * const menu_names[N_PROFILES] =
+{
+ "desktop",
+ "desktop_greeter"
+};
+
+struct ProfileMenuInfo
+{
+ /* the root level -- the header is the only child of this */
+ GMenu * menu;
+
+ /* parent of the sections. This is the header's submenu */
+ GMenu * submenu;
+
+ guint export_id;
+};
+
+struct _IndicatorSessionServicePrivate
+{
+ guint own_id;
+ guint max_users;
+ IndicatorSessionUsers * backend_users;
+ IndicatorSessionGuest * backend_guest;
+ IndicatorSessionActions * backend_actions;
+ GSettings * indicator_settings;
+ GSettings * keybinding_settings;
+ GSimpleActionGroup * actions;
+ guint actions_export_id;
+ struct ProfileMenuInfo menus[N_PROFILES];
+ GSimpleAction * header_action;
+ GSimpleAction * user_switcher_action;
+ GSimpleAction * guest_switcher_action;
+ GHashTable * users;
+ guint rebuild_id;
+ int rebuild_flags;
+ GDBusConnection * conn;
+ GCancellable * cancellable;
+};
+
+typedef IndicatorSessionServicePrivate priv_t;
+
+static const char * get_current_real_name (IndicatorSessionService * self);
+
+/***
+****
+***/
+
+static void rebuild_now (IndicatorSessionService * self, int section);
+static void rebuild_soon (IndicatorSessionService * self, int section);
+
+static inline void
+rebuild_header_soon (IndicatorSessionService * self)
+{
+ rebuild_soon (self, SECTION_HEADER);
+}
+static inline void
+rebuild_switch_section_soon (IndicatorSessionService * self)
+{
+ rebuild_soon (self, SECTION_SWITCH);
+}
+static inline void
+rebuild_session_section_soon (IndicatorSessionService * self)
+{
+ rebuild_soon (self, SECTION_SESSION);
+}
+static inline void
+rebuild_settings_section_soon (IndicatorSessionService * self)
+{
+ rebuild_soon (self, SECTION_SETTINGS);
+}
+
+/***
+****
+***/
+
+static void
+update_header_action (IndicatorSessionService * self)
+{
+ gchar * a11y;
+ gboolean need_attn;
+ gboolean show_name;
+ GVariant * variant;
+ const gchar * real_name;
+ const gchar * label;
+ const gchar * iconstr;
+ const priv_t * const p = self->priv;
+
+ g_return_if_fail (p->header_action != NULL);
+
+ if (indicator_session_actions_has_online_account_error (p->backend_actions))
+ {
+ need_attn = TRUE;
+ iconstr = ICON_ALERT;
+ }
+ else
+ {
+ need_attn = FALSE;
+ iconstr = ICON_DEFAULT;
+ }
+
+ show_name = g_settings_get_boolean (p->indicator_settings,
+ "show-real-name-on-panel");
+
+ real_name = get_current_real_name (self);
+ label = show_name && real_name ? real_name : "";
+
+ if (*label && need_attn)
+ {
+ /* Translators: the name of the menu ("System"), then the user's name,
+ then a hint that something in this menu requires user attention */
+ a11y = g_strdup_printf (_("System, %s (Attention Required)"), real_name);
+ }
+ else if (*label)
+ {
+ /* Translators: the name of the menu ("System"), then the user's name */
+ a11y = g_strdup_printf (_("System, %s"), label);
+ }
+ else if (need_attn)
+ {
+ a11y = g_strdup (_("System (Attention Required)"));
+ }
+ else
+ {
+ a11y = g_strdup (_("System"));
+ }
+
+ variant = g_variant_new ("(sssb)", label, iconstr, a11y, TRUE);
+ g_simple_action_set_state (p->header_action, variant);
+ g_free (a11y);
+}
+
+/***
+**** USERS
+***/
+
+static GMenuModel * create_switch_section (IndicatorSessionService * self);
+
+static void
+add_user (IndicatorSessionService * self, guint uid)
+{
+ IndicatorSessionUser * u;
+
+ if ((u = indicator_session_users_get_user (self->priv->backend_users, uid)))
+ {
+ /* update our user table */
+ g_hash_table_insert (self->priv->users, GUINT_TO_POINTER(uid), u);
+
+ /* queue rebuilds for the affected sections */
+ rebuild_switch_section_soon (self);
+ if (u->is_current_user)
+ rebuild_header_soon (self);
+ }
+}
+
+static void
+on_user_added (IndicatorSessionUsers * backend_users G_GNUC_UNUSED,
+ guint uid,
+ gpointer gself)
+{
+ add_user (INDICATOR_SESSION_SERVICE(gself), uid);
+}
+
+static void
+on_user_changed (IndicatorSessionUsers * backend_users G_GNUC_UNUSED,
+ guint uid,
+ gpointer gself)
+{
+ add_user (INDICATOR_SESSION_SERVICE(gself), uid);
+}
+
+static void
+on_user_removed (IndicatorSessionUsers * backend_users G_GNUC_UNUSED,
+ guint uid,
+ gpointer gself)
+{
+ IndicatorSessionService * self = INDICATOR_SESSION_SERVICE (gself);
+ g_return_if_fail (self != NULL);
+
+ /* update our user table */
+ g_hash_table_remove (self->priv->users, GUINT_TO_POINTER(uid));
+
+ /* enqueue rebuilds for the affected sections */
+ rebuild_switch_section_soon (self);
+}
+
+static const char *
+get_current_real_name (IndicatorSessionService * self)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ /* is it the guest? */
+ if (indicator_session_guest_is_active (self->priv->backend_guest))
+ return _("Guest");
+
+ /* is it a user? */
+ g_hash_table_iter_init (&iter, self->priv->users);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ IndicatorSessionUser * user = value;
+ if (user->is_current_user)
+ return user->real_name;
+ }
+
+ return "";
+}
+
+/***
+****
+***/
+
+static GMenuModel *
+create_admin_section (void)
+{
+ GMenu * menu;
+
+ menu = g_menu_new ();
+ g_menu_append (menu, _("About This Computer"), "indicator.about");
+ g_menu_append (menu, _("Ubuntu Help"), "indicator.help");
+ return G_MENU_MODEL (menu);
+}
+
+static GMenuModel *
+create_settings_section (IndicatorSessionService * self)
+{
+ GMenu * menu;
+ priv_t * p = self->priv;
+
+ menu = g_menu_new ();
+ g_menu_append (menu, _("System Settings…"), "indicator.settings");
+ if (indicator_session_actions_has_online_account_error (p->backend_actions))
+ g_menu_append (menu, _("Online Accounts…"), "indicator.online-accounts");
+
+ return G_MENU_MODEL (menu);
+}
+
+/**
+ * The switch-to-guest action's state is a dictionary with these entries:
+ * - "is-active" (boolean)
+ * - "is-logged-in" (boolean)
+ */
+static GVariant *
+create_guest_switcher_state (IndicatorSessionService * self)
+{
+ GVariant * val;
+ GVariantBuilder b;
+ IndicatorSessionGuest * const g = self->priv->backend_guest;
+
+ g_variant_builder_init (&b, G_VARIANT_TYPE ("a{sv}"));
+ val = g_variant_new_boolean (indicator_session_guest_is_active (g));
+ g_variant_builder_add (&b, "{sv}", "is-active", val);
+ val = g_variant_new_boolean (indicator_session_guest_is_logged_in (g));
+ g_variant_builder_add (&b, "{sv}", "is-logged-in", val);
+ return g_variant_builder_end (&b);
+}
+
+/**
+ * The switch-to-user action's state is a dictionary with these entries:
+ * - "active-user" (username string)
+ * - "logged-in-users" (array of username strings)
+ */
+static GVariant *
+create_user_switcher_state (IndicatorSessionService * self)
+{
+ GVariantBuilder a;
+ GVariantBuilder b;
+ GVariant * val;
+ GHashTableIter ht_iter;
+ gpointer ht_value;
+ const char * current_user;
+
+ current_user = "";
+ g_variant_builder_init (&a, G_VARIANT_TYPE("as"));
+ g_hash_table_iter_init (&ht_iter, self->priv->users);
+ while (g_hash_table_iter_next (&ht_iter, NULL, &ht_value))
+ {
+ const IndicatorSessionUser * u = ht_value;
+
+ if (u->is_current_user)
+ current_user = u->user_name;
+
+ if (u->is_logged_in)
+ g_variant_builder_add (&a, "s", u->user_name);
+ }
+
+ g_variant_builder_init (&b, G_VARIANT_TYPE("a{sv}"));
+ val = g_variant_new_string (current_user);
+ g_variant_builder_add (&b, "{sv}", "active-user", val);
+ val = g_variant_builder_end (&a);
+ g_variant_builder_add (&b, "{sv}", "logged-in-users", val);
+ return g_variant_builder_end (&b);
+}
+
+static void
+update_switch_actions (IndicatorSessionService * self)
+{
+ g_simple_action_set_state (self->priv->guest_switcher_action,
+ create_guest_switcher_state (self));
+
+ g_simple_action_set_state (self->priv->user_switcher_action,
+ create_user_switcher_state (self));
+}
+
+static gboolean
+use_ellipsis (IndicatorSessionService * self)
+{
+ /* does the backend support confirmation prompts? */
+ if (!indicator_session_actions_can_prompt (self->priv->backend_actions))
+ return FALSE;
+
+ /* has the user disabled prompts? */
+ if (g_settings_get_boolean (self->priv->indicator_settings,
+ "suppress-logout-restart-shutdown"))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* lower index == more useful.
+ When there are too many users for the menu,
+ we use this to decide which to cull. */
+static int
+compare_users_by_usefulness (gconstpointer ga, gconstpointer gb)
+{
+ const IndicatorSessionUser * a = *(const IndicatorSessionUser**)ga;
+ const IndicatorSessionUser * b = *(const IndicatorSessionUser**)gb;
+
+ if (a->is_current_user != b->is_current_user)
+ return a->is_current_user ? -1 : 1;
+
+ if (a->is_logged_in != b->is_logged_in)
+ return a->is_logged_in ? -1 : 1;
+
+ if (a->login_frequency != b->login_frequency)
+ return a->login_frequency > b->login_frequency ? -1 : 1;
+
+ return 0;
+}
+
+/* sorting them for display in the menu */
+static int
+compare_users_by_label (gconstpointer ga, gconstpointer gb)
+{
+ int i;
+ const IndicatorSessionUser * a = *(const IndicatorSessionUser**)ga;
+ const IndicatorSessionUser * b = *(const IndicatorSessionUser**)gb;
+
+ if ((i = g_strcmp0 (a->real_name, b->real_name)))
+ return i;
+
+ return g_strcmp0 (a->user_name, b->user_name);
+}
+
+static GMenuModel *
+create_switch_section (IndicatorSessionService * self)
+{
+ gchar * str;
+ GMenu * menu;
+ GMenuItem * item;
+ guint i;
+ gpointer guser;
+ GHashTableIter iter;
+ GPtrArray * users;
+ const priv_t * const p = self->priv;
+ const gboolean ellipsis = use_ellipsis (self);
+
+ menu = g_menu_new ();
+
+ /* lockswitch */
+ if (indicator_session_users_is_live_session (p->backend_users))
+ {
+ const char * action = "indicator.switch-to-screensaver";
+ item = g_menu_item_new (_("Start Screen Saver"), action);
+ }
+ else if (indicator_session_guest_is_active (p->backend_guest))
+ {
+ const char * action = "indicator.switch-to-greeter";
+ item = g_menu_item_new (ellipsis ? _("Switch Account…")
+ : _("Switch Account"), action);
+ }
+ else if (g_hash_table_size (p->users) == 1)
+ {
+ const char * action = "indicator.switch-to-greeter";
+ item = g_menu_item_new (_("Lock"), action);
+ }
+ else
+ {
+ const char * action = "indicator.switch-to-greeter";
+ item = g_menu_item_new (ellipsis ? _("Lock/Switch Account…")
+ : _("Lock/Switch Account"), action);
+ }
+ str = g_settings_get_string (p->keybinding_settings, "screensaver");
+ g_menu_item_set_attribute (item, "accel", "s", str);
+ g_free (str);
+ g_menu_append_item (menu, item);
+ g_object_unref (item);
+
+ if (indicator_session_guest_is_allowed (p->backend_guest))
+ {
+ GMenuItem *item;
+
+ item = g_menu_item_new (_("Guest Session"), "indicator.switch-to-guest");
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "indicator.guest-menu-item");
+ g_menu_append_item (menu, item);
+
+ g_object_unref (item);
+ }
+
+ /* build an array of all the users we know of */
+ users = g_ptr_array_new ();
+ g_hash_table_iter_init (&iter, p->users);
+ while (g_hash_table_iter_next (&iter, NULL, &guser))
+ g_ptr_array_add (users, guser);
+
+ /* if there are too many users, cull out the less interesting ones */
+ if (users->len > p->max_users)
+ {
+ g_ptr_array_sort (users, compare_users_by_usefulness);
+ g_ptr_array_set_size (users, p->max_users);
+ }
+
+ /* sort the users by name */
+ g_ptr_array_sort (users, compare_users_by_label);
+
+ /* add the users */
+ for (i=0; i<users->len; ++i)
+ {
+ const IndicatorSessionUser * u = g_ptr_array_index (users, i);
+ item = g_menu_item_new (u->real_name, NULL);
+ g_menu_item_set_action_and_target (item, "indicator.switch-to-user", "s", u->user_name);
+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "indicator.user-menu-item");
+
+ if (u->icon_file != NULL)
+ {
+ GFile * file = g_file_new_for_path (u->icon_file);
+ GIcon * icon = g_file_icon_new (file);
+ g_menu_item_set_attribute_value (item, G_MENU_ATTRIBUTE_ICON, g_icon_serialize (icon));
+ g_clear_object (&icon);
+ g_clear_object (&file);
+ }
+
+ g_menu_append_item (menu, item);
+ g_object_unref (item);
+ }
+
+ /* cleanup */
+ g_ptr_array_free (users, TRUE);
+ return G_MENU_MODEL (menu);
+}
+
+static GMenuModel *
+create_session_section (IndicatorSessionService * self, int profile)
+{
+ GMenu * menu;
+ const priv_t * const p = self->priv;
+ GSettings * const s = p->indicator_settings;
+ const gboolean ellipsis = use_ellipsis (self);
+
+ menu = g_menu_new ();
+
+ if ((profile == PROFILE_DESKTOP) &&
+ (indicator_session_actions_can_logout (p->backend_actions)))
+ {
+ const char * label = ellipsis ? _("Log Out…") : _("Log Out");
+ g_menu_append (menu, label, "indicator.logout");
+ }
+
+ if (indicator_session_actions_can_suspend (p->backend_actions))
+ g_menu_append (menu, _("Suspend"), "indicator.suspend");
+
+ if (indicator_session_actions_can_hibernate (p->backend_actions))
+ g_menu_append (menu, _("Hibernate"), "indicator.hibernate");
+
+ if (indicator_session_actions_can_reboot (p->backend_actions))
+ {
+ const char * label = ellipsis ? _("Restart…") : _("Restart");
+ g_menu_append (menu, label, "indicator.reboot");
+ }
+
+ if (!g_settings_get_boolean (s, "suppress-shutdown-menuitem"))
+ {
+ const char * label = ellipsis ? _("Shut Down…") : _("Shut Down");
+ g_menu_append (menu, label, "indicator.power-off");
+ }
+
+ return G_MENU_MODEL (menu);
+}
+
+static void
+create_menu (IndicatorSessionService * self, int profile)
+{
+ GMenu * menu;
+ GMenu * submenu;
+ GMenuItem * header;
+ GMenuModel * sections[16];
+ int i;
+ int n = 0;
+
+ g_assert (0<=profile && profile<N_PROFILES);
+ g_assert (self->priv->menus[profile].menu == NULL);
+
+ if (profile == PROFILE_DESKTOP)
+ {
+ sections[n++] = create_admin_section ();
+ sections[n++] = create_settings_section (self);
+ sections[n++] = create_switch_section (self);
+ sections[n++] = create_session_section (self, profile);
+ }
+ else if (profile == PROFILE_GREETER)
+ {
+ sections[n++] = create_session_section (self, profile);
+ }
+
+ /* add sections to the submenu */
+ submenu = g_menu_new ();
+ for (i=0; i<n; ++i)
+ {
+ g_menu_append_section (submenu, NULL, sections[i]);
+ g_object_unref (sections[i]);
+ }
+
+ /* add submenu to the header */
+ header = g_menu_item_new (NULL, "indicator._header");
+ g_menu_item_set_attribute (header, "x-canonical-type", "s", "com.canonical.indicator.root");
+ g_menu_item_set_submenu (header, G_MENU_MODEL (submenu));
+ g_object_unref (submenu);
+
+ /* add header to the menu */
+ menu = g_menu_new ();
+ g_menu_append_item (menu, header);
+ g_object_unref (header);
+
+ self->priv->menus[profile].menu = menu;
+ self->priv->menus[profile].submenu = submenu;
+}
+
+/***
+**** GActions
+***/
+
+static IndicatorSessionActions *
+get_backend_actions (gpointer gself)
+{
+ return INDICATOR_SESSION_SERVICE(gself)->priv->backend_actions;
+}
+
+static void
+on_about_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_about (get_backend_actions(gself));
+}
+
+static void
+on_online_accounts_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_online_accounts (get_backend_actions(gself));
+}
+
+static void
+on_help_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_help (get_backend_actions(gself));
+}
+
+static void
+on_settings_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_settings (get_backend_actions(gself));
+}
+
+static void
+on_logout_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_logout (get_backend_actions(gself));
+}
+
+static void
+on_suspend_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_suspend (get_backend_actions(gself));
+}
+
+static void
+on_hibernate_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_hibernate (get_backend_actions(gself));
+}
+
+static void
+on_reboot_activated (GSimpleAction * action G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_reboot (get_backend_actions(gself));
+}
+
+static void
+on_power_off_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_power_off (get_backend_actions(gself));
+}
+
+static void
+on_guest_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_switch_to_guest (get_backend_actions(gself));
+}
+
+static void
+on_screensaver_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_switch_to_screensaver (get_backend_actions(gself));
+}
+
+static void
+on_greeter_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param G_GNUC_UNUSED,
+ gpointer gself)
+{
+ indicator_session_actions_switch_to_greeter (get_backend_actions(gself));
+}
+
+static void
+on_user_activated (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param,
+ gpointer gself)
+{
+ const char * username = g_variant_get_string (param, NULL);
+ indicator_session_actions_switch_to_username (get_backend_actions(gself),
+ username);
+}
+
+static void
+init_gactions (IndicatorSessionService * self)
+{
+ GVariant * v;
+ GSimpleAction * a;
+ priv_t * p = self->priv;
+
+ GActionEntry entries[] = {
+ { "about", on_about_activated },
+ { "help", on_help_activated },
+ { "hibernate", on_hibernate_activated },
+ { "logout", on_logout_activated },
+ { "online-accounts", on_online_accounts_activated },
+ { "reboot", on_reboot_activated },
+ { "settings", on_settings_activated },
+ { "switch-to-screensaver", on_screensaver_activated },
+ { "switch-to-greeter", on_greeter_activated },
+ { "suspend", on_suspend_activated },
+ { "power-off", on_power_off_activated }
+ };
+
+ p->actions = g_simple_action_group_new ();
+
+ g_action_map_add_action_entries (G_ACTION_MAP(p->actions),
+ entries,
+ G_N_ELEMENTS(entries),
+ self);
+
+ /* add switch-to-guest action */
+ v = create_guest_switcher_state (self);
+ a = g_simple_action_new_stateful ("switch-to-guest", NULL, v);
+ g_signal_connect (a, "activate", G_CALLBACK(on_guest_activated), self);
+ g_simple_action_group_insert (p->actions, G_ACTION(a));
+ p->guest_switcher_action = a;
+
+ /* add switch-to-user action... parameter is the uesrname */
+ v = create_user_switcher_state (self);
+ a = g_simple_action_new_stateful ("switch-to-user", G_VARIANT_TYPE_STRING, v);
+ g_signal_connect (a, "activate", G_CALLBACK(on_user_activated), self);
+ g_simple_action_group_insert (p->actions, G_ACTION(a));
+ p->user_switcher_action = a;
+
+ /* add the header action */
+ v = g_variant_new ("(sssb)", "label", ICON_DEFAULT, "a11y", TRUE);
+ a = g_simple_action_new_stateful ("_header", NULL, v);
+ g_simple_action_group_insert (p->actions, G_ACTION(a));
+ p->header_action = a;
+
+ rebuild_now (self, SECTION_HEADER);
+}
+
+/***
+****
+***/
+
+/**
+ * A small helper function for rebuild_now().
+ * - removes the previous section
+ * - adds and unrefs the new section
+ */
+static void
+rebuild_section (GMenu * parent, int pos, GMenuModel * new_section)
+{
+ g_menu_remove (parent, pos);
+ g_menu_insert_section (parent, pos, NULL, new_section);
+ g_object_unref (new_section);
+}
+
+static void
+rebuild_now (IndicatorSessionService * self, int sections)
+{
+ priv_t * p = self->priv;
+ struct ProfileMenuInfo * desktop = &p->menus[PROFILE_DESKTOP];
+ struct ProfileMenuInfo * greeter = &p->menus[PROFILE_GREETER];
+
+ if (sections & SECTION_HEADER)
+ {
+ update_header_action (self);
+ }
+
+ if (sections & SECTION_ADMIN)
+ {
+ rebuild_section (desktop->submenu, 0, create_admin_section());
+ }
+
+ if (sections & SECTION_SETTINGS)
+ {
+ rebuild_section (desktop->submenu, 1, create_settings_section(self));
+ }
+
+ if (sections & SECTION_SWITCH)
+ {
+ rebuild_section (desktop->submenu, 2, create_switch_section(self));
+ update_switch_actions (self);
+ }
+
+ if (sections & SECTION_SESSION)
+ {
+ rebuild_section (desktop->submenu, 3, create_session_section(self, PROFILE_DESKTOP));
+ rebuild_section (greeter->submenu, 0, create_session_section(self, PROFILE_GREETER));
+ }
+}
+
+static int
+rebuild_timeout_func (IndicatorSessionService * self)
+{
+ priv_t * p = self->priv;
+ rebuild_now (self, p->rebuild_flags);
+ p->rebuild_flags = 0;
+ p->rebuild_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+rebuild_soon (IndicatorSessionService * self, int section)
+{
+ priv_t * p = self->priv;
+
+ p->rebuild_flags |= section;
+
+ if (p->rebuild_id == 0)
+ {
+ /* Change events seem to come over the bus in small bursts. This msec
+ value is an arbitrary number that tries to be large enough to fold
+ multiple events into a single rebuild, but small enough that the
+ user won't notice any lag. */
+ static const int REBUILD_INTERVAL_MSEC = 500;
+
+ p->rebuild_id = g_timeout_add (REBUILD_INTERVAL_MSEC,
+ (GSourceFunc)rebuild_timeout_func,
+ self);
+ }
+}
+
+/***
+**** GDBus
+***/
+
+static void
+on_bus_acquired (GDBusConnection * connection,
+ const gchar * name,
+ gpointer gself)
+{
+ int i;
+ guint id;
+ GError * err = NULL;
+ IndicatorSessionService * self = INDICATOR_SESSION_SERVICE(gself);
+ priv_t * p = self->priv;
+
+ g_debug ("bus acquired: %s", name);
+
+ p->conn = g_object_ref (G_OBJECT (connection));
+
+ /* export the actions */
+ if ((id = g_dbus_connection_export_action_group (connection,
+ BUS_PATH,
+ G_ACTION_GROUP (p->actions),
+ &err)))
+ {
+ p->actions_export_id = id;
+ }
+ else
+ {
+ g_warning ("cannot export action group: %s", err->message);
+ g_clear_error (&err);
+ }
+
+ /* export the menus */
+ for (i=0; i<N_PROFILES; ++i)
+ {
+ char * path = g_strdup_printf ("%s/%s", BUS_PATH, menu_names[i]);
+ struct ProfileMenuInfo * menu = &p->menus[i];
+
+ if (menu->menu == NULL)
+ create_menu (self, i);
+
+ if ((id = g_dbus_connection_export_menu_model (connection,
+ path,
+ G_MENU_MODEL (menu->menu),
+ &err)))
+ {
+ menu->export_id = id;
+ }
+ else
+ {
+ g_warning ("cannot export %s menu: %s", menu_names[i], err->message);
+ g_clear_error (&err);
+ }
+
+ g_free (path);
+ }
+}
+
+static void
+unexport (IndicatorSessionService * self)
+{
+ int i;
+ priv_t * p = self->priv;
+
+ /* unexport the menus */
+ for (i=0; i<N_PROFILES; ++i)
+ {
+ guint * id = &self->priv->menus[i].export_id;
+
+ if (*id)
+ {
+ g_dbus_connection_unexport_menu_model (p->conn, *id);
+ *id = 0;
+ }
+ }
+
+ /* unexport the actions */
+ if (p->actions_export_id)
+ {
+ g_dbus_connection_unexport_action_group (p->conn, p->actions_export_id);
+ p->actions_export_id = 0;
+ }
+}
+
+static void
+on_name_lost (GDBusConnection * connection G_GNUC_UNUSED,
+ const gchar * name,
+ gpointer gself)
+{
+ IndicatorSessionService * self = INDICATOR_SESSION_SERVICE (gself);
+
+ g_debug ("%s %s name lost %s", G_STRLOC, G_STRFUNC, name);
+
+ unexport (self);
+
+ g_signal_emit (self, signals[NAME_LOST], 0, NULL);
+}
+
+/***
+****
+***/
+
+static void
+/* cppcheck-suppress unusedFunction */
+indicator_session_service_init (IndicatorSessionService * self)
+{
+ GList * l;
+ GList * uids;
+ priv_t * p;
+ gpointer gp;
+
+ /* init our priv pointer */
+ p = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ INDICATOR_TYPE_SESSION_SERVICE,
+ IndicatorSessionServicePrivate);
+ p->indicator_settings = g_settings_new ("com.canonical.indicator.session");
+ p->keybinding_settings = g_settings_new ("org.gnome.settings-daemon.plugins.media-keys");
+ self->priv = p;
+
+ /* init the backend objects */
+ p->cancellable = g_cancellable_new ();
+ backend_get (p->cancellable, &p->backend_actions,
+ &p->backend_users,
+ &p->backend_guest);
+
+ /* init our key-to-User table */
+ p->users = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify)indicator_session_user_free);
+ uids = indicator_session_users_get_uids (p->backend_users);
+ for (l=uids; l!=NULL; l=l->next)
+ add_user (self, GPOINTER_TO_UINT(l->data));
+ g_list_free (uids);
+
+ init_gactions (self);
+
+ /* watch for changes in backend_users */
+ gp = p->backend_users;
+ g_signal_connect (gp, INDICATOR_SESSION_USERS_SIGNAL_USER_ADDED,
+ G_CALLBACK(on_user_added), self);
+ g_signal_connect (gp, INDICATOR_SESSION_USERS_SIGNAL_USER_CHANGED,
+ G_CALLBACK(on_user_changed), self);
+ g_signal_connect (gp, INDICATOR_SESSION_USERS_SIGNAL_USER_REMOVED,
+ G_CALLBACK(on_user_removed), self);
+ g_signal_connect_swapped (gp, "notify::is-live-session",
+ G_CALLBACK(rebuild_switch_section_soon), self);
+
+ /* watch for changes in backend_guest */
+ gp = p->backend_guest;
+ g_signal_connect_swapped (gp, "notify::guest-is-active-session",
+ G_CALLBACK(rebuild_header_soon), self);
+ g_signal_connect_swapped (gp, "notify",
+ G_CALLBACK(rebuild_switch_section_soon), self);
+
+ /* watch for updates in backend_actions */
+ gp = p->backend_actions;
+ g_signal_connect_swapped (gp, "notify",
+ G_CALLBACK(rebuild_switch_section_soon), self);
+ g_signal_connect_swapped (gp, "notify",
+ G_CALLBACK(rebuild_session_section_soon), self);
+ g_signal_connect_swapped (gp, "notify::has-online-account-error",
+ G_CALLBACK(rebuild_header_soon), self);
+ g_signal_connect_swapped (gp, "notify::has-online-account-error",
+ G_CALLBACK(rebuild_settings_section_soon), self);
+
+ /* watch for changes in the indicator's settings */
+ gp = p->indicator_settings;
+ g_signal_connect_swapped (gp, "changed::suppress-logout-restart-shutdown",
+ G_CALLBACK(rebuild_switch_section_soon), self);
+ g_signal_connect_swapped (gp, "changed::suppress-logout-restart-shutdown",
+ G_CALLBACK(rebuild_session_section_soon), self);
+ g_signal_connect_swapped (gp, "changed::suppress-shutdown-menuitem",
+ G_CALLBACK(rebuild_session_section_soon), self);
+ g_signal_connect_swapped (gp, "changed::show-real-name-on-panel",
+ G_CALLBACK(rebuild_header_soon), self);
+
+ /* watch for changes to the lock keybinding */
+ gp = p->keybinding_settings;
+ g_signal_connect_swapped (gp, "changed::screensaver",
+ G_CALLBACK(rebuild_switch_section_soon), self);
+
+ self->priv->own_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ BUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
+ on_bus_acquired,
+ NULL,
+ on_name_lost,
+ self,
+ NULL);
+}
+
+/***
+**** GObject plumbing: properties
+***/
+
+static void
+my_get_property (GObject * o,
+ guint property_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ IndicatorSessionService * self = INDICATOR_SESSION_SERVICE (o);
+
+ switch (property_id)
+ {
+ case PROP_MAX_USERS:
+ g_value_set_uint (value, self->priv->max_users);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ }
+}
+
+static void
+my_set_property (GObject * o,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ IndicatorSessionService * self = INDICATOR_SESSION_SERVICE (o);
+
+ switch (property_id)
+ {
+ case PROP_MAX_USERS:
+ self->priv->max_users = g_value_get_uint (value);
+ rebuild_switch_section_soon (self);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ }
+}
+
+/***
+**** GObject plumbing: life cycle
+***/
+
+static void
+my_dispose (GObject * o)
+{
+ int i;
+ IndicatorSessionService * self = INDICATOR_SESSION_SERVICE(o);
+ priv_t * p = self->priv;
+
+ if (p->own_id)
+ {
+ g_bus_unown_name (p->own_id);
+ p->own_id = 0;
+ }
+
+ unexport (self);
+
+ if (p->cancellable != NULL)
+ {
+ g_cancellable_cancel (p->cancellable);
+ g_clear_object (&p->cancellable);
+ }
+
+ if (p->rebuild_id)
+ {
+ g_source_remove (p->rebuild_id);
+ p->rebuild_id = 0;
+ }
+
+ g_clear_pointer (&p->users, g_hash_table_destroy);
+ g_clear_object (&p->backend_users);
+ g_clear_object (&p->backend_guest);
+ g_clear_object (&p->backend_actions);
+ g_clear_object (&p->indicator_settings);
+ g_clear_object (&p->keybinding_settings);
+ g_clear_object (&p->actions);
+
+ for (i=0; i<N_PROFILES; ++i)
+ g_clear_object (&p->menus[i].menu);
+
+ g_clear_object (&p->header_action);
+ g_clear_object (&p->user_switcher_action);
+ g_clear_object (&p->guest_switcher_action);
+ g_clear_object (&p->conn);
+
+ G_OBJECT_CLASS (indicator_session_service_parent_class)->dispose (o);
+}
+
+static void
+/* cppcheck-suppress unusedFunction */
+indicator_session_service_class_init (IndicatorSessionServiceClass * klass)
+{
+ GObjectClass * object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = my_dispose;
+ object_class->get_property = my_get_property;
+ object_class->set_property = my_set_property;
+
+ g_type_class_add_private (klass, sizeof (IndicatorSessionServicePrivate));
+
+ signals[NAME_LOST] = g_signal_new (INDICATOR_SESSION_SERVICE_SIGNAL_NAME_LOST,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorSessionServiceClass, name_lost),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ properties[PROP_0] = NULL;
+
+ properties[PROP_MAX_USERS] = g_param_spec_uint ("max-users",
+ "Max Users",
+ "Max visible users",
+ 0, INT_MAX, 12,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, PROP_LAST, properties);
+}
+
+IndicatorSessionService *
+indicator_session_service_new (void)
+{
+ GObject * o = g_object_new (INDICATOR_TYPE_SESSION_SERVICE, NULL);
+
+ return INDICATOR_SESSION_SERVICE (o);
+}