aboutsummaryrefslogtreecommitdiff
path: root/src/session-menu-mgr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/session-menu-mgr.c')
-rw-r--r--src/session-menu-mgr.c1172
1 files changed, 1172 insertions, 0 deletions
diff --git a/src/session-menu-mgr.c b/src/session-menu-mgr.c
new file mode 100644
index 0000000..96fc2a0
--- /dev/null
+++ b/src/session-menu-mgr.c
@@ -0,0 +1,1172 @@
+/*
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Charles Kerr <charles.kerr@canonical.com>
+ Conor Curran <conor.curran@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 "config.h"
+
+#include <sys/types.h>
+#include <pwd.h> /* geteuid(), getpwuid() */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libdbusmenu-glib/client.h>
+#include <libdbusmenu-gtk/menuitem.h>
+
+#include "dbus-upower.h"
+#include "session-menu-mgr.h"
+#include "shared-names.h"
+#include "users-service-dbus.h"
+
+#define DEBUG_SHOW_ALL FALSE
+
+#define UPOWER_ADDRESS "org.freedesktop.UPower"
+#define UPOWER_PATH "/org/freedesktop/UPower"
+
+#define CMD_HELP "yelp"
+#define CMD_INFO "gnome-control-center info"
+#define CMD_SYSTEM_SETTINGS "gnome-control-center"
+#ifdef HAVE_GTKLOGOUTHELPER
+ #define HAVE_RESTART_CMD TRUE
+ #define CMD_RESTART LIBEXECDIR"/gtk-logout-helper --restart"
+ #define CMD_LOGOUT LIBEXECDIR"/gtk-logout-helper --logout"
+ #define CMD_SHUTDOWN LIBEXECDIR"/gtk-logout-helper --shutdown"
+#else
+ #define HAVE_RESTART_CMD FALSE /* hmm, no gnome-session-quit --restart? */
+ #define CMD_RESTART ""
+ #define CMD_LOGOUT "gnome-session-quit --logout"
+ #define CMD_SHUTDOWN "gnome-session-quit --power-off"
+#endif
+
+/**
+ * Which switch menuitem to show -- based on lockdown settings,
+ * greeter mode, number of users in the system, and so on.
+ * See get_switcher_mode()
+ */
+typedef enum
+{
+ SWITCHER_MODE_SCREENSAVER,
+ SWITCHER_MODE_LOCK,
+ SWITCHER_MODE_SWITCH,
+ SWITCHER_MODE_SWITCH_OR_LOCK
+}
+SwitcherMode;
+
+/**
+ * Creates and manages the menumodel and associated actions for the
+ * session menu described at <https://wiki.ubuntu.com/SystemMenu>.
+ *
+ * This is a pretty straightforward class: it creates the menumodel
+ * and listens for events that can affect the model's properties.
+ *
+ * Simple event sources, such as GSettings and a UPower DBus proxy,
+ * are handled here. More involved event sources are delegated to the
+ * UsersServiceDBus facade class.
+ */
+struct _SessionMenuMgr
+{
+ GObject parent_instance;
+
+ DbusmenuMenuitem * top_mi;
+ DbusmenuMenuitem * screensaver_mi;
+ DbusmenuMenuitem * lock_mi;
+ DbusmenuMenuitem * lock_switch_mi;
+ DbusmenuMenuitem * guest_mi;
+ DbusmenuMenuitem * logout_mi;
+ DbusmenuMenuitem * suspend_mi;
+ DbusmenuMenuitem * hibernate_mi;
+ DbusmenuMenuitem * restart_mi;
+ DbusmenuMenuitem * shutdown_mi;
+
+ GSList * user_menuitems;
+ gint user_menuitem_index;
+
+ GSettings * lockdown_settings;
+ GSettings * indicator_settings;
+ GSettings * keybinding_settings;
+
+ /* cached settings taken from the upower proxy */
+ gboolean can_hibernate;
+ gboolean can_suspend;
+ gboolean allow_hibernate;
+ gboolean allow_suspend;
+
+ gboolean greeter_mode;
+
+ GCancellable * cancellable;
+ DBusUPower * upower_proxy;
+ SessionDbus * session_dbus;
+ UsersServiceDbus * users_dbus_facade;
+};
+
+static SwitcherMode get_switcher_mode (SessionMenuMgr *);
+
+static void init_upower_proxy (SessionMenuMgr *);
+
+static void update_screensaver_shortcut (SessionMenuMgr *);
+static void update_user_menuitems (SessionMenuMgr *);
+static void update_session_menuitems (SessionMenuMgr *);
+static void update_confirmation_labels (SessionMenuMgr *);
+
+static void action_func_lock (SessionMenuMgr *);
+static void action_func_suspend (SessionMenuMgr *);
+static void action_func_hibernate (SessionMenuMgr *);
+static void action_func_switch_to_lockscreen (SessionMenuMgr *);
+static void action_func_switch_to_greeter (SessionMenuMgr *);
+static void action_func_switch_to_guest (SessionMenuMgr *);
+static void action_func_switch_to_user (AccountsUser *);
+static void action_func_spawn_async (const char * cmd);
+
+static gboolean is_this_guest_session (void);
+static gboolean is_this_live_session (void);
+
+static void on_guest_logged_in_changed (UsersServiceDbus *,
+ SessionMenuMgr *);
+
+static void on_user_logged_in_changed (UsersServiceDbus *,
+ AccountsUser *,
+ SessionMenuMgr *);
+
+/**
+*** GObject init / dispose
+**/
+
+G_DEFINE_TYPE (SessionMenuMgr, session_menu_mgr, G_TYPE_OBJECT);
+
+static void
+session_menu_mgr_init (SessionMenuMgr *mgr)
+{
+ mgr->top_mi = dbusmenu_menuitem_new ();
+
+ /* Lockdown settings */
+ GSettings * s = g_settings_new ("org.gnome.desktop.lockdown");
+ g_signal_connect_swapped (s, "changed::disable-log-out",
+ G_CALLBACK(update_session_menuitems), mgr);
+ g_signal_connect_swapped (s, "changed::disable-lock-screen",
+ G_CALLBACK(update_user_menuitems), mgr);
+ g_signal_connect_swapped (s, "changed::disable-user-switching",
+ G_CALLBACK(update_user_menuitems), mgr);
+ mgr->lockdown_settings = s;
+
+ /* Indicator settings */
+ s = g_settings_new ("com.canonical.indicator.session");
+ g_signal_connect_swapped (s, "changed::suppress-logout-restart-shutdown",
+ G_CALLBACK(update_confirmation_labels), mgr);
+ g_signal_connect_swapped (s, "changed::suppress-logout-menuitem",
+ G_CALLBACK(update_session_menuitems), mgr);
+ g_signal_connect_swapped (s, "changed::suppress-restart-menuitem",
+ G_CALLBACK(update_session_menuitems), mgr);
+ g_signal_connect_swapped (s, "changed::suppress-shutdown-menuitem",
+ G_CALLBACK(update_session_menuitems), mgr);
+ mgr->indicator_settings = s;
+
+ /* Keybinding settings */
+ s = g_settings_new ("org.gnome.settings-daemon.plugins.media-keys");
+ g_signal_connect_swapped (s, "changed::screensaver",
+ G_CALLBACK(update_screensaver_shortcut), mgr);
+ mgr->keybinding_settings = s;
+
+ /* listen for user events */
+ mgr->users_dbus_facade = g_object_new (USERS_SERVICE_DBUS_TYPE, NULL);
+ g_signal_connect_swapped (mgr->users_dbus_facade, "user-list-changed",
+ G_CALLBACK (update_user_menuitems), mgr);
+ g_signal_connect (mgr->users_dbus_facade, "user-logged-in-changed",
+ G_CALLBACK(on_user_logged_in_changed), mgr);
+ g_signal_connect (mgr->users_dbus_facade, "guest-logged-in-changed",
+ G_CALLBACK(on_guest_logged_in_changed), mgr);
+
+ init_upower_proxy (mgr);
+}
+
+static void
+session_menu_mgr_dispose (GObject *object)
+{
+ SessionMenuMgr * mgr = SESSION_MENU_MGR (object);
+
+ if (mgr->cancellable != NULL)
+ {
+ g_cancellable_cancel (mgr->cancellable);
+ g_clear_object (&mgr->cancellable);
+ }
+
+ g_clear_object (&mgr->indicator_settings);
+ g_clear_object (&mgr->lockdown_settings);
+ g_clear_object (&mgr->keybinding_settings);
+ g_clear_object (&mgr->upower_proxy);
+ g_clear_object (&mgr->users_dbus_facade);
+ g_clear_object (&mgr->top_mi);
+ g_clear_object (&mgr->session_dbus);
+
+ g_slist_free (mgr->user_menuitems);
+ mgr->user_menuitems = NULL;
+
+ G_OBJECT_CLASS (session_menu_mgr_parent_class)->dispose (object);
+}
+
+static void
+session_menu_mgr_class_init (SessionMenuMgrClass * klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = session_menu_mgr_dispose;
+}
+
+/***
+**** UPower Proxy:
+****
+**** 1. While bootstrapping, we invoke the AllowSuspend and AllowHibernate
+**** methods to find out whether or not those features are allowed.
+**** 2. While bootstrapping, we get the CanSuspend and CanHibernate properties
+**** and also listen for property changes.
+**** 3. These four values are used to set suspend and hibernate's visibility.
+****
+***/
+
+static void
+on_upower_properties_changed (SessionMenuMgr * mgr)
+{
+ gboolean b;
+ gboolean need_refresh = FALSE;
+
+ /* suspend */
+ b = dbus_upower_get_can_suspend (mgr->upower_proxy);
+ if (mgr->can_suspend != b)
+ {
+ mgr->can_suspend = b;
+ need_refresh = TRUE;
+ }
+
+ /* hibernate */
+ b = dbus_upower_get_can_hibernate (mgr->upower_proxy);
+ if (mgr->can_hibernate != b)
+ {
+ mgr->can_hibernate = b;
+ need_refresh = TRUE;
+ }
+
+ if (need_refresh)
+ {
+ update_session_menuitems (mgr);
+ }
+}
+
+static void
+init_upower_proxy (SessionMenuMgr * mgr)
+{
+ /* default values */
+ mgr->can_suspend = TRUE;
+ mgr->can_hibernate = TRUE;
+ mgr->allow_suspend = TRUE;
+ mgr->allow_hibernate = TRUE;
+
+ mgr->cancellable = g_cancellable_new ();
+
+ GError * error = NULL;
+ mgr->upower_proxy = dbus_upower_proxy_new_for_bus_sync (
+ G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ UPOWER_ADDRESS,
+ UPOWER_PATH,
+ NULL,
+ &error);
+ if (error != NULL)
+ {
+ g_warning ("Error creating upower proxy: %s", error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ dbus_upower_call_suspend_allowed_sync (mgr->upower_proxy,
+ &mgr->allow_suspend,
+ NULL,
+ &error);
+ if (error != NULL)
+ {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_clear_error (&error);
+ }
+
+ dbus_upower_call_hibernate_allowed_sync (mgr->upower_proxy,
+ &mgr->allow_hibernate,
+ NULL,
+ &error);
+ if (error != NULL)
+ {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_clear_error (&error);
+ }
+
+ on_upower_properties_changed (mgr);
+ g_signal_connect_swapped (mgr->upower_proxy, "changed",
+ G_CALLBACK(on_upower_properties_changed), mgr);
+ }
+}
+
+/***
+**** Menuitem Helpers
+***/
+
+static inline void
+mi_set_label (DbusmenuMenuitem * mi, const char * str)
+{
+ dbusmenu_menuitem_property_set (mi, DBUSMENU_MENUITEM_PROP_LABEL, str);
+}
+
+static inline void
+mi_set_type (DbusmenuMenuitem * mi, const char * str)
+{
+ dbusmenu_menuitem_property_set (mi, DBUSMENU_MENUITEM_PROP_TYPE, str);
+}
+
+static inline void
+mi_set_visible (DbusmenuMenuitem * mi, gboolean b)
+{
+ dbusmenu_menuitem_property_set_bool (mi, DBUSMENU_MENUITEM_PROP_VISIBLE,
+ b || DEBUG_SHOW_ALL);
+}
+
+static inline void
+mi_set_logged_in (DbusmenuMenuitem * mi, gboolean b)
+{
+ dbusmenu_menuitem_property_set_bool (mi, USER_ITEM_PROP_LOGGED_IN, b);
+}
+
+static DbusmenuMenuitem*
+mi_new_separator (void)
+{
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_new ();
+ mi_set_type (mi, DBUSMENU_CLIENT_TYPES_SEPARATOR);
+ return mi;
+}
+
+static DbusmenuMenuitem*
+mi_new (const char * label)
+{
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_new ();
+ mi_set_label (mi, label);
+ return mi;
+}
+
+/***
+**** Admin Menuitems
+**** <https://wiki.ubuntu.com/SystemMenu#Admin_items>
+***/
+
+static void
+build_admin_menuitems (SessionMenuMgr * mgr)
+{
+ if (!mgr->greeter_mode)
+ {
+ DbusmenuMenuitem * mi;
+ const gboolean show_settings = !mgr->greeter_mode;
+
+ mi = mi_new (_("About This Computer"));
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK(action_func_spawn_async), CMD_INFO);
+
+ mi = mi_new (_("Ubuntu Help"));
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK(action_func_spawn_async), CMD_HELP);
+
+ mi = mi_new_separator ();
+ mi_set_visible (mi, show_settings);
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+
+ mi = mi_new (_("System Settings\342\200\246"));
+ mi_set_visible (mi, show_settings);
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK(action_func_spawn_async),
+ CMD_SYSTEM_SETTINGS);
+
+ mi = mi_new_separator ();
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+ }
+}
+
+/***
+**** Session Menuitems
+**** <https://wiki.ubuntu.com/SystemMenu#Session_items>
+***/
+
+static void
+update_session_menuitems (SessionMenuMgr * mgr)
+{
+ gboolean v;
+ GSettings * s = mgr->indicator_settings;
+
+ v = !mgr->greeter_mode
+ && !is_this_live_session()
+ && !g_settings_get_boolean (mgr->lockdown_settings, "disable-log-out")
+ && !g_settings_get_boolean (s, "suppress-logout-menuitem");
+ mi_set_visible (mgr->logout_mi, v);
+
+ v = mgr->can_suspend
+ && mgr->allow_suspend;
+ mi_set_visible (mgr->suspend_mi, v);
+
+ v = mgr->can_hibernate
+ && mgr->allow_hibernate;
+ mi_set_visible (mgr->hibernate_mi, v);
+
+ v = HAVE_RESTART_CMD
+ && !g_settings_get_boolean (s, "suppress-restart-menuitem");
+ mi_set_visible (mgr->restart_mi, v);
+
+ v = !g_settings_get_boolean (s, "suppress-shutdown-menuitem");
+ mi_set_visible (mgr->shutdown_mi, v);
+}
+
+/* Update the ellipses when the confirmation setting changes.
+ *
+ * <http://developer.gnome.org/hig-book/3.0/menus-design.html.en>:
+ * "Label the menu item with a trailing ellipsis ("...") only if the
+ * command requires further input from the user before it can be performed."
+ */
+static void
+update_confirmation_labels (SessionMenuMgr * mgr)
+{
+ const gboolean confirm_needed = !g_settings_get_boolean (
+ mgr->indicator_settings,
+ "suppress-logout-restart-shutdown");
+
+ mi_set_label (mgr->logout_mi, confirm_needed ? _("Log Out\342\200\246")
+ : _("Log Out"));
+
+ mi_set_label (mgr->shutdown_mi, confirm_needed ? _("Switch Off\342\200\246")
+ : _("Switch Off"));
+
+ dbusmenu_menuitem_property_set (mgr->restart_mi, RESTART_ITEM_LABEL,
+ confirm_needed ? _("Restart\342\200\246")
+ : _("Restart"));
+}
+
+static void
+build_session_menuitems (SessionMenuMgr* mgr)
+{
+ DbusmenuMenuitem * mi;
+
+ mi = mgr->logout_mi = mi_new (_("Log Out\342\200\246"));
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK(action_func_spawn_async), CMD_LOGOUT);
+
+ mi = mgr->suspend_mi = mi_new ("Suspend");
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK(action_func_suspend), mgr);
+
+ mi = mgr->hibernate_mi = mi_new (_("Hibernate"));
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK(action_func_hibernate), mgr);
+
+ mi = mgr->restart_mi = dbusmenu_menuitem_new ();
+ mi_set_type (mi, RESTART_ITEM_TYPE);
+ dbusmenu_menuitem_property_set (mi, RESTART_ITEM_LABEL, _("Restart\342\200\246"));
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK(action_func_spawn_async), CMD_RESTART);
+
+ mi = mgr->shutdown_mi = mi_new (_("Switch Off\342\200\246"));
+ dbusmenu_menuitem_child_append (mgr->top_mi, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK(action_func_spawn_async), CMD_SHUTDOWN);
+
+ update_confirmation_labels (mgr);
+ update_session_menuitems (mgr);
+}
+
+/****
+***** User Menuitems
+***** https://wiki.ubuntu.com/SystemMenu#Account-switching_items
+****/
+
+/* Local extensions to AccountsUser */
+
+static GQuark
+get_menuitem_quark (void)
+{
+ static GQuark q = 0;
+
+ if (G_UNLIKELY(!q))
+ {
+ q = g_quark_from_static_string ("menuitem");
+ }
+
+ return q;
+}
+
+static DbusmenuMenuitem*
+user_get_menuitem (AccountsUser * user)
+{
+ return g_object_get_qdata (G_OBJECT(user), get_menuitem_quark());
+}
+
+static void
+user_clear_menuitem (AccountsUser * user)
+{
+ g_object_steal_qdata (G_OBJECT(user), get_menuitem_quark());
+}
+
+static void
+user_set_menuitem (AccountsUser * user, DbusmenuMenuitem * mi)
+{
+ g_object_set_qdata (G_OBJECT(user), get_menuitem_quark(), mi);
+
+ g_object_weak_ref (G_OBJECT(mi), (GWeakNotify)user_clear_menuitem, user);
+}
+
+/***/
+
+static GQuark
+get_mgr_quark (void)
+{
+ static GQuark q = 0;
+
+ if (G_UNLIKELY(!q))
+ {
+ q = g_quark_from_static_string ("session-menu-mgr");
+ }
+
+ return q;
+}
+
+static SessionMenuMgr*
+user_get_mgr (AccountsUser * user)
+{
+ return g_object_get_qdata (G_OBJECT(user), get_mgr_quark());
+}
+
+static void
+user_set_mgr (AccountsUser * user, SessionMenuMgr * mgr)
+{
+ g_object_set_qdata (G_OBJECT(user), get_mgr_quark(), mgr);
+}
+
+/***/
+
+static GQuark
+get_collision_quark (void)
+{
+ static GQuark q = 0;
+
+ if (G_UNLIKELY(!q))
+ {
+ q = g_quark_from_static_string ("name-collision");
+ }
+
+ return q;
+}
+
+static gboolean
+user_has_name_collision (AccountsUser * u)
+{
+ return g_object_get_qdata (G_OBJECT(u), get_collision_quark()) != NULL;
+}
+
+static void
+user_set_name_collision (AccountsUser * u, gboolean b)
+{
+ g_object_set_qdata (G_OBJECT(u), get_collision_quark(), GINT_TO_POINTER(b));
+}
+
+/***
+****
+***/
+
+static void
+on_guest_logged_in_changed (UsersServiceDbus * usd,
+ SessionMenuMgr * mgr)
+{
+ if (mgr->guest_mi != NULL)
+ {
+ mi_set_logged_in (mgr->guest_mi,
+ users_service_dbus_is_guest_logged_in (usd));
+ }
+}
+
+/* When a user's login state changes,
+ update the corresponding menuitem's LOGGED_IN property */
+static void
+on_user_logged_in_changed (UsersServiceDbus * usd,
+ AccountsUser * user,
+ SessionMenuMgr * mgr)
+{
+ DbusmenuMenuitem * mi = user_get_menuitem (user);
+
+ if (mi != NULL)
+ {
+ mi_set_logged_in (mi, users_service_dbus_is_user_logged_in (usd, user));
+ }
+}
+
+static void
+update_screensaver_shortcut (SessionMenuMgr * mgr)
+{
+ gchar * s = g_settings_get_string (mgr->keybinding_settings, "screensaver");
+ g_debug ("%s Screensaver shortcut changed to: '%s'", G_STRLOC, s);
+
+ if (mgr->lock_mi != NULL)
+ {
+ dbusmenu_menuitem_property_set_shortcut_string (mgr->lock_mi, s);
+ }
+
+ if (mgr->lock_switch_mi != NULL)
+ {
+ dbusmenu_menuitem_property_set_shortcut_string (mgr->lock_switch_mi, s);
+ }
+
+ if (mgr->screensaver_mi != NULL)
+ {
+ dbusmenu_menuitem_property_set_shortcut_string (mgr->screensaver_mi, s);
+ }
+
+ g_free (s);
+}
+
+static void
+update_user_menuitem_icon (DbusmenuMenuitem * mi, AccountsUser * user)
+{
+ const gchar * str = accounts_user_get_icon_file (user);
+
+ if (!str || !*str)
+ {
+ str = USER_ITEM_ICON_DEFAULT;
+ }
+
+ dbusmenu_menuitem_property_set (mi, USER_ITEM_PROP_ICON, str);
+}
+
+static void
+update_user_menuitem_name (DbusmenuMenuitem * mi, AccountsUser * user)
+{
+ GString * gstr = g_string_new (accounts_user_get_real_name (user));
+
+ if (user_has_name_collision (user))
+ {
+ g_string_append_printf (gstr, " (%s)", accounts_user_get_user_name(user));
+ }
+
+ dbusmenu_menuitem_property_set (mi, USER_ITEM_PROP_NAME, gstr->str);
+ g_string_free (gstr, TRUE);
+}
+
+static void
+on_user_property_changed (AccountsUser * user,
+ GParamSpec * pspec,
+ DbusmenuMenuitem * mi)
+{
+ static const char * interned_icon_file = NULL;
+ static const char * interned_real_name = NULL;
+ static const char * interned_user_name = NULL;
+
+ if (G_UNLIKELY (interned_icon_file == NULL))
+ {
+ interned_icon_file = g_intern_static_string ("icon-file");
+ interned_user_name = g_intern_static_string ("user-name");
+ interned_real_name = g_intern_static_string ("real-name");
+ }
+
+ if (pspec->name == interned_icon_file)
+ {
+ update_user_menuitem_icon (mi, user);
+ }
+ else if ((pspec->name == interned_real_name)
+ || (pspec->name == interned_user_name))
+ {
+ /* name changing can affect other menuitems too by invalidating
+ the sort order or name collision flags... so let's rebuild */
+ update_user_menuitems (user_get_mgr (user));
+ }
+}
+
+typedef struct
+{
+ gpointer instance;
+ gulong handler_id;
+}
+SignalHandlerData;
+
+/* when a user menuitem is destroyed,
+ it should stop listening for its UserAccount's property changes */
+static void
+on_user_menuitem_destroyed (SignalHandlerData * data)
+{
+ g_signal_handler_disconnect (data->instance, data->handler_id);
+ g_free (data);
+}
+
+static DbusmenuMenuitem*
+user_menuitem_new (AccountsUser * user, SessionMenuMgr * mgr)
+{
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_new ();
+ mi_set_type (mi, USER_ITEM_TYPE);
+
+ /* set the name & icon and listen for property changes */
+ update_user_menuitem_name (mi, user);
+ update_user_menuitem_icon (mi, user);
+ SignalHandlerData * hd = g_new0 (SignalHandlerData, 1);
+ hd->instance = user;
+ hd->handler_id = g_signal_connect (user, "notify",
+ G_CALLBACK(on_user_property_changed), mi);
+ g_object_weak_ref (G_OBJECT(mi), (GWeakNotify)on_user_menuitem_destroyed, hd);
+
+ /* set the logged-in property */
+ mi_set_logged_in (mi,
+ users_service_dbus_is_user_logged_in (mgr->users_dbus_facade, user));
+
+ /* set the is-current-user property */
+ const gboolean is_current_user =
+ !g_strcmp0 (g_get_user_name(), accounts_user_get_user_name(user));
+ dbusmenu_menuitem_property_set_bool (mi,
+ USER_ITEM_PROP_IS_CURRENT_USER,
+ is_current_user);
+
+ /* set the switch-to-user action */
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK (action_func_switch_to_user), user);
+
+ /* give this AccountsUser a hook back to this menuitem */
+ user_set_menuitem (user, mi);
+ user_set_mgr (user, mgr);
+
+ return mi;
+}
+
+/* for sorting AccountsUsers from most to least frequently used */
+static gint
+compare_users_by_login_frequency (gconstpointer a, gconstpointer b)
+{
+ const guint64 a_freq = accounts_user_get_login_frequency (ACCOUNTS_USER(a));
+ const guint64 b_freq = accounts_user_get_login_frequency (ACCOUNTS_USER(b));
+ if (a_freq > b_freq) return -1;
+ if (a_freq < b_freq) return 1;
+ return 0;
+}
+
+/* for sorting AccountsUsers alphabetically */
+static gint
+compare_users_by_username (gconstpointer ga, gconstpointer gb)
+{
+ AccountsUser * a = ACCOUNTS_USER(ga);
+ AccountsUser * b = ACCOUNTS_USER(gb);
+
+ const int ret = g_strcmp0 (accounts_user_get_real_name (a),
+ accounts_user_get_real_name (b));
+
+ if (!ret) /* names are the same, so both have a name collision */
+ {
+ user_set_name_collision (a, TRUE);
+ user_set_name_collision (b, TRUE);
+ }
+
+ return ret;
+}
+
+static gboolean
+is_user_switching_allowed (SessionMenuMgr * mgr)
+{
+ /* maybe it's locked down */
+ if (g_settings_get_boolean (mgr->lockdown_settings, "disable-user-switching"))
+ {
+ return FALSE;
+ }
+
+ /* maybe the seat doesn't support activation */
+ if (!users_service_dbus_can_activate_session (mgr->users_dbus_facade))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+build_user_menuitems (SessionMenuMgr * mgr)
+{
+ g_return_if_fail (!mgr->greeter_mode);
+
+ DbusmenuMenuitem * mi;
+ GSList * items = NULL;
+ gint pos = mgr->user_menuitem_index;
+ const char * current_real_name = NULL;
+
+ /**
+ *** Start Screen Saver
+ *** Switch Account...
+ *** Lock
+ *** Lock / Switch Account...
+ **/
+
+ const SwitcherMode mode = get_switcher_mode (mgr);
+
+ mi = mgr->screensaver_mi = mi_new (_("Start Screen Saver"));
+ mi_set_visible (mi, mode == SWITCHER_MODE_SCREENSAVER);
+ dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
+ items = g_slist_prepend (items, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK (action_func_lock), mgr);
+
+ mi = mi_new (_("Switch User Account\342\200\246"));
+ mi_set_visible (mi, mode == SWITCHER_MODE_SWITCH);
+ dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
+ items = g_slist_prepend (items, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK (action_func_switch_to_greeter), mgr);
+
+ mi = mgr->lock_mi = mi_new (_("Lock"));
+ mi_set_visible (mi, mode == SWITCHER_MODE_LOCK);
+ dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
+ items = g_slist_prepend (items, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK (action_func_switch_to_lockscreen), mgr);
+
+ mi = mgr->lock_switch_mi = mi_new (_("Lock/Switch Account\342\200\246"));
+ mi_set_visible (mi, mode == SWITCHER_MODE_SWITCH_OR_LOCK);
+ dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
+ items = g_slist_prepend (items, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK (action_func_switch_to_lockscreen), mgr);
+
+ const gboolean is_guest = is_this_guest_session ();
+ const gboolean guest_allowed =
+ users_service_dbus_guest_session_enabled (mgr->users_dbus_facade);
+ mi = mgr->guest_mi = dbusmenu_menuitem_new ();
+ mi_set_type (mi, USER_ITEM_TYPE);
+ mi_set_visible (mi, !is_guest && guest_allowed);
+ dbusmenu_menuitem_property_set (mi, USER_ITEM_PROP_NAME, _("Guest Session"));
+ dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
+ on_guest_logged_in_changed (mgr->users_dbus_facade, mgr);
+ items = g_slist_prepend (items, mi);
+ g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK (action_func_switch_to_guest), mgr);
+
+ if (guest_allowed && is_guest)
+ {
+ current_real_name = _("Guest");
+ }
+
+ /***
+ **** Users
+ ***/
+
+ /* if we can switch to another user account, show them here */
+ const char * const username = g_get_user_name();
+ GList * users = users_service_dbus_get_user_list (mgr->users_dbus_facade);
+
+ /* since we're building (or rebuilding) from scratch,
+ clear the name collision flags */
+ GList * u;
+ for (u=users; u!=NULL; u=u->next)
+ {
+ AccountsUser * user = ACCOUNTS_USER(u->data);
+
+ user_set_name_collision (user, FALSE);
+
+ if (!g_strcmp0 (username, accounts_user_get_user_name(user)))
+ {
+ current_real_name = accounts_user_get_real_name (user);
+ }
+ }
+
+ if (is_user_switching_allowed (mgr))
+ {
+ /* pick the most frequently used accounts */
+ const int MAX_USERS = 12; /* this limit comes from the spec */
+ if (g_list_length(users) > MAX_USERS)
+ {
+ users = g_list_sort (users, compare_users_by_login_frequency);
+ GList * last = g_list_nth (users, MAX_USERS-1);
+ GList * remainder = last->next;
+ last->next = NULL;
+ remainder->prev = NULL;
+ g_list_free (remainder);
+ }
+
+ /* Sort the users by name for display */
+ users = g_list_sort (users, compare_users_by_username);
+
+ /* Create menuitems for them */
+ int i;
+ for (i=0, u=users; i<MAX_USERS && u!=NULL; u=u->next, i++)
+ {
+ AccountsUser * user = u->data;
+ DbusmenuMenuitem * mi = user_menuitem_new (user, mgr);
+ dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
+ items = g_slist_prepend (items, mi);
+ }
+ }
+
+ g_list_free (users);
+
+ /* separator */
+ mi = mi_new_separator ();
+ dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
+ items = g_slist_prepend (items, mi);
+
+ if (current_real_name != NULL)
+ {
+ session_dbus_set_users_real_name (mgr->session_dbus,
+ current_real_name);
+ }
+
+ update_screensaver_shortcut (mgr);
+ mgr->user_menuitems = items;
+}
+
+static void
+update_user_menuitems (SessionMenuMgr * mgr)
+{
+ /* remove any previous user menuitems */
+ GSList * l;
+ for (l=mgr->user_menuitems; l!=NULL; l=l->next)
+ {
+ dbusmenu_menuitem_child_delete (mgr->top_mi, l->data);
+ }
+ g_slist_free (mgr->user_menuitems);
+ mgr->user_menuitems = NULL;
+
+ /* add fresh user menuitems */
+ if (!mgr->greeter_mode)
+ {
+ build_user_menuitems (mgr);
+ }
+}
+
+/***
+**** Actions!
+***/
+
+static void
+action_func_spawn_async (const char * cmd)
+{
+ GError * error = NULL;
+
+ g_debug ("%s calling \"%s\"", G_STRFUNC, cmd);
+ g_spawn_command_line_async (cmd, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Unable to execute \"%s\": %s", cmd, error->message);
+ g_clear_error (&error);
+ }
+}
+
+/* Calling "Lock" locks the screen & goes to black.
+ Calling "SimulateUserActivity" afterwards shows the Lock Screen. */
+static void
+lock_helper (SessionMenuMgr * mgr, gboolean show_lock_screen)
+{
+ if (!g_settings_get_boolean (mgr->lockdown_settings, "disable-lock-screen"))
+ {
+ GError * error = NULL;
+ GDBusProxy * proxy = g_dbus_proxy_new_for_bus_sync (
+ G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.gnome.ScreenSaver",
+ "/org/gnome/ScreenSaver",
+ "org.gnome.ScreenSaver",
+ NULL,
+ &error);
+
+ if (error == NULL)
+ {
+ g_dbus_proxy_call_sync (proxy, "Lock",
+ NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL,
+ &error);
+ }
+
+ if ((error == NULL) && show_lock_screen)
+ {
+ g_dbus_proxy_call_sync (proxy, "SimulateUserActivity",
+ NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL,
+ &error);
+ }
+
+ if (error != NULL)
+ {
+ g_warning ("Error locking screen: %s", error->message);
+ }
+
+ g_clear_object (&proxy);
+ g_clear_error (&error);
+ }
+}
+
+static void
+action_func_lock (SessionMenuMgr * mgr)
+{
+ lock_helper (mgr, FALSE);
+}
+
+static void
+action_func_switch_to_lockscreen (SessionMenuMgr * mgr)
+{
+ lock_helper (mgr, TRUE);
+}
+
+static void
+action_func_switch_to_greeter (SessionMenuMgr * mgr)
+{
+ action_func_lock (mgr);
+ users_service_dbus_show_greeter (mgr->users_dbus_facade);
+}
+
+static void
+action_func_switch_to_user (AccountsUser * user)
+{
+ SessionMenuMgr * mgr = user_get_mgr (user);
+ g_return_if_fail (mgr != NULL);
+ action_func_lock (mgr);
+ users_service_dbus_activate_user_session (mgr->users_dbus_facade, user);
+}
+
+static void
+action_func_switch_to_guest (SessionMenuMgr * mgr)
+{
+ action_func_lock (mgr);
+ users_service_dbus_activate_guest_session (mgr->users_dbus_facade);
+}
+
+static void
+action_func_suspend (SessionMenuMgr * mgr)
+{
+ GError * error = NULL;
+
+ dbus_upower_call_suspend_sync (mgr->upower_proxy,
+ mgr->cancellable,
+ &error);
+
+ if (error != NULL)
+ {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_clear_error (&error);
+ }
+}
+
+static void
+action_func_hibernate (SessionMenuMgr * mgr)
+{
+ GError * error = NULL;
+
+ dbus_upower_call_hibernate_sync (mgr->upower_proxy,
+ mgr->cancellable,
+ &error);
+
+ if (error != NULL)
+ {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_clear_error (&error);
+ }
+}
+
+/***
+****
+***/
+
+static gboolean
+is_this_guest_session (void)
+{
+ /* FIXME: this test has been here awhile and seems to work,
+ but seems brittle to me */
+ return geteuid() < 500;
+}
+
+static gboolean
+is_this_live_session (void)
+{
+ const struct passwd * const pw = getpwuid (geteuid());
+ return (pw->pw_uid==999) && !g_strcmp0("ubuntu",pw->pw_name);
+}
+
+static SwitcherMode
+get_switcher_mode (SessionMenuMgr * mgr)
+{
+ SwitcherMode mode;
+
+ const gboolean can_lock = !g_settings_get_boolean (mgr->lockdown_settings,
+ "disable-lock-screen");
+ const gboolean can_switch = is_user_switching_allowed (mgr);
+
+ if (!can_lock && !can_switch) /* hmm, quite an extreme lockdown */
+ {
+ mode = SWITCHER_MODE_SCREENSAVER;
+ }
+ else if (is_this_live_session()) /* live sessions can't lock or switch */
+ {
+ mode = SWITCHER_MODE_SCREENSAVER;
+ }
+ else if (!can_switch) /* switching's locked down */
+ {
+ mode = SWITCHER_MODE_LOCK;
+ }
+ else if (is_this_guest_session ()) /* guest sessions can't lock */
+ {
+ mode = SWITCHER_MODE_SWITCH;
+ }
+ else /* both locking & switching are allowed */
+ {
+ GList * l = users_service_dbus_get_user_list (mgr->users_dbus_facade);
+ const size_t user_count = g_list_length (l);
+ g_list_free (l);
+
+ /* only show switch mode if we have users to switch to */
+ mode = user_count > (is_this_guest_session() ? 0 : 1)
+ ? SWITCHER_MODE_SWITCH_OR_LOCK
+ : SWITCHER_MODE_LOCK;
+ }
+
+ return mode;
+}
+
+
+/***
+****
+***/
+
+SessionMenuMgr*
+session_menu_mgr_new (SessionDbus * session_dbus,
+ gboolean greeter_mode)
+{
+ SessionMenuMgr* mgr = g_object_new (SESSION_TYPE_MENU_MGR, NULL);
+ mgr->greeter_mode = greeter_mode;
+ mgr->session_dbus = g_object_ref (session_dbus);
+ build_admin_menuitems (mgr);
+ const guint n = g_list_length (dbusmenu_menuitem_get_children (mgr->top_mi));
+ mgr->user_menuitem_index = n;
+ update_user_menuitems (mgr);
+ build_session_menuitems (mgr);
+ return mgr;
+}
+
+/**
+ * session_menu_mgr_get_menu:
+ *
+ * Returns: (transfer none): the manager's menu.
+ */
+DbusmenuMenuitem *
+session_menu_mgr_get_menu (SessionMenuMgr * mgr)
+{
+ g_return_val_if_fail (IS_SESSION_MENU_MGR(mgr), NULL);
+
+ return mgr->top_mi;
+}