Back to index

indicator-session  12.10.0
session-menu-mgr.c
Go to the documentation of this file.
00001 /*
00002 Copyright 2011 Canonical Ltd.
00003 
00004 Authors:
00005     Charles Kerr <charles.kerr@canonical.com>
00006     Conor Curran <conor.curran@canonical.com>
00007 
00008 This program is free software: you can redistribute it and/or modify it
00009 under the terms of the GNU General Public License version 3, as published
00010 by the Free Software Foundation.
00011 
00012 This program is distributed in the hope that it will be useful, but
00013 WITHOUT ANY WARRANTY; without even the implied warranties of
00014 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
00015 PURPOSE.  See the GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License along
00018 with this program.  If not, see <http://www.gnu.org/licenses/>.
00019 */
00020 
00021 #include "config.h"
00022 
00023 #include <sys/types.h>
00024 #include <pwd.h> /* geteuid(), getpwuid() */
00025 
00026 #include <glib.h>
00027 #include <glib/gi18n.h>
00028 
00029 #include <libdbusmenu-glib/client.h>
00030 #include <libdbusmenu-gtk/menuitem.h>
00031 
00032 #include "dbus-upower.h"
00033 #include "session-menu-mgr.h"
00034 #include "shared-names.h"
00035 #include "users-service-dbus.h"
00036 
00037 #define DEBUG_SHOW_ALL FALSE
00038 
00039 #define UPOWER_ADDRESS    "org.freedesktop.UPower"
00040 #define UPOWER_PATH       "/org/freedesktop/UPower"
00041 
00042 #define CMD_HELP            "yelp"
00043 #define CMD_INFO            "gnome-control-center info"
00044 #define CMD_SYSTEM_SETTINGS "gnome-control-center"
00045 #ifdef HAVE_GTKLOGOUTHELPER
00046  #define HAVE_RESTART_CMD TRUE
00047  #define CMD_RESTART  LIBEXECDIR"/gtk-logout-helper --restart"
00048  #define CMD_LOGOUT   LIBEXECDIR"/gtk-logout-helper --logout"
00049  #define CMD_SHUTDOWN LIBEXECDIR"/gtk-logout-helper --shutdown"
00050 #else
00051  #define HAVE_RESTART_CMD FALSE /* hmm, no gnome-session-quit --restart? */
00052  #define CMD_RESTART  ""
00053  #define CMD_LOGOUT   "gnome-session-quit --logout"
00054  #define CMD_SHUTDOWN "gnome-session-quit --power-off"
00055 #endif
00056 
00062 typedef enum
00063 {
00064   SWITCHER_MODE_SCREENSAVER,
00065   SWITCHER_MODE_LOCK,
00066   SWITCHER_MODE_SWITCH,
00067   SWITCHER_MODE_SWITCH_OR_LOCK
00068 }
00069 SwitcherMode;
00070 
00082 struct _SessionMenuMgr
00083 {
00084   GObject parent_instance;
00085 
00086   DbusmenuMenuitem * top_mi;
00087   DbusmenuMenuitem * screensaver_mi;
00088   DbusmenuMenuitem * lock_mi;
00089   DbusmenuMenuitem * lock_switch_mi;
00090   DbusmenuMenuitem * guest_mi;
00091   DbusmenuMenuitem * logout_mi;
00092   DbusmenuMenuitem * suspend_mi;
00093   DbusmenuMenuitem * hibernate_mi;
00094   DbusmenuMenuitem * restart_mi;
00095   DbusmenuMenuitem * shutdown_mi;
00096 
00097   GSList * user_menuitems;
00098   gint user_menuitem_index;
00099 
00100   GSettings * lockdown_settings;
00101   GSettings * indicator_settings;
00102   GSettings * keybinding_settings;
00103 
00104   /* cached settings taken from the upower proxy */
00105   gboolean can_hibernate;
00106   gboolean can_suspend;
00107   gboolean allow_hibernate;
00108   gboolean allow_suspend;
00109 
00110   gboolean greeter_mode;
00111 
00112   GCancellable * cancellable;
00113   DBusUPower * upower_proxy;
00114   SessionDbus * session_dbus;
00115   UsersServiceDbus * users_dbus_facade;
00116 };
00117 
00118 static SwitcherMode get_switcher_mode         (SessionMenuMgr *);
00119 
00120 static void init_upower_proxy                 (SessionMenuMgr *);
00121 
00122 static void update_screensaver_shortcut       (SessionMenuMgr *);
00123 static void update_user_menuitems             (SessionMenuMgr *);
00124 static void update_session_menuitems          (SessionMenuMgr *);
00125 static void update_confirmation_labels        (SessionMenuMgr *);
00126 
00127 static void action_func_lock                  (SessionMenuMgr *);
00128 static void action_func_suspend               (SessionMenuMgr *);
00129 static void action_func_hibernate             (SessionMenuMgr *);
00130 static void action_func_switch_to_lockscreen  (SessionMenuMgr *);
00131 static void action_func_switch_to_greeter     (SessionMenuMgr *);
00132 static void action_func_switch_to_guest       (SessionMenuMgr *);
00133 static void action_func_switch_to_user        (AccountsUser   *);
00134 static void action_func_spawn_async           (const char * cmd);
00135 
00136 static gboolean is_this_guest_session         (void);
00137 static gboolean is_this_live_session          (void);
00138 
00139 static void on_guest_logged_in_changed        (UsersServiceDbus *,
00140                                                SessionMenuMgr   *);
00141 
00142 static void on_user_logged_in_changed         (UsersServiceDbus *,
00143                                                AccountsUser     *,
00144                                                SessionMenuMgr   *);
00145 
00150 G_DEFINE_TYPE (SessionMenuMgr, session_menu_mgr, G_TYPE_OBJECT);
00151 
00152 static void
00153 session_menu_mgr_init (SessionMenuMgr *mgr)
00154 {
00155   mgr->top_mi = dbusmenu_menuitem_new ();
00156 
00157   /* Lockdown settings */
00158   GSettings * s = g_settings_new ("org.gnome.desktop.lockdown");
00159   g_signal_connect_swapped (s, "changed::disable-log-out",
00160                             G_CALLBACK(update_session_menuitems), mgr);
00161   g_signal_connect_swapped (s, "changed::disable-lock-screen",
00162                             G_CALLBACK(update_user_menuitems), mgr);
00163   g_signal_connect_swapped (s, "changed::disable-user-switching",
00164                             G_CALLBACK(update_user_menuitems), mgr);
00165   mgr->lockdown_settings = s;
00166 
00167   /* Indicator settings */
00168   s = g_settings_new ("com.canonical.indicator.session");
00169   g_signal_connect_swapped (s, "changed::suppress-logout-restart-shutdown",
00170                             G_CALLBACK(update_confirmation_labels), mgr);
00171   g_signal_connect_swapped (s, "changed::suppress-logout-menuitem",
00172                             G_CALLBACK(update_session_menuitems), mgr);
00173   g_signal_connect_swapped (s, "changed::suppress-restart-menuitem",
00174                             G_CALLBACK(update_session_menuitems), mgr);
00175   g_signal_connect_swapped (s, "changed::suppress-shutdown-menuitem",
00176                             G_CALLBACK(update_session_menuitems), mgr);
00177   mgr->indicator_settings = s;
00178 
00179   /* Keybinding settings */
00180   s = g_settings_new ("org.gnome.settings-daemon.plugins.media-keys");
00181   g_signal_connect_swapped (s, "changed::screensaver",
00182                             G_CALLBACK(update_screensaver_shortcut), mgr);
00183   mgr->keybinding_settings = s;
00184 
00185   /* listen for user events */
00186   mgr->users_dbus_facade = g_object_new (USERS_SERVICE_DBUS_TYPE, NULL);
00187   g_signal_connect_swapped (mgr->users_dbus_facade, "user-list-changed",
00188                             G_CALLBACK (update_user_menuitems), mgr);
00189   g_signal_connect (mgr->users_dbus_facade, "user-logged-in-changed",
00190                     G_CALLBACK(on_user_logged_in_changed), mgr);
00191   g_signal_connect (mgr->users_dbus_facade, "guest-logged-in-changed",
00192                     G_CALLBACK(on_guest_logged_in_changed), mgr);
00193 
00194   init_upower_proxy (mgr);
00195 }
00196 
00197 static void
00198 session_menu_mgr_dispose (GObject *object)
00199 {
00200   SessionMenuMgr * mgr = SESSION_MENU_MGR (object);
00201 
00202   if (mgr->cancellable != NULL)
00203     {
00204       g_cancellable_cancel (mgr->cancellable);
00205       g_clear_object (&mgr->cancellable);
00206      }
00207 
00208   g_clear_object (&mgr->indicator_settings);
00209   g_clear_object (&mgr->lockdown_settings);
00210   g_clear_object (&mgr->keybinding_settings);
00211   g_clear_object (&mgr->upower_proxy);
00212   g_clear_object (&mgr->users_dbus_facade);
00213   g_clear_object (&mgr->top_mi);
00214   g_clear_object (&mgr->session_dbus);
00215 
00216   g_slist_free (mgr->user_menuitems);
00217   mgr->user_menuitems = NULL;
00218 
00219   G_OBJECT_CLASS (session_menu_mgr_parent_class)->dispose (object);
00220 }
00221 
00222 static void
00223 session_menu_mgr_class_init (SessionMenuMgrClass * klass)
00224 {
00225   GObjectClass* object_class = G_OBJECT_CLASS (klass);
00226   object_class->dispose = session_menu_mgr_dispose;
00227 }
00228 
00229 /***
00230 ****  UPower Proxy:
00231 ****
00232 ****  1. While bootstrapping, we invoke the AllowSuspend and AllowHibernate
00233 ****     methods to find out whether or not those features are allowed.
00234 ****  2. While bootstrapping, we get the CanSuspend and CanHibernate properties
00235 ****     and also listen for property changes.
00236 ****  3. These four values are used to set suspend and hibernate's visibility.
00237 ****
00238 ***/
00239 
00240 static void
00241 on_upower_properties_changed (SessionMenuMgr * mgr)
00242 {
00243   gboolean b;
00244   gboolean need_refresh = FALSE;
00245 
00246   /* suspend */
00247   b = dbus_upower_get_can_suspend (mgr->upower_proxy);
00248   if (mgr->can_suspend != b)
00249     {
00250       mgr->can_suspend = b;
00251       need_refresh = TRUE;
00252     }
00253 
00254   /* hibernate */
00255   b = dbus_upower_get_can_hibernate (mgr->upower_proxy);
00256   if (mgr->can_hibernate != b)
00257     {
00258       mgr->can_hibernate = b;
00259       need_refresh = TRUE;
00260     }
00261 
00262   if (need_refresh)
00263     {
00264       update_session_menuitems (mgr);
00265     }
00266 }
00267 
00268 static void
00269 init_upower_proxy (SessionMenuMgr * mgr)
00270 {
00271   /* default values */
00272   mgr->can_suspend = TRUE;
00273   mgr->can_hibernate = TRUE;
00274   mgr->allow_suspend = TRUE;
00275   mgr->allow_hibernate = TRUE;
00276 
00277   mgr->cancellable = g_cancellable_new ();
00278 
00279   GError * error = NULL;
00280   mgr->upower_proxy = dbus_upower_proxy_new_for_bus_sync (
00281                          G_BUS_TYPE_SYSTEM,
00282                          G_DBUS_PROXY_FLAGS_NONE,
00283                          UPOWER_ADDRESS,
00284                          UPOWER_PATH,
00285                          NULL,
00286                          &error);
00287   if (error != NULL)
00288     {
00289       g_warning ("Error creating upower proxy: %s", error->message);
00290       g_clear_error (&error);
00291     }
00292   else
00293     {
00294       dbus_upower_call_suspend_allowed_sync (mgr->upower_proxy,
00295                                              &mgr->allow_suspend,
00296                                              NULL,
00297                                              &error);
00298       if (error != NULL)
00299         {
00300           g_warning ("%s: %s", G_STRFUNC, error->message);
00301           g_clear_error (&error);
00302         }
00303 
00304       dbus_upower_call_hibernate_allowed_sync (mgr->upower_proxy,
00305                                                &mgr->allow_hibernate,
00306                                                NULL,
00307                                                &error);
00308       if (error != NULL)
00309         {
00310           g_warning ("%s: %s", G_STRFUNC, error->message);
00311           g_clear_error (&error);
00312         }
00313 
00314       on_upower_properties_changed (mgr);
00315       g_signal_connect_swapped (mgr->upower_proxy, "changed",
00316                                 G_CALLBACK(on_upower_properties_changed), mgr);
00317     }
00318 }
00319 
00320 /***
00321 ****  Menuitem Helpers
00322 ***/
00323 
00324 static inline void
00325 mi_set_label (DbusmenuMenuitem * mi, const char * str)
00326 {
00327   dbusmenu_menuitem_property_set (mi, DBUSMENU_MENUITEM_PROP_LABEL, str);
00328 }
00329 
00330 static inline void
00331 mi_set_type (DbusmenuMenuitem * mi, const char * str)
00332 {
00333   dbusmenu_menuitem_property_set (mi, DBUSMENU_MENUITEM_PROP_TYPE, str);
00334 }
00335 
00336 static inline void
00337 mi_set_visible (DbusmenuMenuitem * mi, gboolean b)
00338 {
00339   dbusmenu_menuitem_property_set_bool (mi, DBUSMENU_MENUITEM_PROP_VISIBLE,
00340                                        b || DEBUG_SHOW_ALL);
00341 }
00342 
00343 static inline void
00344 mi_set_logged_in (DbusmenuMenuitem * mi, gboolean b)
00345 {
00346   dbusmenu_menuitem_property_set_bool (mi, USER_ITEM_PROP_LOGGED_IN, b);
00347 }
00348 
00349 static DbusmenuMenuitem*
00350 mi_new_separator (void)
00351 {
00352   DbusmenuMenuitem * mi = dbusmenu_menuitem_new ();
00353   mi_set_type (mi, DBUSMENU_CLIENT_TYPES_SEPARATOR);
00354   return mi;
00355 }
00356 
00357 static DbusmenuMenuitem*
00358 mi_new (const char * label)
00359 {
00360   DbusmenuMenuitem * mi = dbusmenu_menuitem_new ();
00361   mi_set_label (mi, label);
00362   return mi;
00363 }
00364 
00365 /***
00366 ****  Admin Menuitems
00367 ****  <https://wiki.ubuntu.com/SystemMenu#Admin_items>
00368 ***/
00369 
00370 static void
00371 build_admin_menuitems (SessionMenuMgr * mgr)
00372 {
00373   if (!mgr->greeter_mode)
00374   {
00375     DbusmenuMenuitem * mi;
00376     const gboolean show_settings = !mgr->greeter_mode;
00377 
00378     mi = mi_new (_("About This Computer"));
00379     dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00380     g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00381                               G_CALLBACK(action_func_spawn_async), CMD_INFO);
00382 
00383     mi = mi_new (_("Ubuntu Help"));
00384     dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00385     g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00386                               G_CALLBACK(action_func_spawn_async), CMD_HELP);
00387 
00388     mi = mi_new_separator ();
00389     mi_set_visible (mi, show_settings);
00390     dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00391 
00392     mi = mi_new (_("System Settings\342\200\246"));
00393     mi_set_visible (mi, show_settings);
00394     dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00395     g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00396                               G_CALLBACK(action_func_spawn_async),
00397                               CMD_SYSTEM_SETTINGS);
00398 
00399     mi = mi_new_separator ();
00400     dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00401   }
00402 }
00403 
00404 /***
00405 ****  Session Menuitems
00406 ****  <https://wiki.ubuntu.com/SystemMenu#Session_items>
00407 ***/
00408 
00409 static void
00410 update_session_menuitems (SessionMenuMgr * mgr)
00411 {
00412   gboolean v;
00413   GSettings * s = mgr->indicator_settings;
00414 
00415   v = !mgr->greeter_mode
00416    && !is_this_live_session()
00417    && !g_settings_get_boolean (mgr->lockdown_settings, "disable-log-out")
00418    && !g_settings_get_boolean (s, "suppress-logout-menuitem");
00419   mi_set_visible (mgr->logout_mi, v);
00420 
00421   v = mgr->can_suspend
00422    && mgr->allow_suspend;
00423   mi_set_visible (mgr->suspend_mi, v);
00424 
00425   v = mgr->can_hibernate
00426    && mgr->allow_hibernate;
00427   mi_set_visible (mgr->hibernate_mi, v);
00428 
00429   v = HAVE_RESTART_CMD
00430    && !g_settings_get_boolean (s, "suppress-restart-menuitem");
00431   mi_set_visible (mgr->restart_mi, v);
00432 
00433   v = !g_settings_get_boolean (s, "suppress-shutdown-menuitem");
00434   mi_set_visible (mgr->shutdown_mi, v);
00435 }
00436 
00437 /* Update the ellipses when the confirmation setting changes.
00438  *
00439  * <http://developer.gnome.org/hig-book/3.0/menus-design.html.en>:
00440  * "Label the menu item with a trailing ellipsis ("...") only if the
00441  * command requires further input from the user before it can be performed."
00442  */
00443 static void
00444 update_confirmation_labels (SessionMenuMgr * mgr)
00445 {
00446   const gboolean confirm_needed = !g_settings_get_boolean (
00447                                        mgr->indicator_settings,
00448                                        "suppress-logout-restart-shutdown");
00449 
00450   mi_set_label (mgr->logout_mi, confirm_needed ? _("Log Out\342\200\246")
00451                                                : _("Log Out"));
00452 
00453   mi_set_label (mgr->shutdown_mi, confirm_needed ? _("Switch Off\342\200\246")
00454                                                  : _("Switch Off"));
00455 
00456   dbusmenu_menuitem_property_set (mgr->restart_mi, RESTART_ITEM_LABEL,
00457                                   confirm_needed ? _("Restart\342\200\246")
00458                                                  : _("Restart"));
00459 }
00460 
00461 static void
00462 build_session_menuitems (SessionMenuMgr* mgr)
00463 {
00464   DbusmenuMenuitem * mi;
00465 
00466   mi = mgr->logout_mi = mi_new (_("Log Out\342\200\246"));
00467   dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00468   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00469                             G_CALLBACK(action_func_spawn_async), CMD_LOGOUT);
00470 
00471   mi = mgr->suspend_mi = mi_new ("Suspend");
00472   dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00473   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00474                             G_CALLBACK(action_func_suspend), mgr);
00475 
00476   mi = mgr->hibernate_mi = mi_new (_("Hibernate"));
00477   dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00478   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00479                             G_CALLBACK(action_func_hibernate), mgr);
00480 
00481   mi = mgr->restart_mi = dbusmenu_menuitem_new ();
00482   mi_set_type (mi, RESTART_ITEM_TYPE);
00483   dbusmenu_menuitem_property_set (mi, RESTART_ITEM_LABEL, _("Restart\342\200\246"));
00484   dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00485   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00486                             G_CALLBACK(action_func_spawn_async), CMD_RESTART);
00487 
00488   mi = mgr->shutdown_mi = mi_new (_("Switch Off\342\200\246"));
00489   dbusmenu_menuitem_child_append (mgr->top_mi, mi);
00490   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00491                             G_CALLBACK(action_func_spawn_async), CMD_SHUTDOWN);
00492 
00493   update_confirmation_labels (mgr);
00494   update_session_menuitems (mgr);
00495 }
00496 
00497 /****
00498 *****  User Menuitems
00499 *****  https://wiki.ubuntu.com/SystemMenu#Account-switching_items
00500 ****/
00501 
00502 /* Local extensions to AccountsUser */
00503 
00504 static GQuark
00505 get_menuitem_quark (void)
00506 {
00507   static GQuark q = 0;
00508 
00509   if (G_UNLIKELY(!q))
00510     {
00511       q = g_quark_from_static_string ("menuitem");
00512     }
00513 
00514   return q;
00515 }
00516 
00517 static DbusmenuMenuitem*
00518 user_get_menuitem (AccountsUser * user)
00519 {
00520   return g_object_get_qdata (G_OBJECT(user), get_menuitem_quark());
00521 }
00522 
00523 static void
00524 user_clear_menuitem (AccountsUser * user)
00525 {
00526   g_object_steal_qdata (G_OBJECT(user), get_menuitem_quark());
00527 }
00528 
00529 static void
00530 user_set_menuitem (AccountsUser * user, DbusmenuMenuitem * mi)
00531 {
00532   g_object_set_qdata (G_OBJECT(user), get_menuitem_quark(), mi);
00533 
00534   g_object_weak_ref (G_OBJECT(mi), (GWeakNotify)user_clear_menuitem, user);
00535 }
00536 
00537 /***/
00538 
00539 static GQuark
00540 get_mgr_quark (void)
00541 {
00542   static GQuark q = 0;
00543 
00544   if (G_UNLIKELY(!q))
00545     {
00546       q = g_quark_from_static_string ("session-menu-mgr");
00547     }
00548 
00549   return q;
00550 }
00551 
00552 static SessionMenuMgr*
00553 user_get_mgr (AccountsUser * user)
00554 {
00555   return g_object_get_qdata (G_OBJECT(user), get_mgr_quark());
00556 }
00557 
00558 static void
00559 user_set_mgr (AccountsUser * user, SessionMenuMgr * mgr)
00560 {
00561   g_object_set_qdata (G_OBJECT(user), get_mgr_quark(), mgr);
00562 }
00563 
00564 /***/
00565 
00566 static GQuark
00567 get_collision_quark (void)
00568 {
00569   static GQuark q = 0;
00570 
00571   if (G_UNLIKELY(!q))
00572     {
00573       q = g_quark_from_static_string ("name-collision");
00574     }
00575 
00576   return q;
00577 }
00578 
00579 static gboolean
00580 user_has_name_collision (AccountsUser * u)
00581 {
00582   return g_object_get_qdata (G_OBJECT(u), get_collision_quark()) != NULL;
00583 }
00584 
00585 static void
00586 user_set_name_collision (AccountsUser * u, gboolean b)
00587 {
00588   g_object_set_qdata (G_OBJECT(u), get_collision_quark(), GINT_TO_POINTER(b));
00589 }
00590 
00591 /***
00592 ****
00593 ***/
00594 
00595 static void
00596 on_guest_logged_in_changed (UsersServiceDbus * usd,
00597                             SessionMenuMgr   * mgr)
00598 {
00599   if (mgr->guest_mi != NULL)
00600     {
00601       mi_set_logged_in (mgr->guest_mi,
00602                         users_service_dbus_is_guest_logged_in (usd));
00603     }
00604 }
00605 
00606 /* When a user's login state changes,
00607    update the corresponding menuitem's LOGGED_IN property */
00608 static void
00609 on_user_logged_in_changed (UsersServiceDbus  * usd,
00610                            AccountsUser      * user,
00611                            SessionMenuMgr    * mgr)
00612 {
00613   DbusmenuMenuitem * mi = user_get_menuitem (user);
00614 
00615   if (mi != NULL)
00616     {
00617       mi_set_logged_in (mi, users_service_dbus_is_user_logged_in (usd, user));
00618     }
00619 }
00620 
00621 static void
00622 update_screensaver_shortcut (SessionMenuMgr * mgr)
00623 {
00624   gchar * s = g_settings_get_string (mgr->keybinding_settings, "screensaver");
00625   g_debug ("%s Screensaver shortcut changed to: '%s'", G_STRLOC, s);
00626 
00627   if (mgr->lock_mi != NULL)
00628     {
00629       dbusmenu_menuitem_property_set_shortcut_string (mgr->lock_mi, s);
00630     }
00631 
00632   if (mgr->lock_switch_mi != NULL)
00633     {
00634       dbusmenu_menuitem_property_set_shortcut_string (mgr->lock_switch_mi, s);
00635     }
00636 
00637   if (mgr->screensaver_mi != NULL)
00638     {
00639       dbusmenu_menuitem_property_set_shortcut_string (mgr->screensaver_mi, s);
00640     }
00641 
00642   g_free (s);
00643 }
00644 
00645 static void
00646 update_user_menuitem_icon (DbusmenuMenuitem * mi, AccountsUser * user)
00647 {
00648   const gchar * str = accounts_user_get_icon_file (user);
00649 
00650   if (!str || !*str)
00651     {
00652       str = USER_ITEM_ICON_DEFAULT;
00653     }
00654 
00655   dbusmenu_menuitem_property_set (mi, USER_ITEM_PROP_ICON, str);
00656 }
00657 
00658 static void
00659 update_user_menuitem_name (DbusmenuMenuitem * mi, AccountsUser * user)
00660 {
00661   GString * gstr = g_string_new (accounts_user_get_real_name (user));
00662 
00663   if (user_has_name_collision (user))
00664     {
00665       g_string_append_printf (gstr, " (%s)", accounts_user_get_user_name(user));
00666     }
00667 
00668   dbusmenu_menuitem_property_set (mi, USER_ITEM_PROP_NAME, gstr->str);
00669   g_string_free (gstr, TRUE);
00670 }
00671 
00672 static void
00673 on_user_property_changed (AccountsUser      * user,
00674                           GParamSpec        * pspec,
00675                           DbusmenuMenuitem  * mi)
00676 {
00677   static const char * interned_icon_file = NULL;
00678   static const char * interned_real_name = NULL;
00679   static const char * interned_user_name = NULL;
00680 
00681   if (G_UNLIKELY (interned_icon_file == NULL))
00682     {
00683       interned_icon_file = g_intern_static_string ("icon-file");
00684       interned_user_name = g_intern_static_string ("user-name");
00685       interned_real_name = g_intern_static_string ("real-name");
00686     }
00687 
00688   if (pspec->name == interned_icon_file)
00689     {
00690       update_user_menuitem_icon (mi, user);
00691     }
00692   else if ((pspec->name == interned_real_name)
00693         || (pspec->name == interned_user_name))
00694     {
00695       /* name changing can affect other menuitems too by invalidating
00696          the sort order or name collision flags... so let's rebuild */
00697       update_user_menuitems (user_get_mgr (user));
00698     }
00699 }
00700 
00701 typedef struct
00702 {
00703   gpointer instance;
00704   gulong handler_id;
00705 }
00706 SignalHandlerData;
00707 
00708 /* when a user menuitem is destroyed,
00709    it should stop listening for its UserAccount's property changes */
00710 static void
00711 on_user_menuitem_destroyed (SignalHandlerData * data)
00712 {
00713   g_signal_handler_disconnect (data->instance, data->handler_id);
00714   g_free (data);
00715 }
00716 
00717 static DbusmenuMenuitem*
00718 user_menuitem_new (AccountsUser * user, SessionMenuMgr * mgr)
00719 {
00720   DbusmenuMenuitem * mi = dbusmenu_menuitem_new ();
00721   mi_set_type (mi, USER_ITEM_TYPE);
00722 
00723   /* set the name & icon and listen for property changes */
00724   update_user_menuitem_name (mi, user);
00725   update_user_menuitem_icon (mi, user);
00726   SignalHandlerData * hd = g_new0 (SignalHandlerData, 1);
00727   hd->instance = user;
00728   hd->handler_id = g_signal_connect (user, "notify",
00729                                      G_CALLBACK(on_user_property_changed), mi);
00730   g_object_weak_ref (G_OBJECT(mi), (GWeakNotify)on_user_menuitem_destroyed, hd);
00731 
00732   /* set the logged-in property */
00733   mi_set_logged_in (mi,
00734          users_service_dbus_is_user_logged_in (mgr->users_dbus_facade, user));
00735 
00736   /* set the is-current-user property */
00737   const gboolean is_current_user =
00738               !g_strcmp0 (g_get_user_name(), accounts_user_get_user_name(user));
00739   dbusmenu_menuitem_property_set_bool (mi,
00740                                        USER_ITEM_PROP_IS_CURRENT_USER,
00741                                        is_current_user);
00742 
00743   /* set the switch-to-user action */
00744   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00745                             G_CALLBACK (action_func_switch_to_user), user);
00746 
00747   /* give this AccountsUser a hook back to this menuitem */
00748   user_set_menuitem (user, mi);
00749   user_set_mgr (user, mgr);
00750 
00751   return mi;
00752 }
00753 
00754 /* for sorting AccountsUsers from most to least frequently used */
00755 static gint
00756 compare_users_by_login_frequency (gconstpointer a, gconstpointer b)
00757 {
00758   const guint64 a_freq = accounts_user_get_login_frequency (ACCOUNTS_USER(a));
00759   const guint64 b_freq = accounts_user_get_login_frequency (ACCOUNTS_USER(b));
00760   if (a_freq > b_freq) return -1;
00761   if (a_freq < b_freq) return  1;
00762   return 0;
00763 }
00764 
00765 /* for sorting AccountsUsers alphabetically */
00766 static gint
00767 compare_users_by_username (gconstpointer ga, gconstpointer gb)
00768 {
00769   AccountsUser * a = ACCOUNTS_USER(ga);
00770   AccountsUser * b = ACCOUNTS_USER(gb);
00771 
00772   const int ret = g_strcmp0 (accounts_user_get_real_name (a),
00773                              accounts_user_get_real_name (b));
00774 
00775   if (!ret) /* names are the same, so both have a name collision */
00776     {
00777       user_set_name_collision (a, TRUE);
00778       user_set_name_collision (b, TRUE);
00779     }
00780 
00781   return ret;
00782 }
00783 
00784 static gboolean
00785 is_user_switching_allowed (SessionMenuMgr * mgr)
00786 {
00787   /* maybe it's locked down */
00788   if (g_settings_get_boolean (mgr->lockdown_settings, "disable-user-switching"))
00789     {
00790       return FALSE;
00791     }
00792 
00793   /* maybe the seat doesn't support activation */
00794   if (!users_service_dbus_can_activate_session (mgr->users_dbus_facade))
00795     {
00796       return FALSE;
00797     }
00798 
00799   return TRUE;
00800 }
00801 
00802 static void
00803 build_user_menuitems (SessionMenuMgr * mgr)
00804 {
00805   g_return_if_fail (!mgr->greeter_mode);
00806 
00807   DbusmenuMenuitem * mi;
00808   GSList * items = NULL;
00809   gint pos = mgr->user_menuitem_index;
00810   const char * current_real_name = NULL;
00811 
00819   const SwitcherMode mode = get_switcher_mode (mgr);
00820 
00821   mi = mgr->screensaver_mi = mi_new (_("Start Screen Saver"));
00822   mi_set_visible (mi, mode == SWITCHER_MODE_SCREENSAVER);
00823   dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
00824   items = g_slist_prepend (items, mi);
00825   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00826                             G_CALLBACK (action_func_lock), mgr);
00827 
00828   mi = mi_new (_("Switch User Account\342\200\246"));
00829   mi_set_visible (mi, mode == SWITCHER_MODE_SWITCH);
00830   dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
00831   items = g_slist_prepend (items, mi);
00832   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00833                             G_CALLBACK (action_func_switch_to_greeter), mgr);
00834 
00835   mi = mgr->lock_mi = mi_new (_("Lock"));
00836   mi_set_visible (mi, mode == SWITCHER_MODE_LOCK);
00837   dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
00838   items = g_slist_prepend (items, mi);
00839   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00840                             G_CALLBACK (action_func_switch_to_lockscreen), mgr);
00841 
00842   mi = mgr->lock_switch_mi = mi_new (_("Lock/Switch Account\342\200\246"));
00843   mi_set_visible (mi, mode == SWITCHER_MODE_SWITCH_OR_LOCK);
00844   dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
00845   items = g_slist_prepend (items, mi);
00846   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00847                             G_CALLBACK (action_func_switch_to_lockscreen), mgr);
00848 
00849   const gboolean is_guest = is_this_guest_session ();
00850   const gboolean guest_allowed =
00851               users_service_dbus_guest_session_enabled (mgr->users_dbus_facade);
00852   mi = mgr->guest_mi = dbusmenu_menuitem_new ();
00853   mi_set_type (mi, USER_ITEM_TYPE);
00854   mi_set_visible (mi, !is_guest && guest_allowed);
00855   dbusmenu_menuitem_property_set (mi, USER_ITEM_PROP_NAME, _("Guest Session"));
00856   dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
00857   on_guest_logged_in_changed (mgr->users_dbus_facade, mgr);
00858   items = g_slist_prepend (items, mi);
00859   g_signal_connect_swapped (mi, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00860                             G_CALLBACK (action_func_switch_to_guest), mgr);
00861 
00862   if (guest_allowed && is_guest)
00863     {
00864       current_real_name = _("Guest");
00865     }
00866 
00867   /***
00868   ****  Users
00869   ***/
00870 
00871   /* if we can switch to another user account, show them here */
00872   const char * const username = g_get_user_name();
00873   GList * users = users_service_dbus_get_user_list (mgr->users_dbus_facade);
00874 
00875   /* since we're building (or rebuilding) from scratch,
00876      clear the name collision flags */
00877   GList * u;
00878   for (u=users; u!=NULL; u=u->next)
00879     {
00880       AccountsUser * user = ACCOUNTS_USER(u->data);
00881 
00882       user_set_name_collision (user, FALSE);
00883 
00884       if (!g_strcmp0 (username, accounts_user_get_user_name(user)))
00885         {
00886           current_real_name = accounts_user_get_real_name (user);
00887         }
00888     }
00889 
00890   if (is_user_switching_allowed (mgr))
00891     {
00892       /* pick the most frequently used accounts */
00893       const int MAX_USERS = 12; /* this limit comes from the spec */
00894       if (g_list_length(users) > MAX_USERS)
00895         {
00896           users = g_list_sort (users, compare_users_by_login_frequency);
00897           GList * last = g_list_nth (users, MAX_USERS-1);
00898           GList * remainder = last->next;
00899           last->next = NULL;
00900           remainder->prev = NULL;
00901           g_list_free (remainder);
00902         }
00903 
00904       /* Sort the users by name for display */
00905       users = g_list_sort (users, compare_users_by_username);
00906 
00907       /* Create menuitems for them */
00908       int i;
00909       for (i=0, u=users; i<MAX_USERS && u!=NULL; u=u->next, i++)
00910         {
00911           AccountsUser * user = u->data;
00912           DbusmenuMenuitem * mi = user_menuitem_new (user, mgr);
00913           dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
00914           items = g_slist_prepend (items, mi);
00915         }
00916     }
00917 
00918   g_list_free (users);
00919 
00920   /* separator */
00921   mi = mi_new_separator ();
00922   dbusmenu_menuitem_child_add_position (mgr->top_mi, mi, pos++);
00923   items = g_slist_prepend (items, mi);
00924 
00925   if (current_real_name != NULL)
00926     {
00927       session_dbus_set_users_real_name (mgr->session_dbus,
00928                                         current_real_name);
00929     }
00930 
00931   update_screensaver_shortcut (mgr);
00932   mgr->user_menuitems = items;
00933 }
00934 
00935 static void
00936 update_user_menuitems (SessionMenuMgr * mgr)
00937 {
00938   /* remove any previous user menuitems */
00939   GSList * l;
00940   for (l=mgr->user_menuitems; l!=NULL; l=l->next)
00941     {
00942       dbusmenu_menuitem_child_delete (mgr->top_mi, l->data);
00943     }
00944   g_slist_free (mgr->user_menuitems);
00945   mgr->user_menuitems = NULL;
00946 
00947   /* add fresh user menuitems */
00948   if (!mgr->greeter_mode)
00949     {
00950       build_user_menuitems (mgr);
00951     }
00952 }
00953 
00954 /***
00955 ****  Actions!
00956 ***/
00957 
00958 static void
00959 action_func_spawn_async (const char * cmd)
00960 {
00961   GError * error = NULL;
00962 
00963   g_debug ("%s calling \"%s\"", G_STRFUNC, cmd);
00964   g_spawn_command_line_async (cmd, &error);
00965 
00966   if (error != NULL)
00967     {
00968       g_warning ("Unable to execute \"%s\": %s", cmd, error->message);
00969       g_clear_error (&error);
00970     }
00971 }
00972 
00973 /* Calling "Lock" locks the screen & goes to black.
00974    Calling "SimulateUserActivity" afterwards shows the Lock Screen. */
00975 static void
00976 lock_helper (SessionMenuMgr * mgr, gboolean show_lock_screen)
00977 {
00978   if (!g_settings_get_boolean (mgr->lockdown_settings, "disable-lock-screen"))
00979     {
00980       GError * error = NULL;
00981       GDBusProxy * proxy = g_dbus_proxy_new_for_bus_sync (
00982                              G_BUS_TYPE_SESSION,
00983                              G_DBUS_PROXY_FLAGS_NONE,
00984                              NULL,
00985                              "org.gnome.ScreenSaver",
00986                              "/org/gnome/ScreenSaver",
00987                              "org.gnome.ScreenSaver",
00988                              NULL,
00989                              &error);
00990 
00991       if (error == NULL)
00992         {
00993           g_dbus_proxy_call_sync (proxy, "Lock",
00994                                   NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL,
00995                                   &error);
00996         }
00997 
00998       if ((error == NULL) && show_lock_screen)
00999         {
01000           g_dbus_proxy_call_sync (proxy, "SimulateUserActivity",
01001                                   NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL,
01002                                   &error);
01003         }
01004 
01005       if (error != NULL)
01006         {
01007           g_warning ("Error locking screen: %s", error->message);
01008         }
01009 
01010       g_clear_object (&proxy);
01011       g_clear_error (&error);
01012     }
01013 }
01014 
01015 static void
01016 action_func_lock (SessionMenuMgr * mgr)
01017 {
01018   lock_helper (mgr, FALSE);
01019 }
01020 
01021 static void
01022 action_func_switch_to_lockscreen (SessionMenuMgr * mgr)
01023 {
01024   lock_helper (mgr, TRUE);
01025 }
01026 
01027 static void
01028 action_func_switch_to_greeter (SessionMenuMgr * mgr)
01029 {
01030   action_func_lock (mgr);
01031   users_service_dbus_show_greeter (mgr->users_dbus_facade);
01032 }
01033 
01034 static void
01035 action_func_switch_to_user (AccountsUser * user)
01036 {
01037   SessionMenuMgr * mgr = user_get_mgr (user);
01038   g_return_if_fail (mgr != NULL);
01039   action_func_lock (mgr);
01040   users_service_dbus_activate_user_session (mgr->users_dbus_facade, user);
01041 }
01042 
01043 static void
01044 action_func_switch_to_guest (SessionMenuMgr * mgr)
01045 {
01046   action_func_lock (mgr);
01047   users_service_dbus_activate_guest_session (mgr->users_dbus_facade);
01048 }
01049 
01050 static void
01051 action_func_suspend (SessionMenuMgr * mgr)
01052 {
01053   GError * error = NULL;
01054 
01055   dbus_upower_call_suspend_sync (mgr->upower_proxy,
01056                                  mgr->cancellable,
01057                                  &error);
01058 
01059   if (error != NULL)
01060     {
01061       g_warning ("%s: %s", G_STRFUNC, error->message);
01062       g_clear_error (&error);
01063     }
01064 }
01065 
01066 static void
01067 action_func_hibernate (SessionMenuMgr * mgr)
01068 {
01069   GError * error = NULL;
01070 
01071   dbus_upower_call_hibernate_sync (mgr->upower_proxy,
01072                                    mgr->cancellable,
01073                                    &error);
01074 
01075   if (error != NULL)
01076     {
01077       g_warning ("%s: %s", G_STRFUNC, error->message);
01078       g_clear_error (&error);
01079     }
01080 }
01081 
01082 /***
01083 ****
01084 ***/
01085 
01086 static gboolean
01087 is_this_guest_session (void)
01088 {
01089   /* FIXME: this test has been here awhile and seems to work,
01090      but seems brittle to me */
01091   return geteuid() < 500;
01092 }
01093 
01094 static gboolean
01095 is_this_live_session (void)
01096 {
01097   const struct passwd * const pw = getpwuid (geteuid());
01098   return (pw->pw_uid==999) && !g_strcmp0("ubuntu",pw->pw_name);
01099 }
01100 
01101 static SwitcherMode
01102 get_switcher_mode (SessionMenuMgr * mgr)
01103 {
01104   SwitcherMode mode;
01105 
01106   const gboolean can_lock = !g_settings_get_boolean (mgr->lockdown_settings,
01107                                                      "disable-lock-screen");
01108   const gboolean can_switch = is_user_switching_allowed (mgr);
01109 
01110   if (!can_lock && !can_switch) /* hmm, quite an extreme lockdown */
01111     {
01112       mode = SWITCHER_MODE_SCREENSAVER;
01113     }
01114   else if (is_this_live_session()) /* live sessions can't lock or switch */
01115     {
01116       mode = SWITCHER_MODE_SCREENSAVER;
01117     }
01118   else if (!can_switch) /* switching's locked down */
01119     {
01120       mode = SWITCHER_MODE_LOCK;
01121     }
01122   else if (is_this_guest_session ()) /* guest sessions can't lock */
01123     {
01124       mode = SWITCHER_MODE_SWITCH;
01125     }
01126   else /* both locking & switching are allowed */
01127     {
01128       GList * l = users_service_dbus_get_user_list (mgr->users_dbus_facade);
01129       const size_t user_count = g_list_length (l);
01130       g_list_free (l);
01131 
01132       /* only show switch mode if we have users to switch to */
01133       mode = user_count > (is_this_guest_session() ? 0 : 1)
01134            ? SWITCHER_MODE_SWITCH_OR_LOCK
01135            : SWITCHER_MODE_LOCK;
01136     }
01137 
01138   return mode;
01139 }
01140 
01141 
01142 /***
01143 ****
01144 ***/
01145 
01146 SessionMenuMgr*
01147 session_menu_mgr_new (SessionDbus  * session_dbus,
01148                       gboolean       greeter_mode)
01149 {
01150   SessionMenuMgr* mgr = g_object_new (SESSION_TYPE_MENU_MGR, NULL);
01151   mgr->greeter_mode = greeter_mode;
01152   mgr->session_dbus = g_object_ref (session_dbus);
01153   build_admin_menuitems (mgr);
01154   const guint n = g_list_length (dbusmenu_menuitem_get_children (mgr->top_mi));
01155   mgr->user_menuitem_index = n;
01156   update_user_menuitems (mgr);
01157   build_session_menuitems (mgr);
01158   return mgr;
01159 }
01160 
01166 DbusmenuMenuitem *
01167 session_menu_mgr_get_menu (SessionMenuMgr * mgr)
01168 {
01169   g_return_val_if_fail (IS_SESSION_MENU_MGR(mgr), NULL);
01170 
01171   return mgr->top_mi;
01172 }