/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright 2009-2010 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. * * Written by: Matthias Clasen */ #include "config.h" #include "um-user-panel.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_CHEESE #include #endif /* HAVE_CHEESE */ #include "shell/cc-editable-entry.h" #include "um-editable-button.h" #include "um-editable-combo.h" #include "um-account-dialog.h" #include "cc-language-chooser.h" #include "um-password-dialog.h" #include "um-photo-dialog.h" #include "um-fingerprint-dialog.h" #include "um-utils.h" #include "um-resources.h" #include "um-history-dialog.h" #include "cc-common-language.h" #define USER_ACCOUNTS_PERMISSION "com.canonical.controlcenter.user-accounts.administration" #define INDICATOR_SESSION_SCHEMA "com.canonical.indicator.session" CC_PANEL_REGISTER (CcUserPanel, cc_user_panel) #define UM_USER_PANEL_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), UM_TYPE_USER_PANEL, CcUserPanelPrivate)) struct _CcUserPanelPrivate { ActUserManager *um; GtkBuilder *builder; GtkWidget *main_box; GPermission *permission; GtkWidget *language_chooser; UmPasswordDialog *password_dialog; UmPhotoDialog *photo_dialog; UmHistoryDialog *history_dialog; gint other_accounts; GtkTreeIter *other_iter; UmAccountDialog *account_dialog; GSettings *indicator_session_schema; }; static GtkWidget * get_widget (CcUserPanelPrivate *d, const char *name) { return (GtkWidget *)gtk_builder_get_object (d->builder, name); } enum { USER_COL, FACE_COL, NAME_COL, USER_ROW_COL, TITLE_COL, HEADING_ROW_COL, SORT_KEY_COL, AUTOLOGIN_COL, NUM_USER_LIST_COLS }; static ActUser * get_selected_user (CcUserPanelPrivate *d) { GtkTreeView *tv; GtkTreeIter iter; GtkTreeSelection *selection; GtkTreeModel *model; ActUser *user; tv = (GtkTreeView *)get_widget (d, "list-treeview"); selection = gtk_tree_view_get_selection (tv); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gtk_tree_model_get (model, &iter, USER_COL, &user, -1); return user; } return NULL; } static char * get_name_col_str (ActUser *user) { return g_markup_printf_escaped ("%s\n%s", act_user_get_real_name (user), act_user_get_user_name (user)); } static void user_added (ActUserManager *um, ActUser *user, CcUserPanelPrivate *d) { GtkWidget *widget; GtkTreeModel *model; GtkListStore *store; GtkTreeIter iter; GtkTreeIter dummy; GdkPixbuf *pixbuf; gchar *text, *title; GtkTreeSelection *selection; gint sort_key; gboolean is_autologin; if (act_user_is_system_account (user)) { return; } g_debug ("user added: %d %s\n", act_user_get_uid (user), act_user_get_real_name (user)); widget = get_widget (d, "list-treeview"); model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget)); store = GTK_LIST_STORE (model); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); pixbuf = render_user_icon (user, UM_ICON_STYLE_FRAME | UM_ICON_STYLE_STATUS, 48); text = get_name_col_str (user); is_autologin = act_user_get_automatic_login (user); if (act_user_get_uid (user) == getuid ()) { sort_key = 1; } else { d->other_accounts++; sort_key = 3; } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, USER_COL, user, FACE_COL, pixbuf, NAME_COL, text, USER_ROW_COL, TRUE, TITLE_COL, NULL, HEADING_ROW_COL, FALSE, SORT_KEY_COL, sort_key, AUTOLOGIN_COL, is_autologin, -1); g_object_unref (pixbuf); g_free (text); if (sort_key == 1 && !gtk_tree_selection_get_selected (selection, &model, &dummy)) { gtk_tree_selection_select_iter (selection, &iter); } /* Show heading for other accounts if new one have been added. */ if (d->other_accounts == 1 && sort_key == 3) { title = g_strdup_printf ("%s", _("Other Accounts")); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, TITLE_COL, title, HEADING_ROW_COL, TRUE, SORT_KEY_COL, 2, -1); d->other_iter = gtk_tree_iter_copy (&iter); g_free (title); } } static void get_previous_user_row (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *prev) { GtkTreePath *path; ActUser *user; path = gtk_tree_model_get_path (model, iter); while (gtk_tree_path_prev (path)) { gtk_tree_model_get_iter (model, prev, path); gtk_tree_model_get (model, prev, USER_COL, &user, -1); if (user) { g_object_unref (user); break; } } gtk_tree_path_free (path); } static gboolean get_next_user_row (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *next) { ActUser *user; *next = *iter; while (gtk_tree_model_iter_next (model, next)) { gtk_tree_model_get (model, next, USER_COL, &user, -1); if (user) { g_object_unref (user); return TRUE; } } return FALSE; } static void user_removed (ActUserManager *um, ActUser *user, CcUserPanelPrivate *d) { GtkTreeView *tv; GtkTreeModel *model; GtkTreeSelection *selection; GtkListStore *store; GtkTreeIter iter, next; ActUser *u; gint key; g_debug ("user removed: %s\n", act_user_get_user_name (user)); tv = (GtkTreeView *)get_widget (d, "list-treeview"); selection = gtk_tree_view_get_selection (tv); model = gtk_tree_view_get_model (tv); store = GTK_LIST_STORE (model); if (gtk_tree_model_get_iter_first (model, &iter)) { do { gtk_tree_model_get (model, &iter, USER_COL, &u, SORT_KEY_COL, &key, -1); if (u != NULL) { if (act_user_get_uid (user) == act_user_get_uid (u)) { if (!get_next_user_row (model, &iter, &next)) get_previous_user_row (model, &iter, &next); if (key == 3) { d->other_accounts--; } gtk_list_store_remove (store, &iter); gtk_tree_selection_select_iter (selection, &next); g_object_unref (u); break; } g_object_unref (u); } } while (gtk_tree_model_iter_next (model, &iter)); } /* Hide heading for other accounts if last one have been removed. */ if (d->other_iter != NULL && d->other_accounts == 0 && key == 3) { gtk_list_store_remove (store, d->other_iter); gtk_tree_iter_free (d->other_iter); d->other_iter = NULL; } } static void show_user (ActUser *user, CcUserPanelPrivate *d); static void user_changed (ActUserManager *um, ActUser *user, CcUserPanelPrivate *d) { GtkTreeView *tv; GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; ActUser *current; GdkPixbuf *pixbuf; char *text; gboolean is_autologin; tv = (GtkTreeView *)get_widget (d, "list-treeview"); model = gtk_tree_view_get_model (tv); selection = gtk_tree_view_get_selection (tv); gtk_tree_model_get_iter_first (model, &iter); do { gtk_tree_model_get (model, &iter, USER_COL, ¤t, -1); if (current == user) { pixbuf = render_user_icon (user, UM_ICON_STYLE_FRAME | UM_ICON_STYLE_STATUS, 48); text = get_name_col_str (user); is_autologin = act_user_get_automatic_login (user); gtk_list_store_set (GTK_LIST_STORE (model), &iter, USER_COL, user, FACE_COL, pixbuf, NAME_COL, text, AUTOLOGIN_COL, is_autologin, -1); g_object_unref (pixbuf); g_free (text); g_object_unref (current); break; } if (current) g_object_unref (current); } while (gtk_tree_model_iter_next (model, &iter)); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gtk_tree_model_get (model, &iter, USER_COL, ¤t, -1); if (current == user) { show_user (user, d); } if (current) g_object_unref (current); } } static void select_created_user (GObject *object, GAsyncResult *result, gpointer user_data) { CcUserPanelPrivate *d = user_data; UmAccountDialog *dialog; GtkTreeView *tv; GtkTreeModel *model; GtkTreeSelection *selection; GtkTreeIter iter; ActUser *current; GtkTreePath *path; ActUser *user; uid_t user_uid; dialog = UM_ACCOUNT_DIALOG (object); user = um_account_dialog_finish (dialog, result); gtk_widget_destroy (GTK_WIDGET (dialog)); d->account_dialog = NULL; if (user == NULL) return; tv = (GtkTreeView *)get_widget (d, "list-treeview"); model = gtk_tree_view_get_model (tv); selection = gtk_tree_view_get_selection (tv); user_uid = act_user_get_uid (user); gtk_tree_model_get_iter_first (model, &iter); do { gtk_tree_model_get (model, &iter, USER_COL, ¤t, -1); if (current) { if (user_uid == act_user_get_uid (current)) { path = gtk_tree_model_get_path (model, &iter); gtk_tree_view_scroll_to_cell (tv, path, NULL, FALSE, 0.0, 0.0); gtk_tree_selection_select_path (selection, path); gtk_tree_path_free (path); g_object_unref (current); break; } g_object_unref (current); } } while (gtk_tree_model_iter_next (model, &iter)); g_object_unref (user); } static void add_user (GtkButton *button, CcUserPanelPrivate *d) { d->account_dialog = um_account_dialog_new (); um_account_dialog_show (d->account_dialog, GTK_WINDOW (gtk_widget_get_toplevel (d->main_box)), d->permission, select_created_user, d); } static void delete_user_done (ActUserManager *manager, GAsyncResult *res, CcUserPanelPrivate *d) { GError *error; error = NULL; if (!act_user_manager_delete_user_finish (manager, res, &error)) { if (!g_error_matches (error, ACT_USER_MANAGER_ERROR, ACT_USER_MANAGER_ERROR_PERMISSION_DENIED)) { GtkWidget *dialog; dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->main_box)), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("Failed to delete user")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", error->message); g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (gtk_widget_destroy), NULL); gtk_window_present (GTK_WINDOW (dialog)); } g_error_free (error); } } static void delete_user_response (GtkWidget *dialog, gint response_id, CcUserPanelPrivate *d) { ActUser *user; gboolean remove_files; gtk_widget_destroy (dialog); if (response_id == GTK_RESPONSE_CANCEL) { return; } else if (response_id == GTK_RESPONSE_NO) { remove_files = TRUE; } else { remove_files = FALSE; } user = get_selected_user (d); /* remove autologin */ if (act_user_get_automatic_login (user)) { act_user_set_automatic_login (user, FALSE); } act_user_manager_delete_user_async (d->um, user, remove_files, NULL, (GAsyncReadyCallback)delete_user_done, d); g_object_unref (user); } static void delete_user (GtkButton *button, CcUserPanelPrivate *d) { ActUser *user; GtkWidget *dialog; user = get_selected_user (d); if (user == NULL) { return; } else if (act_user_get_uid (user) == getuid ()) { dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->main_box)), 0, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _("You cannot delete your own account.")); g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); } else if (act_user_is_logged_in_anywhere (user)) { dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->main_box)), 0, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _("%s is still logged in"), act_user_get_real_name (user)); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("Deleting a user while they are logged in can leave the system in an inconsistent state.")); g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); } else { dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->main_box)), 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Do you want to keep %s's files?"), act_user_get_real_name (user)); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("It is possible to keep the home directory, mail spool and temporary files around when deleting a user account.")); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Delete Files"), GTK_RESPONSE_NO, _("_Keep Files"), GTK_RESPONSE_YES, _("_Cancel"), GTK_RESPONSE_CANCEL, NULL); gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users"); g_signal_connect (dialog, "response", G_CALLBACK (delete_user_response), d); } g_signal_connect (dialog, "close", G_CALLBACK (gtk_widget_destroy), NULL); gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); gtk_window_present (GTK_WINDOW (dialog)); g_object_unref (user); } static const gchar * get_invisible_text (void) { GtkWidget *entry; gunichar invisible_char; static gchar invisible_text[40]; gchar *p; gint i; entry = gtk_entry_new (); invisible_char = gtk_entry_get_invisible_char (GTK_ENTRY (entry)); if (invisible_char == 0) invisible_char = 0x2022; g_object_ref_sink (entry); g_object_unref (entry); /* five bullets */ p = invisible_text; for (i = 0; i < 5; i++) p += g_unichar_to_utf8 (invisible_char, p); *p = 0; return invisible_text; } static const gchar * get_password_mode_text (ActUser *user) { const gchar *text; if (act_user_get_locked (user)) { text = C_("Password mode", "Account disabled"); } else { switch (act_user_get_password_mode (user)) { case ACT_USER_PASSWORD_MODE_REGULAR: text = get_invisible_text (); break; case ACT_USER_PASSWORD_MODE_SET_AT_LOGIN: text = C_("Password mode", "To be set at next login"); break; case ACT_USER_PASSWORD_MODE_NONE: text = C_("Password mode", "None"); break; default: g_assert_not_reached (); } } return text; } static void autologin_changed (GObject *object, GParamSpec *pspec, CcUserPanelPrivate *d) { gboolean active; ActUser *user; active = gtk_switch_get_active (GTK_SWITCH (object)); user = get_selected_user (d); if (active != act_user_get_automatic_login (user)) { act_user_set_automatic_login (user, active); if (act_user_get_automatic_login (user)) { GSList *list; GSList *l; list = act_user_manager_list_users (d->um); for (l = list; l != NULL; l = l->next) { ActUser *u = l->data; if (act_user_get_uid (u) != act_user_get_uid (user)) { act_user_set_automatic_login (user, FALSE); } } g_slist_free (list); } } g_object_unref (user); } static gchar * get_login_time_text (ActUser *user) { gchar *text, *date_str, *time_str; GDateTime *date_time; gint64 time; time = act_user_get_login_time (user); if (act_user_is_logged_in (user)) { text = g_strdup (_("Logged in")); } else if (time > 0) { date_time = g_date_time_new_from_unix_local (time); date_str = get_smart_date (date_time); /* Translators: This is a time format string in the style of "22:58". It indicates a login time which follows a date. */ time_str = g_date_time_format (date_time, C_("login date-time", "%k:%M")); /* Translators: This indicates a login date-time. The first %s is a date, and the second %s a time. */ text = g_strdup_printf(C_("login date-time", "%s, %s"), date_str, time_str); g_date_time_unref (date_time); g_free (date_str); g_free (time_str); } else { text = g_strdup ("—"); } return text; } static gboolean get_autologin_possible (ActUser *user) { gboolean locked; gboolean set_password_at_login; locked = act_user_get_locked (user); set_password_at_login = (act_user_get_password_mode (user) == ACT_USER_PASSWORD_MODE_SET_AT_LOGIN); return !(locked || set_password_at_login); } static void on_permission_changed (GPermission *permission, GParamSpec *pspec, gpointer data); static void show_user (ActUser *user, CcUserPanelPrivate *d) { GtkWidget *image; GtkWidget *label; GtkWidget *label2; GtkWidget *label3; GdkPixbuf *pixbuf; gchar *lang, *text; GtkWidget *widget; GtkTreeModel *model; GtkTreeIter iter; gboolean show, enable; ActUser *current; pixbuf = render_user_icon (user, UM_ICON_STYLE_NONE, 48); image = get_widget (d, "user-icon-image"); gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); image = get_widget (d, "user-icon-image2"); gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); g_object_unref (pixbuf); um_photo_dialog_set_user (d->photo_dialog, user); widget = get_widget (d, "full-name-entry"); cc_editable_entry_set_text (CC_EDITABLE_ENTRY (widget), act_user_get_real_name (user)); gtk_widget_set_tooltip_text (widget, act_user_get_user_name (user)); widget = get_widget (d, "account-type-combo"); um_editable_combo_set_active (UM_EDITABLE_COMBO (widget), act_user_get_account_type (user)); widget = get_widget (d, "account-password-button"); um_editable_button_set_text (UM_EDITABLE_BUTTON (widget), get_password_mode_text (user)); enable = act_user_is_local_account (user); gtk_widget_set_sensitive (widget, enable); widget = get_widget (d, "autologin-switch"); g_signal_handlers_block_by_func (widget, autologin_changed, d); gtk_switch_set_active (GTK_SWITCH (widget), act_user_get_automatic_login (user)); g_signal_handlers_unblock_by_func (widget, autologin_changed, d); gtk_widget_set_sensitive (widget, get_autologin_possible (user)); widget = get_widget (d, "account-language-combo"); model = um_editable_combo_get_model (UM_EDITABLE_COMBO (widget)); cc_add_user_languages (model); lang = g_strdup (act_user_get_language (user)); if (!lang) lang = cc_common_language_get_current_language (); if (cc_common_language_get_iter_for_language (model, lang, &iter)) um_editable_combo_set_active_iter (UM_EDITABLE_COMBO (widget), &iter); g_free (lang); /* Fingerprint: show when self, possible, and local account */ widget = get_widget (d, "account-fingerprint-notebook"); label = get_widget (d, "account-fingerprint-label"); label2 = get_widget (d, "account-fingerprint-value-label"); label3 = get_widget (d, "account-fingerprint-button-label"); show = (act_user_get_uid (user) == getuid() && act_user_is_local_account (user) && set_fingerprint_label (label2, label3)); gtk_widget_set_visible (label, show); gtk_widget_set_visible (widget, show); /* Autologin: show when local account */ widget = get_widget (d, "autologin-switch"); label = get_widget (d, "autologin-label"); /* Don't show autologin option if ecryptfs is in use, because it won't work if user turns it on. */ show = act_user_is_local_account (user) && !is_using_ecryptfs (act_user_get_user_name (user)); gtk_widget_set_visible (widget, show); gtk_widget_set_visible (label, show); /* Menu bar: show when self and have indicator schema */ widget = get_widget (d, "show-login-name-checkbutton"); label = get_widget (d, "show-login-name-spacer"); show = act_user_get_uid (user) == getuid() && d->indicator_session_schema; gtk_widget_set_visible (widget, show); gtk_widget_set_visible (label, show); /* Last login: show when administrator or current user */ widget = get_widget (d, "last-login-value-label"); label = get_widget (d, "last-login-label"); current = act_user_manager_get_user_by_id (d->um, getuid ()); show = act_user_get_uid (user) == getuid () || act_user_get_account_type (current) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR; if (show) { text = get_login_time_text (user); gtk_label_set_text (GTK_LABEL (widget), text); g_free (text); } gtk_widget_set_visible (widget, show); gtk_widget_set_visible (label, show); enable = act_user_get_login_history (user) != NULL; widget = get_widget (d, "last-login-history-button"); gtk_widget_set_visible (widget, show); gtk_widget_set_sensitive (widget, enable); if (d->permission != NULL) on_permission_changed (d->permission, NULL, d); } static void selected_user_changed (GtkTreeSelection *selection, CcUserPanelPrivate *d) { GtkTreeModel *model; GtkTreeIter iter; ActUser *user; if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gtk_tree_model_get (model, &iter, USER_COL, &user, -1); show_user (user, d); gtk_widget_set_sensitive (get_widget (d, "main-user-vbox"), TRUE); g_object_unref (user); } else { gtk_widget_set_sensitive (get_widget (d, "main-user-vbox"), FALSE); } } static void change_name_done (GtkWidget *entry, CcUserPanelPrivate *d) { const gchar *text; ActUser *user; user = get_selected_user (d); text = cc_editable_entry_get_text (CC_EDITABLE_ENTRY (entry)); if (g_strcmp0 (text, act_user_get_real_name (user)) != 0 && is_valid_name (text)) { act_user_set_real_name (user, text); } g_object_unref (user); } static void account_type_changed (UmEditableCombo *combo, CcUserPanelPrivate *d) { ActUser *user; GtkTreeModel *model; GtkTreeIter iter; gint account_type; user = get_selected_user (d); model = um_editable_combo_get_model (combo); um_editable_combo_get_active_iter (combo, &iter); gtk_tree_model_get (model, &iter, 1, &account_type, -1); if (account_type != act_user_get_account_type (user)) { act_user_set_account_type (user, account_type); } g_object_unref (user); } static void language_response (GtkDialog *dialog, gint response_id, CcUserPanelPrivate *d) { GtkWidget *combo; ActUser *user; gchar *lang; GtkTreeModel *model; GtkTreeIter iter; user = get_selected_user (d); combo = get_widget (d, "account-language-combo"); model = um_editable_combo_get_model (UM_EDITABLE_COMBO (combo)); if (response_id == GTK_RESPONSE_OK) { lang = cc_language_chooser_get_language (GTK_WIDGET (dialog)); act_user_set_language (user, lang); } else { lang = g_strdup (act_user_get_language (user)); if (!lang) lang = cc_common_language_get_current_language (); } cc_common_language_get_iter_for_language (model, lang, &iter); um_editable_combo_set_active_iter (UM_EDITABLE_COMBO (combo), &iter); g_free (lang); gtk_widget_hide (GTK_WIDGET (dialog)); gtk_widget_set_sensitive (combo, TRUE); g_object_unref (user); } static void language_changed (UmEditableCombo *combo, CcUserPanelPrivate *d) { GtkTreeModel *model; GtkTreeIter iter; gchar *lang; ActUser *user; if (!um_editable_combo_get_active_iter (combo, &iter)) return; user = get_selected_user (d); model = um_editable_combo_get_model (combo); gtk_tree_model_get (model, &iter, 0, &lang, -1); if (lang) { if (g_strcmp0 (lang, act_user_get_language (user)) != 0) { act_user_set_language (user, lang); } g_free (lang); goto out; } if (d->language_chooser) { cc_language_chooser_clear_filter (d->language_chooser); gtk_window_present (GTK_WINDOW (d->language_chooser)); gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE); goto out; } d->language_chooser = cc_language_chooser_new (gtk_widget_get_toplevel (d->main_box), FALSE); g_signal_connect (d->language_chooser, "response", G_CALLBACK (language_response), d); g_signal_connect (d->language_chooser, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); gdk_window_set_cursor (gtk_widget_get_window (gtk_widget_get_toplevel (d->main_box)), NULL); gtk_window_present (GTK_WINDOW (d->language_chooser)); gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE); out: g_object_unref (user); } static void change_password (GtkButton *button, CcUserPanelPrivate *d) { ActUser *user; user = get_selected_user (d); um_password_dialog_set_user (d->password_dialog, user); um_password_dialog_show (d->password_dialog, GTK_WINDOW (gtk_widget_get_toplevel (d->main_box))); g_object_unref (user); } static void change_fingerprint (GtkButton *button, CcUserPanelPrivate *d) { GtkWidget *label, *label2; ActUser *user; user = get_selected_user (d); g_assert (g_strcmp0 (g_get_user_name (), act_user_get_user_name (user)) == 0); label = get_widget (d, "account-fingerprint-value-label"); label2 = get_widget (d, "account-fingerprint-button-label"); fingerprint_button_clicked (GTK_WINDOW (gtk_widget_get_toplevel (d->main_box)), label, label2, user); g_object_unref (user); } static void show_history (GtkButton *button, CcUserPanelPrivate *d) { ActUser *user; user = get_selected_user (d); um_history_dialog_set_user (d->history_dialog, user); um_history_dialog_show (d->history_dialog, GTK_WINDOW (gtk_widget_get_toplevel (d->main_box))); g_object_unref (user); } static gint sort_users (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) { ActUser *ua, *ub; gint sa, sb; gint result; gtk_tree_model_get (model, a, USER_COL, &ua, SORT_KEY_COL, &sa, -1); gtk_tree_model_get (model, b, USER_COL, &ub, SORT_KEY_COL, &sb, -1); if (sa < sb) { result = -1; } else if (sa > sb) { result = 1; } else { result = act_user_collate (ua, ub); } if (ua) { g_object_unref (ua); } if (ub) { g_object_unref (ub); } return result; } static gboolean dont_select_headings (GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data) { GtkTreeIter iter; gboolean is_user; gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, USER_ROW_COL, &is_user, -1); return is_user; } static void users_loaded (ActUserManager *manager, GParamSpec *pspec, CcUserPanelPrivate *d) { GSList *list, *l; ActUser *user; GtkWidget *dialog; if (act_user_manager_no_service (d->um)) { dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->main_box)), GTK_DIALOG_MODAL, GTK_MESSAGE_OTHER, GTK_BUTTONS_CLOSE, _("Failed to contact the accounts service")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("Please make sure that the AccountService is installed and enabled.")); g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); gtk_widget_show (dialog); gtk_widget_set_sensitive (d->main_box, FALSE); } list = act_user_manager_list_users (d->um); g_debug ("Got %d users\n", g_slist_length (list)); g_signal_connect (d->um, "user-changed", G_CALLBACK (user_changed), d); g_signal_connect (d->um, "user-is-logged-in-changed", G_CALLBACK (user_changed), d); for (l = list; l; l = l->next) { user = l->data; g_debug ("adding user %s\n", act_user_get_real_name (user)); user_added (d->um, user, d); } g_slist_free (list); g_signal_connect (d->um, "user-added", G_CALLBACK (user_added), d); g_signal_connect (d->um, "user-removed", G_CALLBACK (user_removed), d); } static void add_unlock_tooltip (GtkWidget *button) { gchar *names[3]; GIcon *icon; names[0] = "changes-allow-symbolic"; names[1] = "changes-allow"; names[2] = NULL; icon = (GIcon *)g_themed_icon_new_from_names (names, -1); /* Translator comments: * We split the line in 2 here to "make it look good", as there's * no good way to do this in GTK+ for tooltips. See: * https://bugzilla.gnome.org/show_bug.cgi?id=657168 */ setup_tooltip_with_embedded_icon (button, _("To make changes,\nclick the * icon first"), "*", icon); g_object_unref (icon); g_signal_connect (button, "button-release-event", G_CALLBACK (show_tooltip_now), NULL); } static void remove_unlock_tooltip (GtkWidget *button) { setup_tooltip_with_embedded_icon (button, NULL, NULL, NULL); g_signal_handlers_disconnect_by_func (button, G_CALLBACK (show_tooltip_now), NULL); } static void on_permission_changed (GPermission *permission, GParamSpec *pspec, gpointer data) { CcUserPanelPrivate *d = data; gboolean is_authorized; gboolean self_selected; ActUser *user; GtkWidget *widget; user = get_selected_user (d); if (!user) { return; } is_authorized = g_permission_get_allowed (G_PERMISSION (d->permission)); self_selected = act_user_get_uid (user) == geteuid (); widget = get_widget (d, "add-user-toolbutton"); gtk_widget_set_sensitive (widget, is_authorized); if (is_authorized) { setup_tooltip_with_embedded_icon (widget, _("Create a user account"), NULL, NULL); } else { gchar *names[3]; GIcon *icon; names[0] = "changes-allow-symbolic"; names[1] = "changes-allow"; names[2] = NULL; icon = (GIcon *)g_themed_icon_new_from_names (names, -1); setup_tooltip_with_embedded_icon (widget, _("To create a user account,\nclick the * icon first"), "*", icon); g_object_unref (icon); } widget = get_widget (d, "remove-user-toolbutton"); gtk_widget_set_sensitive (widget, is_authorized && !self_selected && !would_demote_only_admin (user)); if (is_authorized) { setup_tooltip_with_embedded_icon (widget, _("Delete the selected user account"), NULL, NULL); } else { gchar *names[3]; GIcon *icon; names[0] = "changes-allow-symbolic"; names[1] = "changes-allow"; names[2] = NULL; icon = (GIcon *)g_themed_icon_new_from_names (names, -1); setup_tooltip_with_embedded_icon (widget, _("To delete the selected user account,\nclick the * icon first"), "*", icon); g_object_unref (icon); } if (!act_user_is_local_account (user)) { um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-type-combo")), FALSE); remove_unlock_tooltip (get_widget (d, "account-type-combo")); gtk_widget_set_sensitive (GTK_WIDGET (get_widget (d, "autologin-switch")), FALSE); remove_unlock_tooltip (get_widget (d, "autologin-switch")); } else if (is_authorized && act_user_is_local_account (user)) { if (would_demote_only_admin (user)) { um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-type-combo")), FALSE); } else { um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-type-combo")), TRUE); } remove_unlock_tooltip (get_widget (d, "account-type-combo")); gtk_widget_set_sensitive (GTK_WIDGET (get_widget (d, "autologin-switch")), get_autologin_possible (user)); remove_unlock_tooltip (get_widget (d, "autologin-switch")); } else { um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-type-combo")), FALSE); if (would_demote_only_admin (user)) { remove_unlock_tooltip (get_widget (d, "account-type-combo")); } else { add_unlock_tooltip (get_widget (d, "account-type-combo")); } gtk_widget_set_sensitive (GTK_WIDGET (get_widget (d, "autologin-switch")), FALSE); add_unlock_tooltip (get_widget (d, "autologin-switch")); } /* The full name entry: insensitive if remote or not authorized and not self */ widget = get_widget (d, "full-name-entry"); if (!act_user_is_local_account (user)) { cc_editable_entry_set_editable (CC_EDITABLE_ENTRY (widget), FALSE); remove_unlock_tooltip (widget); } else if (is_authorized || self_selected) { cc_editable_entry_set_editable (CC_EDITABLE_ENTRY (widget), TRUE); remove_unlock_tooltip (widget); } else { cc_editable_entry_set_editable (CC_EDITABLE_ENTRY (widget), FALSE); add_unlock_tooltip (widget); } if (is_authorized || self_selected) { gtk_widget_show (get_widget (d, "user-icon-button")); gtk_widget_hide (get_widget (d, "user-icon-nonbutton")); um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-language-combo")), TRUE); remove_unlock_tooltip (get_widget (d, "account-language-combo")); um_editable_button_set_editable (UM_EDITABLE_BUTTON (get_widget (d, "account-password-button")), TRUE); remove_unlock_tooltip (get_widget (d, "account-password-button")); gtk_notebook_set_current_page (GTK_NOTEBOOK (get_widget (d, "account-fingerprint-notebook")), 1); } else { gtk_widget_hide (get_widget (d, "user-icon-button")); gtk_widget_show (get_widget (d, "user-icon-nonbutton")); um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-language-combo")), FALSE); add_unlock_tooltip (get_widget (d, "account-language-combo")); um_editable_button_set_editable (UM_EDITABLE_BUTTON (get_widget (d, "account-password-button")), FALSE); add_unlock_tooltip (get_widget (d, "account-password-button")); gtk_notebook_set_current_page (GTK_NOTEBOOK (get_widget (d, "account-fingerprint-notebook")), 0); } um_password_dialog_set_user (d->password_dialog, user); um_password_dialog_set_privileged (d->password_dialog, is_authorized); g_object_unref (user); } static gboolean match_user (GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data) { ActUser *user; const gchar *name; gchar *normalized_key = NULL; gchar *normalized_name = NULL; gchar *case_normalized_key = NULL; gchar *case_normalized_name = NULL; gchar *p; gboolean result = TRUE; gint i; gtk_tree_model_get (model, iter, USER_COL, &user, -1); if (!user) { goto out; } normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL); if (!normalized_key) { goto out; } case_normalized_key = g_utf8_casefold (normalized_key, -1); for (i = 0; i < 2; i++) { if (i == 0) { name = act_user_get_real_name (user); } else { name = act_user_get_user_name (user); } g_free (normalized_name); normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL); if (normalized_name) { g_free (case_normalized_name); case_normalized_name = g_utf8_casefold (normalized_name, -1); p = strstr (case_normalized_name, case_normalized_key); /* poor man's \b */ if (p == case_normalized_name || (p && p[-1] == ' ')) { result = FALSE; break; } } } out: if (user) { g_object_unref (user); } g_free (normalized_key); g_free (case_normalized_key); g_free (normalized_name); g_free (case_normalized_name); return result; } static void autologin_cell_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, CcUserPanelPrivate *d) { gboolean is_autologin; gtk_tree_model_get (model, iter, AUTOLOGIN_COL, &is_autologin, -1); if (is_autologin) { g_object_set (cell, "icon-name", "emblem-default-symbolic", NULL); } else { g_object_set (cell, "icon-name", NULL, NULL); } } static void setup_main_window (CcUserPanelPrivate *d) { GtkWidget *userlist; GtkTreeModel *model; GtkListStore *store; GtkTreeViewColumn *column; GtkCellRenderer *cell; GtkTreeSelection *selection; GtkWidget *button; GtkTreeIter iter; gint expander_size; gchar *title; GIcon *icon; GError *error = NULL; gchar *names[3]; gboolean loaded; userlist = get_widget (d, "list-treeview"); store = gtk_list_store_new (NUM_USER_LIST_COLS, ACT_TYPE_USER, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT, G_TYPE_BOOLEAN); model = (GtkTreeModel *)store; gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model), sort_users, NULL, NULL); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING); gtk_tree_view_set_model (GTK_TREE_VIEW (userlist), model); gtk_tree_view_set_search_column (GTK_TREE_VIEW (userlist), USER_COL); gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (userlist), match_user, NULL, NULL); g_object_unref (model); gtk_widget_style_get (userlist, "expander-size", &expander_size, NULL); gtk_tree_view_set_level_indentation (GTK_TREE_VIEW (userlist), - (expander_size + 6)); title = g_strdup_printf ("%s", _("My Account")); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, TITLE_COL, title, HEADING_ROW_COL, TRUE, SORT_KEY_COL, 0, AUTOLOGIN_COL, FALSE, -1); g_free (title); d->other_accounts = 0; d->other_iter = NULL; column = gtk_tree_view_column_new (); cell = gtk_cell_renderer_pixbuf_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, FALSE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "pixbuf", FACE_COL); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", USER_ROW_COL); cell = gtk_cell_renderer_text_new (); g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "markup", NAME_COL); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", USER_ROW_COL); cell = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "markup", TITLE_COL); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", HEADING_ROW_COL); cell = gtk_cell_renderer_pixbuf_new (); g_object_set (cell, "follow-state", TRUE, NULL); gtk_tree_view_column_pack_start (column, cell, FALSE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", USER_ROW_COL); gtk_tree_view_column_set_cell_data_func (column, cell, (GtkTreeCellDataFunc) autologin_cell_data_func, d, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (userlist), column); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (userlist)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); g_signal_connect (selection, "changed", G_CALLBACK (selected_user_changed), d); gtk_tree_selection_set_select_function (selection, dont_select_headings, NULL, NULL); gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW (get_widget (d, "list-scrolledwindow")), 300); gtk_widget_set_size_request (get_widget (d, "list-scrolledwindow"), 200, -1); button = get_widget (d, "add-user-toolbutton"); g_signal_connect (button, "clicked", G_CALLBACK (add_user), d); button = get_widget (d, "remove-user-toolbutton"); g_signal_connect (button, "clicked", G_CALLBACK (delete_user), d); button = get_widget (d, "user-icon-nonbutton"); add_unlock_tooltip (button); button = get_widget (d, "full-name-entry"); g_signal_connect (button, "editing-done", G_CALLBACK (change_name_done), d); button = get_widget (d, "account-type-combo"); g_signal_connect (button, "editing-done", G_CALLBACK (account_type_changed), d); button = get_widget (d, "account-password-button"); g_signal_connect (button, "start-editing", G_CALLBACK (change_password), d); button = get_widget (d, "account-language-combo"); g_signal_connect (button, "editing-done", G_CALLBACK (language_changed), d); button = get_widget (d, "autologin-switch"); g_signal_connect (button, "notify::active", G_CALLBACK (autologin_changed), d); button = get_widget (d, "account-fingerprint-button"); g_signal_connect (button, "clicked", G_CALLBACK (change_fingerprint), d); button = get_widget (d, "last-login-history-button"); g_signal_connect (button, "clicked", G_CALLBACK (show_history), d); d->permission = (GPermission *)polkit_permission_new_sync (USER_ACCOUNTS_PERMISSION, NULL, NULL, &error); if (d->permission != NULL) { g_signal_connect (d->permission, "notify", G_CALLBACK (on_permission_changed), d); on_permission_changed (d->permission, NULL, d); } else { g_warning ("Cannot create '%s' permission: %s", USER_ACCOUNTS_PERMISSION, error->message); g_error_free (error); } button = get_widget (d, "add-user-toolbutton"); names[0] = "changes-allow-symbolic"; names[1] = "changes-allow"; names[2] = NULL; icon = (GIcon *)g_themed_icon_new_from_names (names, -1); setup_tooltip_with_embedded_icon (button, _("To create a user account,\nclick the * icon first"), "*", icon); button = get_widget (d, "remove-user-toolbutton"); setup_tooltip_with_embedded_icon (button, _("To delete the selected user account,\nclick the * icon first"), "*", icon); g_object_unref (icon); g_object_get (d->um, "is-loaded", &loaded, NULL); if (loaded) users_loaded (d->um, NULL, d); else g_signal_connect (d->um, "notify::is-loaded", G_CALLBACK (users_loaded), d); } static void cc_user_panel_init (CcUserPanel *self) { CcUserPanelPrivate *d; GError *error; volatile GType type G_GNUC_UNUSED; GtkWidget *button; GtkStyleContext *context; GSettingsSchema *schema; d = self->priv = UM_USER_PANEL_PRIVATE (self); g_resources_register (um_get_resource ()); /* register types that the builder might need */ type = um_editable_button_get_type (); type = cc_editable_entry_get_type (); type = um_editable_combo_get_type (); gtk_widget_set_size_request (GTK_WIDGET (self), -1, 350); d->builder = gtk_builder_new (); d->um = act_user_manager_get_default (); error = NULL; if (!gtk_builder_add_from_resource (d->builder, "/org/gnome/control-center/user-accounts/user-accounts-dialog.ui", &error)) { g_error ("%s", error->message); g_error_free (error); return; } d->password_dialog = um_password_dialog_new (); button = get_widget (d, "user-icon-button"); d->photo_dialog = um_photo_dialog_new (button); d->main_box = get_widget (d, "accounts-vbox"); gtk_widget_reparent (d->main_box, GTK_WIDGET (self)); d->history_dialog = um_history_dialog_new (); setup_main_window (d); context = gtk_widget_get_style_context (get_widget (d, "list-scrolledwindow")); gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM); context = gtk_widget_get_style_context (get_widget (d, "add-remove-toolbar")); gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP); schema = g_settings_schema_source_lookup (g_settings_schema_source_get_default (), INDICATOR_SESSION_SCHEMA, TRUE); if (schema) { d->indicator_session_schema = g_settings_new (INDICATOR_SESSION_SCHEMA); g_settings_bind (d->indicator_session_schema, "show-real-name-on-panel", get_widget (d, "show-login-name-checkbutton"), "active", G_SETTINGS_BIND_DEFAULT); g_settings_schema_unref (schema); } } static void cc_user_panel_dispose (GObject *object) { CcUserPanelPrivate *priv = UM_USER_PANEL (object)->priv; if (priv->um) { g_signal_handlers_disconnect_by_data (priv->um, priv); priv->um = NULL; } if (priv->builder) { g_object_unref (priv->builder); priv->builder = NULL; } if (priv->password_dialog) { um_password_dialog_free (priv->password_dialog); priv->password_dialog = NULL; } if (priv->photo_dialog) { um_photo_dialog_free (priv->photo_dialog); priv->photo_dialog = NULL; } if (priv->history_dialog) { um_history_dialog_free (priv->history_dialog); priv->history_dialog = NULL; } if (priv->account_dialog) { gtk_dialog_response (GTK_DIALOG (priv->account_dialog), GTK_RESPONSE_DELETE_EVENT); priv->account_dialog = NULL; } if (priv->language_chooser) { gtk_widget_destroy (priv->language_chooser); priv->language_chooser = NULL; } if (priv->permission) { g_object_unref (priv->permission); priv->permission = NULL; } if (priv->other_iter) { gtk_tree_iter_free (priv->other_iter); priv->other_iter = NULL; } G_OBJECT_CLASS (cc_user_panel_parent_class)->dispose (object); } static GPermission * cc_user_panel_get_permission (CcPanel *panel) { CcUserPanelPrivate *priv = UM_USER_PANEL (panel)->priv; return priv->permission; } static const char * cc_user_panel_get_help_uri (CcPanel *panel) { if (!g_strcmp0(g_getenv("XDG_CURRENT_DESKTOP"), "Unity")) return "help:ubuntu-help/user-accounts"; else return "help:gnome-help/user-accounts"; } static void cc_user_panel_class_init (CcUserPanelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); CcPanelClass *panel_class = CC_PANEL_CLASS (klass); object_class->dispose = cc_user_panel_dispose; panel_class->get_permission = cc_user_panel_get_permission; panel_class->get_help_uri = cc_user_panel_get_help_uri; g_type_class_add_private (klass, sizeof (CcUserPanelPrivate)); } void cc_user_panel_register (GIOModule *module) { cc_user_panel_register_type (G_TYPE_MODULE (module)); g_io_extension_point_implement (CC_SHELL_PANEL_EXTENSION_POINT, UM_TYPE_USER_PANEL, "user-accounts", 0); }