/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* Adapted from gdm/gui/user-switch-applet/applet.c */ /* * * Copyright (C) 2004-2005 James M. Cape . * Copyright (C) 2008,2009 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "shell-status-menu.h" #include #include #include #include #include #include #include #include #include #define GDMUSER_I_KNOW_THIS_IS_UNSTABLE #include #include "shell-global.h" #include "shell-gconf.h" #define LOCKDOWN_DIR "/desktop/gnome/lockdown" #define LOCKDOWN_KEY LOCKDOWN_DIR "/disable_user_switching" #define SIDEBAR_VISIBLE_KEY SHELL_GCONF_DIR "/sidebar/visible" struct _ShellStatusMenuPrivate { GConfClient *client; GdmUserManager *manager; GdmUser *user; ClutterTexture *user_icon; BigBox *name_box; ClutterText *name; GtkWidget *menu; GtkWidget *account_item; GtkWidget *sidebar_item; GtkWidget *control_panel_item; GtkWidget *lock_screen_item; GtkWidget *login_screen_item; GtkWidget *quit_session_item; GtkWidget *shut_down_item; guint client_notify_lockdown_id; guint current_status_state; guint user_icon_changed_id; guint user_notify_id; gboolean has_other_users; GtkIconSize icon_size; guint pixel_size; }; enum { PROP_0 }; G_DEFINE_TYPE(ShellStatusMenu, shell_status_menu, BIG_TYPE_BOX); /* Signals */ enum { DEACTIVATED, LAST_SIGNAL }; static guint shell_status_menu_signals [LAST_SIGNAL] = { 0 }; static void reset_icon (ShellStatusMenu *status) { ShellStatusMenuPrivate *priv = status->priv; GdkPixbuf *pixbuf; if (priv->user == NULL) return; if (priv->user_icon != NULL) { pixbuf = gdm_user_render_icon (priv->user, 24); if (pixbuf == NULL) return; shell_clutter_texture_set_from_pixbuf (priv->user_icon, pixbuf); g_object_unref (pixbuf); } } static void update_name_text (ShellStatusMenu *status) { ShellStatusMenuPrivate *priv = status->priv; clutter_text_set_text (priv->name, gdm_user_get_real_name (GDM_USER (priv->user))); } static void on_user_icon_changed (GdmUser *user, ShellStatusMenu *status) { g_debug ("User icon changed"); reset_icon (status); } static void user_notify_display_name_cb (GObject *object, GParamSpec *pspec, ShellStatusMenu *status) { update_name_text (status); } static void setup_current_user (ShellStatusMenu *status) { ShellStatusMenuPrivate *priv = status->priv; const char *name; priv->user = gdm_user_manager_get_user_by_uid (priv->manager, getuid ()); if (priv->user != NULL) { g_object_ref (priv->user); name = gdm_user_get_real_name (priv->user); } else { name = _("Unknown"); } update_name_text (status); if (priv->user != NULL) { reset_icon (status); priv->user_icon_changed_id = g_signal_connect (priv->user, "icon-changed", G_CALLBACK (on_user_icon_changed), status); priv->user_notify_id = g_signal_connect (priv->user, "notify::display-name", G_CALLBACK (user_notify_display_name_cb), status); } } static void maybe_lock_screen (ShellStatusMenu *status) { char *args[3]; GError *err; GdkScreen *screen; gboolean use_gscreensaver = TRUE; gboolean res; g_debug ("Attempting to lock screen"); args[0] = g_find_program_in_path ("gnome-screensaver-command"); if (args[0] == NULL) { args[0] = g_find_program_in_path ("xscreensaver-command"); use_gscreensaver = FALSE; } if (args[0] == NULL) return; if (use_gscreensaver) args[1] = "--lock"; else args[1] = "-lock"; args[2] = NULL; screen = gdk_screen_get_default (); err = NULL; res = gdk_spawn_on_screen (screen, g_get_home_dir (), args, NULL, 0, NULL, NULL, NULL, &err); if (!res) { g_warning (_("Can't lock screen: %s"), err->message); g_error_free (err); } if (use_gscreensaver) args[1] = "--throttle"; else args[1] = "-throttle"; err = NULL; res = gdk_spawn_on_screen (screen, g_get_home_dir (), args, NULL, (G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_STDOUT_TO_DEV_NULL), NULL, NULL, NULL, &err); if (!res) { g_warning (_("Can't temporarily set screensaver to blank screen: %s"), err->message); g_error_free (err); } g_free (args[0]); } static void on_lock_screen_activate (GtkMenuItem *item, ShellStatusMenu *status) { maybe_lock_screen (status); } static void do_switch (ShellStatusMenu *status, GdmUser *user) { ShellStatusMenuPrivate *priv = status->priv; guint num_sessions; g_debug ("Do user switch"); if (user == NULL) { gdm_user_manager_goto_login_session (priv->manager); goto out; } num_sessions = gdm_user_get_num_sessions (user); if (num_sessions > 0) gdm_user_manager_activate_user_session (priv->manager, user); else gdm_user_manager_goto_login_session (priv->manager); out: maybe_lock_screen (status); } static void on_login_screen_activate (GtkMenuItem *item, ShellStatusMenu *status) { GdmUser *user; user = NULL; do_switch (status, user); } static void spawn_external (ShellStatusMenu *status, const char *program) { char *args[2]; GError *error; GdkScreen *screen; gboolean res; args[0] = g_find_program_in_path (program); if (args[0] == NULL) return; args[1] = NULL; screen = gdk_screen_get_default (); error = NULL; res = gdk_spawn_on_screen (screen, g_get_home_dir (), args, NULL, 0, NULL, NULL, NULL, &error); if (!res) { g_warning ("Failed to exec %s: %s", program, error->message); g_clear_error (&error); } g_free (args[0]); } static void on_control_panel_activate (GtkMenuItem *item, ShellStatusMenu *status) { spawn_external (status, "gnome-control-center"); } static void on_account_activate (GtkMenuItem *item, ShellStatusMenu *status) { spawn_external (status, "gnome-about-me"); } static void on_sidebar_toggled (GtkCheckMenuItem *item, ShellStatusMenu *status) { gconf_client_set_bool (status->priv->client, SIDEBAR_VISIBLE_KEY, gtk_check_menu_item_get_active (item), NULL); } /* Calls 'gnome-session-save arg' */ static void gnome_session_save_command (const char *arg) { char *args[3]; GError *error; GdkScreen *screen; gboolean res; args[0] = g_find_program_in_path ("gnome-session-save"); if (args[0] == NULL) return; args[1] = (char *)arg; args[2] = NULL; screen = gdk_screen_get_default (); error = NULL; res = gdk_spawn_on_screen (screen, g_get_home_dir (), args, NULL, 0, NULL, NULL, NULL, &error); if (!res) { g_warning (_("Can't logout: %s"), error->message); g_error_free (error); } g_free (args[0]); } static void on_quit_session_activate (GtkMenuItem *item, ShellStatusMenu *status) { gnome_session_save_command ("--logout-dialog"); } static void on_shut_down_activate (GtkMenuItem *item, ShellStatusMenu *status) { gnome_session_save_command ("--shutdown-dialog"); } static void update_switch_user (ShellStatusMenu *status) { ShellStatusMenuPrivate *priv = status->priv; GSList *users; users = gdm_user_manager_list_users (priv->manager); priv->has_other_users = FALSE; if (users != NULL) priv->has_other_users = g_slist_length (users) > 1; g_slist_free (users); if (priv->has_other_users) gtk_widget_show (priv->login_screen_item); else gtk_widget_hide (priv->login_screen_item); } static void on_manager_user_added (GdmUserManager *manager, GdmUser *user, ShellStatusMenu *status) { update_switch_user (status); } static void on_manager_user_removed (GdmUserManager *manager, GdmUser *user, ShellStatusMenu *status) { update_switch_user (status); } static void on_manager_users_loaded (GdmUserManager *manager, ShellStatusMenu *status) { update_switch_user (status); } static void menu_style_set_cb (GtkWidget *menu, GtkStyle *old_style, ShellStatusMenu *status) { ShellStatusMenuPrivate *priv = status->priv; GtkSettings *settings; int width; int height; priv->icon_size = gtk_icon_size_from_name ("panel-menu"); if (priv->icon_size == GTK_ICON_SIZE_INVALID) priv->icon_size = gtk_icon_size_register ("panel-menu", 24, 24); if (gtk_widget_has_screen (menu)) settings = gtk_settings_get_for_screen (gtk_widget_get_screen (menu)); else settings = gtk_settings_get_default (); if (!gtk_icon_size_lookup_for_settings (settings, priv->icon_size, &width, &height)) priv->pixel_size = -1; else priv->pixel_size = MAX(width, height); } static void menuitem_style_set_cb (GtkWidget *menuitem, GtkStyle *old_style, ShellStatusMenu *status) { GtkWidget *image; const char *icon_name; ShellStatusMenuPrivate *priv = status->priv; if (menuitem == priv->login_screen_item) icon_name = "system-users"; else if (menuitem == priv->lock_screen_item) icon_name = "system-lock-screen"; else if (menuitem == priv->quit_session_item) icon_name = "system-log-out"; else if (menuitem == priv->account_item) icon_name = "user-info"; else if (menuitem == priv->control_panel_item) icon_name = "preferences-desktop"; else if (menuitem == priv->shut_down_item) icon_name = "system-shutdown"; else icon_name = GTK_STOCK_MISSING_IMAGE; image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (menuitem)); gtk_image_set_pixel_size (GTK_IMAGE (image), priv->pixel_size); gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name, priv->icon_size); } static void on_deactivate (GtkMenuShell *menushell, gpointer user_data) { ShellStatusMenu *status = SHELL_STATUS_MENU (user_data); g_signal_emit (G_OBJECT (status), shell_status_menu_signals[DEACTIVATED], 0); } static void create_sub_menu (ShellStatusMenu *status) { ShellStatusMenuPrivate *priv = status->priv; GtkWidget *item; priv->menu = gtk_menu_new (); g_signal_connect (priv->menu, "style-set", G_CALLBACK (menu_style_set_cb), status); g_signal_connect (priv->manager, "users-loaded", G_CALLBACK (on_manager_users_loaded), status); g_signal_connect (priv->manager, "user-added", G_CALLBACK (on_manager_user_added), status); g_signal_connect (priv->manager, "user-removed", G_CALLBACK (on_manager_user_removed), status); priv->account_item = gtk_image_menu_item_new_with_label (_("Account Information...")); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->account_item), gtk_image_new ()); gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->account_item); g_signal_connect (priv->account_item, "style-set", G_CALLBACK (menuitem_style_set_cb), status); g_signal_connect (priv->account_item, "activate", G_CALLBACK (on_account_activate), status); gtk_widget_show (priv->account_item); priv->sidebar_item = gtk_check_menu_item_new_with_label (_("Sidebar")); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->sidebar_item), gconf_client_get_bool (priv->client, SIDEBAR_VISIBLE_KEY, NULL)); gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->sidebar_item); g_signal_connect (priv->sidebar_item, "toggled", G_CALLBACK (on_sidebar_toggled), status); gtk_widget_show (priv->sidebar_item); priv->control_panel_item = gtk_image_menu_item_new_with_label (_("System Preferences...")); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->control_panel_item), gtk_image_new ()); gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->control_panel_item); g_signal_connect (priv->control_panel_item, "style-set", G_CALLBACK (menuitem_style_set_cb), status); g_signal_connect (priv->control_panel_item, "activate", G_CALLBACK (on_control_panel_activate), status); gtk_widget_show (priv->control_panel_item); item = gtk_separator_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), item); gtk_widget_show (item); priv->lock_screen_item = gtk_image_menu_item_new_with_label (_("Lock Screen")); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->lock_screen_item), gtk_image_new ()); gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->lock_screen_item); g_signal_connect (priv->lock_screen_item, "style-set", G_CALLBACK (menuitem_style_set_cb), status); g_signal_connect (priv->lock_screen_item, "activate", G_CALLBACK (on_lock_screen_activate), status); gtk_widget_show (priv->lock_screen_item); priv->login_screen_item = gtk_image_menu_item_new_with_label (_("Switch User")); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->login_screen_item), gtk_image_new ()); gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->login_screen_item); g_signal_connect (priv->login_screen_item, "style-set", G_CALLBACK (menuitem_style_set_cb), status); g_signal_connect (priv->login_screen_item, "activate", G_CALLBACK (on_login_screen_activate), status); /* Only show switch user if there are other users */ /* Log Out */ priv->quit_session_item = gtk_image_menu_item_new_with_label (_("Log Out...")); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->quit_session_item), gtk_image_new ()); gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->quit_session_item); g_signal_connect (priv->quit_session_item, "style-set", G_CALLBACK (menuitem_style_set_cb), status); g_signal_connect (priv->quit_session_item, "activate", G_CALLBACK (on_quit_session_activate), status); gtk_widget_show (priv->quit_session_item); /* Shut down */ priv->shut_down_item = gtk_image_menu_item_new_with_label (_("Shut Down...")); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->shut_down_item), gtk_image_new ()); gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->shut_down_item); g_signal_connect (priv->shut_down_item, "style-set", G_CALLBACK (menuitem_style_set_cb), status); g_signal_connect (priv->shut_down_item, "activate", G_CALLBACK (on_shut_down_activate), status); gtk_widget_show (priv->shut_down_item); g_signal_connect (G_OBJECT (priv->menu), "deactivate", G_CALLBACK (on_deactivate), status); } static void shell_status_menu_init (ShellStatusMenu *status) { ShellStatusMenuPrivate *priv; status->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (status, SHELL_TYPE_STATUS_MENU, ShellStatusMenuPrivate); g_object_set (G_OBJECT (status), "orientation", BIG_BOX_ORIENTATION_HORIZONTAL, NULL); priv->client = gconf_client_get_default (); priv->user_icon = CLUTTER_TEXTURE (clutter_texture_new ()); big_box_append (BIG_BOX (status), CLUTTER_ACTOR (status->priv->user_icon), 0); priv->name_box = BIG_BOX (big_box_new (BIG_BOX_ORIENTATION_VERTICAL)); g_object_set (G_OBJECT (priv->name_box), "y-align", BIG_BOX_ALIGNMENT_CENTER, NULL); big_box_append (BIG_BOX (status), CLUTTER_ACTOR (priv->name_box), BIG_BOX_PACK_EXPAND); priv->name = CLUTTER_TEXT (clutter_text_new ()); big_box_append (BIG_BOX (priv->name_box), CLUTTER_ACTOR (priv->name), BIG_BOX_PACK_EXPAND); priv->manager = gdm_user_manager_ref_default (); setup_current_user (status); create_sub_menu (status); } static void shell_status_menu_finalize (GObject *object) { ShellStatusMenu *status = SHELL_STATUS_MENU (object); ShellStatusMenuPrivate *priv = status->priv; gconf_client_notify_remove (priv->client, priv->client_notify_lockdown_id); g_signal_handler_disconnect (priv->user, priv->user_notify_id); g_signal_handler_disconnect (priv->user, priv->user_icon_changed_id); if (priv->user != NULL) { g_object_unref (priv->user); } g_object_unref (priv->client); g_object_unref (priv->manager); G_OBJECT_CLASS (shell_status_menu_parent_class)->finalize (object); } static void shell_status_menu_class_init (ShellStatusMenuClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (ShellStatusMenuPrivate)); gobject_class->finalize = shell_status_menu_finalize; shell_status_menu_signals[DEACTIVATED] = g_signal_new ("deactivated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ShellStatusMenuClass, deactivated), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void position_menu (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data) { ShellStatusMenu *status = SHELL_STATUS_MENU (user_data); ClutterActor *parent; float src_x, src_y; float width, height; int menu_width; gtk_widget_get_size_request (GTK_WIDGET (menu), &menu_width, NULL); /* Encapsulation breakage: it looks better if the menu is * aligned with the bottom of the actor's grandparent - the * panel, rather than with the bottom of the actor. We just * assume what the hierarchy is and where we are positioned * in the panel. */ parent = clutter_actor_get_parent (CLUTTER_ACTOR (status)); parent = clutter_actor_get_parent (parent); clutter_actor_get_transformed_position (parent, &src_x, &src_y); clutter_actor_get_transformed_size (parent, &width, &height); *x = (gint)(0.5 + src_x + width - menu_width); *y = (gint)(0.5 + src_y + height); } /** * shell_status_menu_toggle: * @menu: a #ShellStatusMenu * * If the menu is not currently up, pops it up. Otherwise, hides it. * Popping up may fail if another grab is already active; check with * shell_status_menu_is_active(). */ void shell_status_menu_toggle (ShellStatusMenu *status, ClutterEvent *event) { ShellStatusMenuPrivate *priv = status->priv; if (GTK_WIDGET_VISIBLE (priv->menu)) { gtk_menu_popdown (GTK_MENU (priv->menu)); } else { /* We don't want to overgrab a Mutter grab with the grab that GTK+ * uses on menus. */ ShellGlobal *global = shell_global_get (); if (shell_global_display_is_grabbed (global)) return; gtk_menu_popup (GTK_MENU (priv->menu), NULL, NULL, position_menu, status, 1, event->button.time); } } /** * shell_status_menu_is_active: * @menu: a #ShellStatusMenu * * Gets whether the menu is currently popped up * * Return value: %TRUE if the menu is currently popped up */ gboolean shell_status_menu_is_active (ShellStatusMenu *status) { ShellStatusMenuPrivate *priv = status->priv; return GTK_WIDGET_VISIBLE (priv->menu); } /** * shell_status_menu_get_name: * @menu: a #ShellStatusMenu * * Return value: (transfer none): the #ClutterText actor with the user's name. */ ClutterText * shell_status_menu_get_name (ShellStatusMenu *menu) { return menu->priv->name; } /** * shell_status_menu_get_icon: * @menu: a #ShellStatusMenu * * Return value: (transfer none): the #ClutterTexture actor with the user icon. */ ClutterTexture * shell_status_menu_get_icon (ShellStatusMenu *menu) { return menu->priv->user_icon; }