diff options
Diffstat (limited to 'panels/user-accounts')
101 files changed, 18976 insertions, 0 deletions
diff --git a/panels/user-accounts/cc-add-user-dialog.c b/panels/user-accounts/cc-add-user-dialog.c new file mode 100644 index 0000000..7a99b09 --- /dev/null +++ b/panels/user-accounts/cc-add-user-dialog.c @@ -0,0 +1,1772 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <act/act.h> + +#include "cc-add-user-dialog.h" +#include "cc-realm-manager.h" +#include "user-utils.h" +#include "pw-utils.h" + +#define PASSWORD_CHECK_TIMEOUT 600 +#define DOMAIN_DEFAULT_HINT _("Should match the web address of your login provider.") + +typedef enum { + MODE_LOCAL, + MODE_ENTERPRISE, + MODE_OFFLINE +} AccountMode; + +static void mode_change (CcAddUserDialog *self, + AccountMode mode); + +static void dialog_validate (CcAddUserDialog *self); + +static void on_join_login (GObject *source, + GAsyncResult *result, + gpointer user_data); + +static void on_realm_joined (GObject *source, + GAsyncResult *result, + gpointer user_data); + +static void add_button_clicked_cb (CcAddUserDialog *self); + +struct _CcAddUserDialog { + GtkDialog parent_instance; + + GtkButton *add_button; + GtkToggleButton *enterprise_button; + GtkComboBox *enterprise_domain_combo; + GtkEntry *enterprise_domain_entry; + GtkLabel *enterprise_domain_hint_label; + GtkGrid *enterprise_grid; + GtkLabel *enterprise_hint_label; + GtkEntry *enterprise_login_entry; + GtkEntry *enterprise_password_entry; + GtkListStore *enterprise_realm_model; + GtkRadioButton *local_account_type_standard; + GtkGrid *local_grid; + GtkLabel *local_hint_label; + GtkEntry *local_name_entry; + GtkComboBoxText *local_username_combo; + GtkListStore *local_username_model; + GtkEntry *local_password_entry; + GtkRadioButton *local_password_radio; + GtkEntry *local_username_entry; + GtkLabel *local_username_hint_label; + GtkLevelBar *local_strength_indicator; + GtkEntry *local_verify_entry; + GtkLabel *local_verify_hint_label; + GtkGrid *offline_grid; + GtkSpinner *spinner; + GtkStack *stack; + + GCancellable *cancellable; + GPermission *permission; + AccountMode mode; + ActUser *user; + + gboolean has_custom_username; + gint local_name_timeout_id; + gint local_username_timeout_id; + ActUserPasswordMode local_password_mode; + gint local_password_timeout_id; + gboolean local_valid_username; + + guint realmd_watch; + CcRealmManager *realm_manager; + CcRealmObject *selected_realm; + gboolean enterprise_check_credentials; + gint enterprise_domain_timeout_id; + gboolean enterprise_domain_chosen; + + /* Join credential dialog */ + GtkDialog *join_dialog; + GtkLabel *join_domain; + GtkEntry *join_name; + GtkEntry *join_password; + gboolean join_prompted; +}; + +G_DEFINE_TYPE (CcAddUserDialog, cc_add_user_dialog, GTK_TYPE_DIALOG); + +static void +show_error_dialog (CcAddUserDialog *self, + const gchar *message, + GError *error) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (GTK_WINDOW (self), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", message); + + if (error != NULL) { + g_dbus_error_strip_remote_error (error); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", error->message); + } + + g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +begin_action (CcAddUserDialog *self) +{ + g_debug ("Beginning action, disabling dialog controls"); + + if (self->enterprise_check_credentials) { + gtk_widget_set_sensitive (GTK_WIDGET (self->stack), FALSE); + } + gtk_widget_set_sensitive (GTK_WIDGET (self->enterprise_button), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE); + + gtk_widget_show (GTK_WIDGET (self->spinner)); + gtk_spinner_start (self->spinner); +} + +static void +finish_action (CcAddUserDialog *self) +{ + g_debug ("Completed domain action"); + + if (self->enterprise_check_credentials) { + gtk_widget_set_sensitive (GTK_WIDGET (self->stack), TRUE); + } + gtk_widget_set_sensitive (GTK_WIDGET (self->enterprise_button), TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), TRUE); + + gtk_widget_hide (GTK_WIDGET (self->spinner)); + gtk_spinner_stop (self->spinner); +} + +static void +user_loaded_cb (CcAddUserDialog *self, + GParamSpec *pspec, + ActUser *user) +{ + const gchar *password; + + finish_action (self); + + /* Set a password for the user */ + password = gtk_entry_get_text (self->local_password_entry); + act_user_set_password_mode (user, self->local_password_mode); + if (self->local_password_mode == ACT_USER_PASSWORD_MODE_REGULAR) + act_user_set_password (user, password, ""); + + self->user = g_object_ref (user); + gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_CLOSE); +} + +static void +create_user_done (ActUserManager *manager, + GAsyncResult *res, + CcAddUserDialog *self) +{ + ActUser *user; + GError *error; + + /* Note that user is returned without an extra reference */ + + error = NULL; + user = act_user_manager_create_user_finish (manager, res, &error); + + if (user == NULL) { + finish_action (self); + g_debug ("Failed to create user: %s", error->message); + if (!g_error_matches (error, ACT_USER_MANAGER_ERROR, ACT_USER_MANAGER_ERROR_PERMISSION_DENIED)) + show_error_dialog (self, _("Failed to add account"), error); + g_error_free (error); + gtk_widget_grab_focus (GTK_WIDGET (self->local_name_entry)); + } else { + g_debug ("Created user: %s", act_user_get_user_name (user)); + + /* Check if the returned object is fully loaded before returning it */ + if (act_user_is_loaded (user)) + user_loaded_cb (self, NULL, user); + else + g_signal_connect_object (user, "notify::is-loaded", G_CALLBACK (user_loaded_cb), self, G_CONNECT_SWAPPED); + } +} + +static void +local_create_user (CcAddUserDialog *self) +{ + ActUserManager *manager; + const gchar *username; + const gchar *name; + gint account_type; + + begin_action (self); + + name = gtk_entry_get_text (self->local_name_entry); + username = gtk_combo_box_text_get_active_text (self->local_username_combo); + account_type = (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->local_account_type_standard)) ? ACT_USER_ACCOUNT_TYPE_STANDARD : ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR); + + g_debug ("Creating local user: %s", username); + + manager = act_user_manager_get_default (); + act_user_manager_create_user_async (manager, + username, + name, + account_type, + self->cancellable, + (GAsyncReadyCallback)create_user_done, + self); +} + +static gint +update_password_strength (CcAddUserDialog *self) +{ + const gchar *password; + const gchar *username; + const gchar *hint; + const gchar *verify; + gint strength_level; + + password = gtk_entry_get_text (self->local_password_entry); + username = gtk_combo_box_text_get_active_text (self->local_username_combo); + + pw_strength (password, NULL, username, &hint, &strength_level); + + gtk_label_set_label (self->local_hint_label, hint); + gtk_level_bar_set_value (self->local_strength_indicator, strength_level); + + if (strength_level > 1) { + set_entry_validation_checkmark (self->local_password_entry); + } else if (strlen (password) == 0) { + set_entry_generation_icon (self->local_password_entry); + } else { + clear_entry_validation_error (self->local_password_entry); + } + + verify = gtk_entry_get_text (self->local_verify_entry); + if (strlen (verify) == 0) { + gtk_widget_set_sensitive (GTK_WIDGET (self->local_verify_entry), strength_level > 1); + } + + return strength_level; +} + +static gboolean +local_validate (CcAddUserDialog *self) +{ + gboolean valid_name; + gboolean valid_password; + const gchar *name; + const gchar *password; + const gchar *verify; + gint strength; + + if (self->local_valid_username) { + set_entry_validation_checkmark (self->local_username_entry); + } + + name = gtk_entry_get_text (self->local_name_entry); + valid_name = is_valid_name (name); + if (valid_name) { + set_entry_validation_checkmark (self->local_name_entry); + } + + password = gtk_entry_get_text (self->local_password_entry); + verify = gtk_entry_get_text (self->local_verify_entry); + if (self->local_password_mode == ACT_USER_PASSWORD_MODE_REGULAR) { + strength = update_password_strength (self); + valid_password = strength > 1 && strcmp (password, verify) == 0; + } else { + valid_password = TRUE; + } + + return valid_name && self->local_valid_username && valid_password; +} + +static void local_username_is_valid_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + g_autoptr(GError) error = NULL; + g_autofree gchar *tip = NULL; + g_autofree gchar *name = NULL; + g_autofree gchar *username = NULL; + gboolean valid; + + valid = is_valid_username_finish (result, &tip, &username, &error); + if (error != NULL) { + g_warning ("Could not check username by usermod: %s", error->message); + valid = TRUE; + } + + name = gtk_combo_box_text_get_active_text (self->local_username_combo); + if (g_strcmp0 (name, username) == 0) { + self->local_valid_username = valid; + gtk_label_set_label (self->local_username_hint_label, tip); + dialog_validate (self); + } + + g_object_unref (self); +} + +static gboolean +local_username_timeout (CcAddUserDialog *self) +{ + g_autofree gchar *name = NULL; + + self->local_username_timeout_id = 0; + + name = gtk_combo_box_text_get_active_text (self->local_username_combo); + is_valid_username_async (name, NULL, local_username_is_valid_cb, g_object_ref (self)); + + return FALSE; +} + +static gboolean +local_username_combo_focus_out_event_cb (CcAddUserDialog *self) +{ + if (self->local_username_timeout_id != 0) { + g_source_remove (self->local_username_timeout_id); + self->local_username_timeout_id = 0; + } + + local_username_timeout (self); + + return FALSE; +} + +static void +local_username_combo_changed_cb (CcAddUserDialog *self) +{ + const gchar *username; + + username = gtk_entry_get_text (self->local_username_entry); + if (*username == '\0') + self->has_custom_username = FALSE; + else if (gtk_widget_has_focus (GTK_WIDGET (self->local_username_entry)) || + gtk_combo_box_get_active (GTK_COMBO_BOX (self->local_username_combo)) > 0) + self->has_custom_username = TRUE; + + if (self->local_username_timeout_id != 0) { + g_source_remove (self->local_username_timeout_id); + self->local_username_timeout_id = 0; + } + + clear_entry_validation_error (self->local_username_entry); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE); + + self->local_valid_username = FALSE; + self->local_username_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT, (GSourceFunc) local_username_timeout, self); +} + +static gboolean +local_name_timeout (CcAddUserDialog *self) +{ + self->local_name_timeout_id = 0; + + dialog_validate (self); + + return FALSE; +} + +static gboolean +local_name_entry_focus_out_event_cb (CcAddUserDialog *self) +{ + if (self->local_name_timeout_id != 0) { + g_source_remove (self->local_name_timeout_id); + self->local_name_timeout_id = 0; + } + + local_name_timeout (self); + + return FALSE; +} + +static void +generate_username_choices (const gchar *name, + GtkListStore *store) +{ + gboolean in_use, same_as_initial; + char *lc_name, *ascii_name, *stripped_name; + char **words1; + char **words2 = NULL; + char **w1, **w2; + char *c; + char *unicode_fallback = "?"; + GString *first_word, *last_word; + GString *item0, *item1, *item2, *item3, *item4; + int len; + int nwords1, nwords2, i; + GHashTable *items; + GtkTreeIter iter; + gsize max_name_length; + + gtk_list_store_clear (store); + + ascii_name = g_convert_with_fallback (name, -1, "ASCII//TRANSLIT", "UTF-8", + unicode_fallback, NULL, NULL, NULL); + + lc_name = g_ascii_strdown (ascii_name, -1); + + /* Remove all non ASCII alphanumeric chars from the name, + * apart from the few allowed symbols. + * + * We do remove '.', even though it is usually allowed, + * since it often comes in via an abbreviated middle name, + * and the dot looks just wrong in the proposals then. + */ + stripped_name = g_strnfill (strlen (lc_name) + 1, '\0'); + i = 0; + for (c = lc_name; *c; c++) { + if (!(g_ascii_isdigit (*c) || g_ascii_islower (*c) || + *c == ' ' || *c == '-' || *c == '_' || + /* used to track invalid words, removed below */ + *c == '?') ) + continue; + + stripped_name[i] = *c; + i++; + } + + if (strlen (stripped_name) == 0) { + g_free (ascii_name); + g_free (lc_name); + g_free (stripped_name); + return; + } + + /* we split name on spaces, and then on dashes, so that we can treat + * words linked with dashes the same way, i.e. both fully shown, or + * both abbreviated + */ + words1 = g_strsplit_set (stripped_name, " ", -1); + len = g_strv_length (words1); + + /* The default item is a concatenation of all words without ? */ + item0 = g_string_sized_new (strlen (stripped_name)); + + g_free (ascii_name); + g_free (lc_name); + g_free (stripped_name); + + /* Concatenate the whole first word with the first letter of each + * word (item1), and the last word with the first letter of each + * word (item2). item3 and item4 are symmetrical respectively to + * item1 and item2. + * + * Constant 5 is the max reasonable number of words we may get when + * splitting on dashes, since we can't guess it at this point, + * and reallocating would be too bad. + */ + item1 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5); + item3 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5); + + item2 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5); + item4 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5); + + /* again, guess at the max size of names */ + first_word = g_string_sized_new (20); + last_word = g_string_sized_new (20); + + nwords1 = 0; + nwords2 = 0; + for (w1 = words1; *w1; w1++) { + if (strlen (*w1) == 0) + continue; + + /* skip words with string '?', most likely resulting + * from failed transliteration to ASCII + */ + if (strstr (*w1, unicode_fallback) != NULL) + continue; + + nwords1++; /* count real words, excluding empty string */ + + item0 = g_string_append (item0, *w1); + + words2 = g_strsplit_set (*w1, "-", -1); + /* reset last word if a new non-empty word has been found */ + if (strlen (*words2) > 0) + last_word = g_string_set_size (last_word, 0); + + for (w2 = words2; *w2; w2++) { + if (strlen (*w2) == 0) + continue; + + nwords2++; + + /* part of the first "toplevel" real word */ + if (nwords1 == 1) { + item1 = g_string_append (item1, *w2); + first_word = g_string_append (first_word, *w2); + } + else { + item1 = g_string_append_unichar (item1, + g_utf8_get_char (*w2)); + item3 = g_string_append_unichar (item3, + g_utf8_get_char (*w2)); + } + + /* not part of the last "toplevel" word */ + if (w1 != words1 + len - 1) { + item2 = g_string_append_unichar (item2, + g_utf8_get_char (*w2)); + item4 = g_string_append_unichar (item4, + g_utf8_get_char (*w2)); + } + + /* always save current word so that we have it if last one reveals empty */ + last_word = g_string_append (last_word, *w2); + } + + g_strfreev (words2); + } + item2 = g_string_append (item2, last_word->str); + item3 = g_string_append (item3, first_word->str); + item4 = g_string_prepend (item4, last_word->str); + + max_name_length = get_username_max_length (); + + g_string_truncate (first_word, max_name_length); + g_string_truncate (last_word, max_name_length); + + g_string_truncate (item0, max_name_length); + g_string_truncate (item1, max_name_length); + g_string_truncate (item2, max_name_length); + g_string_truncate (item3, max_name_length); + g_string_truncate (item4, max_name_length); + + items = g_hash_table_new (g_str_hash, g_str_equal); + + in_use = is_username_used (item0->str); + if (!in_use && !g_ascii_isdigit (item0->str[0])) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item0->str, -1); + g_hash_table_insert (items, item0->str, item0->str); + } + + in_use = is_username_used (item1->str); + same_as_initial = (g_strcmp0 (item0->str, item1->str) == 0); + if (!same_as_initial && nwords2 > 0 && !in_use && !g_ascii_isdigit (item1->str[0])) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item1->str, -1); + g_hash_table_insert (items, item1->str, item1->str); + } + + /* if there's only one word, would be the same as item1 */ + if (nwords2 > 1) { + /* add other items */ + in_use = is_username_used (item2->str); + if (!in_use && !g_ascii_isdigit (item2->str[0]) && + !g_hash_table_lookup (items, item2->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item2->str, -1); + g_hash_table_insert (items, item2->str, item2->str); + } + + in_use = is_username_used (item3->str); + if (!in_use && !g_ascii_isdigit (item3->str[0]) && + !g_hash_table_lookup (items, item3->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item3->str, -1); + g_hash_table_insert (items, item3->str, item3->str); + } + + in_use = is_username_used (item4->str); + if (!in_use && !g_ascii_isdigit (item4->str[0]) && + !g_hash_table_lookup (items, item4->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, item4->str, -1); + g_hash_table_insert (items, item4->str, item4->str); + } + + /* add the last word */ + in_use = is_username_used (last_word->str); + if (!in_use && !g_ascii_isdigit (last_word->str[0]) && + !g_hash_table_lookup (items, last_word->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, last_word->str, -1); + g_hash_table_insert (items, last_word->str, last_word->str); + } + + /* ...and the first one */ + in_use = is_username_used (first_word->str); + if (!in_use && !g_ascii_isdigit (first_word->str[0]) && + !g_hash_table_lookup (items, first_word->str)) { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, first_word->str, -1); + g_hash_table_insert (items, first_word->str, first_word->str); + } + } + + g_hash_table_destroy (items); + g_strfreev (words1); + g_string_free (first_word, TRUE); + g_string_free (last_word, TRUE); + g_string_free (item0, TRUE); + g_string_free (item1, TRUE); + g_string_free (item2, TRUE); + g_string_free (item3, TRUE); + g_string_free (item4, TRUE); +} + +static void +local_name_entry_changed_cb (CcAddUserDialog *self) +{ + const char *name; + + gtk_list_store_clear (self->local_username_model); + + name = gtk_entry_get_text (self->local_name_entry); + if ((name == NULL || strlen (name) == 0) && !self->has_custom_username) { + gtk_entry_set_text (self->local_username_entry, ""); + } else if (name != NULL && strlen (name) != 0) { + generate_username_choices (name, self->local_username_model); + if (!self->has_custom_username) + gtk_combo_box_set_active (GTK_COMBO_BOX (self->local_username_combo), 0); + } + + if (self->local_name_timeout_id != 0) { + g_source_remove (self->local_name_timeout_id); + self->local_name_timeout_id = 0; + } + + clear_entry_validation_error (self->local_name_entry); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE); + + self->local_name_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT, (GSourceFunc) local_name_timeout, self); +} + +static void +update_password_match (CcAddUserDialog *self) +{ + const gchar *password; + const gchar *verify; + const gchar *message = ""; + + password = gtk_entry_get_text (self->local_password_entry); + verify = gtk_entry_get_text (self->local_verify_entry); + if (strlen (verify) != 0) { + if (strcmp (password, verify) != 0) { + message = _("The passwords do not match."); + } else { + set_entry_validation_checkmark (self->local_verify_entry); + } + } + gtk_label_set_label (self->local_verify_hint_label, message); +} + +static void +local_password_entry_icon_press_cb (CcAddUserDialog *self) +{ + gchar *pwd; + + pwd = pw_generate (); + if (pwd == NULL) + return; + + gtk_entry_set_text (self->local_password_entry, pwd); + gtk_entry_set_text (self->local_verify_entry, pwd); + gtk_entry_set_visibility (self->local_password_entry, TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (self->local_verify_entry), TRUE); + + g_free (pwd); +} + +static gboolean +local_password_timeout (CcAddUserDialog *self) +{ + self->local_password_timeout_id = 0; + + dialog_validate (self); + update_password_match (self); + + return FALSE; +} + +static gboolean +password_focus_out_event_cb (CcAddUserDialog *self) +{ + if (self->local_password_timeout_id != 0) { + g_source_remove (self->local_password_timeout_id); + self->local_password_timeout_id = 0; + } + + local_password_timeout (self); + + return FALSE; +} + +static gboolean +local_password_entry_key_press_event_cb (CcAddUserDialog *self, + GdkEvent *event) +{ + GdkEventKey *key = (GdkEventKey *)event; + + if (key->keyval == GDK_KEY_Tab) + local_password_timeout (self); + + return FALSE; +} + +static void +recheck_password_match (CcAddUserDialog *self) +{ + const char *password; + + if (self->local_password_timeout_id != 0) { + g_source_remove (self->local_password_timeout_id); + self->local_password_timeout_id = 0; + } + + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE); + + password = gtk_entry_get_text (self->local_password_entry); + if (strlen (password) == 0) { + gtk_entry_set_visibility (self->local_password_entry, FALSE); + } + + self->local_password_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT, (GSourceFunc) local_password_timeout, self); +} + +static void +local_password_entry_changed_cb (CcAddUserDialog *self) +{ + clear_entry_validation_error (self->local_password_entry); + clear_entry_validation_error (self->local_verify_entry); + recheck_password_match (self); +} + +static void +local_verify_entry_changed_cb (CcAddUserDialog *self) +{ + clear_entry_validation_error (self->local_verify_entry); + recheck_password_match (self); +} + +static void +local_password_radio_changed_cb (CcAddUserDialog *self) +{ + gboolean active; + + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->local_password_radio)); + self->local_password_mode = active ? ACT_USER_PASSWORD_MODE_REGULAR : ACT_USER_PASSWORD_MODE_SET_AT_LOGIN; + + gtk_widget_set_sensitive (GTK_WIDGET (self->local_password_entry), active); + gtk_widget_set_sensitive (GTK_WIDGET (self->local_verify_entry), active); + gtk_widget_set_sensitive (GTK_WIDGET (self->local_strength_indicator), active); + gtk_widget_set_sensitive (GTK_WIDGET (self->local_hint_label), active); + + dialog_validate (self); +} + +static gboolean +enterprise_validate (CcAddUserDialog *self) +{ + const gchar *name; + gboolean valid_name; + gboolean valid_domain; + GtkTreeIter iter; + + name = gtk_entry_get_text (self->enterprise_login_entry); + valid_name = is_valid_name (name); + + if (gtk_combo_box_get_active_iter (self->enterprise_domain_combo, &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (self->enterprise_realm_model), + &iter, 0, &name, -1); + } else { + name = gtk_entry_get_text (self->enterprise_domain_entry); + } + + valid_domain = is_valid_name (name) && self->selected_realm != NULL; + return valid_name && valid_domain; +} + +static void +enterprise_add_realm (CcAddUserDialog *self, + CcRealmObject *realm) +{ + GtkTreeModel *model; + GtkTreeIter iter; + CcRealmCommon *common; + const gchar *realm_name; + gboolean match; + gboolean ret; + gchar *name; + + common = cc_realm_object_get_common (realm); + g_return_if_fail (common != NULL); + + realm_name = cc_realm_common_get_name (common); + + /* + * Don't add a second realm if we already have one with this name. + * Sometimes realmd returns to realms for the same name, if it has + * different ways to use that realm. The first one that realmd + * returns is the one it prefers. + */ + + model = GTK_TREE_MODEL (self->enterprise_realm_model); + ret = gtk_tree_model_get_iter_first (model, &iter); + while (ret) { + gtk_tree_model_get (model, &iter, 0, &name, -1); + match = (g_strcmp0 (name, realm_name) == 0); + g_free (name); + if (match) { + g_debug ("ignoring duplicate realm: %s", realm_name); + g_object_unref (common); + return; + } + ret = gtk_tree_model_iter_next (model, &iter); + } + + gtk_list_store_append (self->enterprise_realm_model, &iter); + gtk_list_store_set (self->enterprise_realm_model, &iter, + 0, realm_name, + 1, realm, + -1); + + /* Prefill domain entry by the existing one */ + if (!self->enterprise_domain_chosen && cc_realm_is_configured (realm)) { + gtk_entry_set_text (self->enterprise_domain_entry, realm_name); + } + + g_debug ("added realm to drop down: %s %s", realm_name, + g_dbus_object_get_object_path (G_DBUS_OBJECT (realm))); + + g_object_unref (common); +} + +static void +on_manager_realm_added (CcAddUserDialog *self, + CcRealmObject *realm) +{ + enterprise_add_realm (self, realm); +} + + +static void +on_register_user (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + GError *error = NULL; + ActUser *user; + + if (g_cancellable_is_cancelled (self->cancellable)) { + g_object_unref (self); + return; + } + + user = act_user_manager_cache_user_finish (ACT_USER_MANAGER (source), result, &error); + + /* This is where we're finally done */ + if (user != NULL) { + g_debug ("Successfully cached remote user: %s", act_user_get_user_name (user)); + finish_action (self); + self->user = g_object_ref (user); + gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_CLOSE); + } else { + show_error_dialog (self, _("Failed to register account"), error); + g_message ("Couldn't cache user account: %s", error->message); + finish_action (self); + g_error_free (error); + } + + g_object_unref (self); +} + +static void +on_permit_user_login (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + CcRealmCommon *common; + ActUserManager *manager; + GError *error = NULL; + gchar *login; + + if (g_cancellable_is_cancelled (self->cancellable)) { + g_object_unref (self); + return; + } + + common = CC_REALM_COMMON (source); + cc_realm_common_call_change_login_policy_finish (common, result, &error); + if (error == NULL) { + + /* + * Now tell the account service about this user. The account service + * should also lookup information about this via the realm and make + * sure all that is functional. + */ + manager = act_user_manager_get_default (); + login = cc_realm_calculate_login (common, gtk_entry_get_text (self->enterprise_login_entry)); + g_return_if_fail (login != NULL); + + g_debug ("Caching remote user: %s", login); + + act_user_manager_cache_user_async (manager, login, self->cancellable, + on_register_user, g_object_ref (self)); + + g_free (login); + + } else { + show_error_dialog (self, _("Failed to register account"), error); + g_message ("Couldn't permit logins on account: %s", error->message); + finish_action (self); + g_error_free (error); + } + + g_object_unref (self); +} + +static void +enterprise_permit_user_login (CcAddUserDialog *self) +{ + CcRealmCommon *common; + gchar *login; + const gchar *add[2]; + const gchar *remove[1]; + GVariant *options; + + common = cc_realm_object_get_common (self->selected_realm); + if (common == NULL) { + g_debug ("Failed to register account: failed to get d-bus interface"); + show_error_dialog (self, _("Failed to register account"), NULL); + finish_action (self); + return; + } + + login = cc_realm_calculate_login (common, gtk_entry_get_text (self->enterprise_login_entry)); + g_return_if_fail (login != NULL); + + add[0] = login; + add[1] = NULL; + remove[0] = NULL; + + g_debug ("Permitting login for: %s", login); + options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0); + + cc_realm_common_call_change_login_policy (common, "", + add, remove, options, + self->cancellable, + on_permit_user_login, + g_object_ref (self)); + + g_object_unref (common); + g_free (login); +} + +static void +on_join_response (CcAddUserDialog *self, + gint response, + GtkDialog *dialog) +{ + gtk_widget_hide (GTK_WIDGET (dialog)); + if (response != GTK_RESPONSE_OK) { + finish_action (self); + return; + } + + g_debug ("Logging in as admin user: %s", gtk_entry_get_text (self->join_name)); + + /* Prompted for some admin credentials, try to use them to log in */ + cc_realm_login (self->selected_realm, + gtk_entry_get_text (self->join_name), + gtk_entry_get_text (self->join_password), + self->cancellable, + on_join_login, + g_object_ref (self)); +} + +static void +join_show_prompt (CcAddUserDialog *self, + GError *error) +{ + CcRealmKerberosMembership *membership; + CcRealmKerberos *kerberos; + const gchar *name; + + gtk_entry_set_text (self->join_password, ""); + gtk_widget_grab_focus (GTK_WIDGET (self->join_password)); + + kerberos = cc_realm_object_get_kerberos (self->selected_realm); + membership = cc_realm_object_get_kerberos_membership (self->selected_realm); + + gtk_label_set_text (self->join_domain, + cc_realm_kerberos_get_domain_name (kerberos)); + + clear_entry_validation_error (self->join_name); + clear_entry_validation_error (self->join_password); + + if (!self->join_prompted) { + name = cc_realm_kerberos_membership_get_suggested_administrator (membership); + if (name && !g_str_equal (name, "")) { + g_debug ("Suggesting admin user: %s", name); + gtk_entry_set_text (self->join_name, name); + } else { + gtk_widget_grab_focus (GTK_WIDGET (self->join_name)); + } + + } else if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_PASSWORD)) { + g_debug ("Bad admin password: %s", error->message); + set_entry_validation_error (self->join_password, error->message); + + } else { + g_debug ("Admin login failure: %s", error->message); + g_dbus_error_strip_remote_error (error); + set_entry_validation_error (self->join_name, error->message); + } + + g_debug ("Showing admin password dialog"); + gtk_window_set_transient_for (GTK_WINDOW (self->join_dialog), GTK_WINDOW (self)); + gtk_window_set_modal (GTK_WINDOW (self->join_dialog), TRUE); + gtk_window_present (GTK_WINDOW (self->join_dialog)); + + self->join_prompted = TRUE; + g_object_unref (kerberos); + g_object_unref (membership); + + /* And now we wait for on_join_response() */ +} + +static void +on_join_login (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + GError *error = NULL; + GBytes *creds; + + if (g_cancellable_is_cancelled (self->cancellable)) { + g_object_unref (self); + return; + } + + creds = cc_realm_login_finish (result, &error); + + /* Logged in as admin successfully, use creds to join domain */ + if (error == NULL) { + if (!cc_realm_join_as_admin (self->selected_realm, + gtk_entry_get_text (self->join_name), + gtk_entry_get_text (self->join_password), + creds, self->cancellable, on_realm_joined, + g_object_ref (self))) { + show_error_dialog (self, _("No supported way to authenticate with this domain"), NULL); + g_message ("Authenticating as admin is not supported by the realm"); + finish_action (self); + } + + g_bytes_unref (creds); + + /* Couldn't login as admin, show prompt again */ + } else { + join_show_prompt (self, error); + g_message ("Couldn't log in as admin to join domain: %s", error->message); + g_error_free (error); + } + + g_object_unref (self); +} + +static void +join_init (CcAddUserDialog *self) +{ + GtkBuilder *builder; + GError *error = NULL; + + builder = gtk_builder_new (); + + if (!gtk_builder_add_from_resource (builder, + "/org/gnome/control-center/user-accounts/join-dialog.ui", + &error)) { + g_error ("%s", error->message); + g_error_free (error); + return; + } + + self->join_dialog = GTK_DIALOG (gtk_builder_get_object (builder, "join-dialog")); + self->join_domain = GTK_LABEL (gtk_builder_get_object (builder, "join-domain")); + self->join_name = GTK_ENTRY (gtk_builder_get_object (builder, "join-name")); + self->join_password = GTK_ENTRY (gtk_builder_get_object (builder, "join-password")); + + g_signal_connect_object (self->join_dialog, "response", + G_CALLBACK (on_join_response), self, G_CONNECT_SWAPPED); + + g_object_unref (builder); +} + +static void +on_realm_joined (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + GError *error = NULL; + + if (g_cancellable_is_cancelled (self->cancellable)) { + g_object_unref (self); + return; + } + + cc_realm_join_finish (self->selected_realm, + result, &error); + + /* Yay, joined the domain, register the user locally */ + if (error == NULL) { + g_debug ("Joining realm completed successfully"); + enterprise_permit_user_login (self); + + /* Credential failure while joining domain, prompt for admin creds */ + } else if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_LOGIN) || + g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_PASSWORD)) { + g_debug ("Joining realm failed due to credentials"); + join_show_prompt (self, error); + + /* Other failure */ + } else { + show_error_dialog (self, _("Failed to join domain"), error); + g_message ("Failed to join the domain: %s", error->message); + finish_action (self); + } + + g_clear_error (&error); + g_object_unref (self); +} + +static void +on_realm_login (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + GError *error = NULL; + GBytes *creds = NULL; + const gchar *message; + + if (g_cancellable_is_cancelled (self->cancellable)) { + g_object_unref (self); + return; + } + + creds = cc_realm_login_finish (result, &error); + + /* + * User login is valid, but cannot authenticate right now (eg: user needs + * to change password at next login etc.) + */ + if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_CANNOT_AUTH)) { + g_clear_error (&error); + creds = NULL; + } + + if (error == NULL) { + + /* Already joined to the domain, just register this user */ + if (cc_realm_is_configured (self->selected_realm)) { + g_debug ("Already joined to this realm"); + enterprise_permit_user_login (self); + + /* Join the domain, try using the user's creds */ + } else if (creds == NULL || + !cc_realm_join_as_user (self->selected_realm, + gtk_entry_get_text (self->enterprise_login_entry), + gtk_entry_get_text (self->enterprise_password_entry), + creds, self->cancellable, + on_realm_joined, + g_object_ref (self))) { + + /* If we can't do user auth, try to authenticate as admin */ + g_debug ("Cannot join with user credentials"); + join_show_prompt (self, NULL); + } + + g_bytes_unref (creds); + + /* A problem with the user's login name or password */ + } else if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_LOGIN)) { + g_debug ("Problem with the user's login: %s", error->message); + message = _("That login name didn’t work.\nPlease try again."); + gtk_label_set_text (self->enterprise_hint_label, message); + finish_action (self); + gtk_widget_grab_focus (GTK_WIDGET (self->enterprise_login_entry)); + + } else if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_PASSWORD)) { + g_debug ("Problem with the user's password: %s", error->message); + message = _("That login password didn’t work.\nPlease try again."); + gtk_label_set_text (self->enterprise_hint_label, message); + finish_action (self); + gtk_widget_grab_focus (GTK_WIDGET (self->enterprise_password_entry)); + + /* Other login failure */ + } else { + g_dbus_error_strip_remote_error (error); + show_error_dialog (self, _("Failed to log into domain"), error); + g_message ("Couldn't log in as user: %s", error->message); + finish_action (self); + } + + g_clear_error (&error); + g_object_unref (self); +} + +static void +enterprise_check_login (CcAddUserDialog *self) +{ + g_assert (self->selected_realm); + + cc_realm_login (self->selected_realm, + gtk_entry_get_text (self->enterprise_login_entry), + gtk_entry_get_text (self->enterprise_password_entry), + self->cancellable, + on_realm_login, + g_object_ref (self)); +} + +static void +on_realm_discover_input (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + GError *error = NULL; + GList *realms; + gchar *message; + + if (g_cancellable_is_cancelled (self->cancellable)) { + g_object_unref (self); + return; + } + + realms = cc_realm_manager_discover_finish (self->realm_manager, + result, &error); + + /* Found a realm, log user into domain */ + if (error == NULL) { + g_assert (realms != NULL); + self->selected_realm = g_object_ref (realms->data); + + if (self->enterprise_check_credentials) { + enterprise_check_login (self); + } + set_entry_validation_checkmark (self->enterprise_domain_entry); + gtk_label_set_text (self->enterprise_domain_hint_label, DOMAIN_DEFAULT_HINT); + g_list_free_full (realms, g_object_unref); + + /* The domain is likely invalid*/ + } else { + g_message ("Couldn't discover domain: %s", error->message); + g_dbus_error_strip_remote_error (error); + + if (g_error_matches (error, CC_REALM_ERROR, CC_REALM_ERROR_GENERIC)) { + message = g_strdup (_("Unable to find the domain. Maybe you misspelled it?")); + } else { + message = g_strdup_printf ("%s.", error->message); + } + gtk_label_set_text (self->enterprise_domain_hint_label, message); + + g_free (message); + g_error_free (error); + + if (self->enterprise_check_credentials) { + finish_action (self); + self->enterprise_check_credentials = FALSE; + } + } + + if (!self->enterprise_check_credentials) { + finish_action (self); + dialog_validate (self); + } + + g_object_unref (self); +} + +static void +enterprise_check_domain (CcAddUserDialog *self) +{ + const gchar *domain; + + domain = gtk_entry_get_text (self->enterprise_domain_entry); + if (strlen (domain) == 0) { + gtk_label_set_text (self->enterprise_domain_hint_label, DOMAIN_DEFAULT_HINT); + return; + } + + begin_action (self); + + self->join_prompted = FALSE; + cc_realm_manager_discover (self->realm_manager, + domain, + self->cancellable, + on_realm_discover_input, + g_object_ref (self)); +} + +static void +enterprise_add_user (CcAddUserDialog *self) +{ + self->join_prompted = FALSE; + self->enterprise_check_credentials = TRUE; + begin_action (self); + enterprise_check_login (self); + +} + +static void +clear_realm_manager (CcAddUserDialog *self) +{ + if (self->realm_manager) { + g_signal_handlers_disconnect_by_func (self->realm_manager, + on_manager_realm_added, + self); + g_object_unref (self->realm_manager); + self->realm_manager = NULL; + } +} + +static void +on_realm_manager_created (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + GError *error = NULL; + GList *realms, *l; + + clear_realm_manager (self); + + self->realm_manager = cc_realm_manager_new_finish (result, &error); + if (error != NULL) { + g_warning ("Couldn't contact realmd service: %s", error->message); + g_object_unref (self); + g_error_free (error); + return; + } + + if (g_cancellable_is_cancelled (self->cancellable)) { + g_object_unref (self); + return; + } + + /* Lookup all the realm objects */ + realms = cc_realm_manager_get_realms (self->realm_manager); + for (l = realms; l != NULL; l = g_list_next (l)) + enterprise_add_realm (self, l->data); + g_list_free (realms); + g_signal_connect_object (self->realm_manager, "realm-added", + G_CALLBACK (on_manager_realm_added), self, G_CONNECT_SWAPPED); + + /* When no realms try to discover a sensible default, triggers realm-added signal */ + cc_realm_manager_discover (self->realm_manager, "", self->cancellable, + NULL, NULL); + + /* Show the 'Enterprise Login' stuff, and update mode */ + gtk_widget_show (GTK_WIDGET (self->enterprise_button)); + mode_change (self, self->mode); + g_object_unref (self); +} + +static void +on_realmd_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + cc_realm_manager_new (self->cancellable, on_realm_manager_created, + g_object_ref (self)); +} + +static void +on_realmd_disappeared (GDBusConnection *unused1, + const gchar *unused2, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + + clear_realm_manager (self); + gtk_list_store_clear (self->enterprise_realm_model); + gtk_widget_hide (GTK_WIDGET (self->enterprise_button)); + mode_change (self, MODE_LOCAL); +} + +static void +on_network_changed (GNetworkMonitor *monitor, + gboolean available, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + + if (self->mode != MODE_LOCAL) + mode_change (self, MODE_ENTERPRISE); +} + +static gboolean +enterprise_domain_timeout (CcAddUserDialog *self) +{ + GtkTreeIter iter; + + self->enterprise_domain_timeout_id = 0; + + if (gtk_combo_box_get_active_iter (self->enterprise_domain_combo, &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (self->enterprise_realm_model), &iter, 1, &self->selected_realm, -1); + set_entry_validation_checkmark (self->enterprise_domain_entry); + gtk_label_set_text (self->enterprise_domain_hint_label, DOMAIN_DEFAULT_HINT); + } + else { + enterprise_check_domain (self); + } + + return FALSE; +} + +static void +enterprise_domain_combo_changed_cb (CcAddUserDialog *self) +{ + if (self->enterprise_domain_timeout_id != 0) { + g_source_remove (self->enterprise_domain_timeout_id); + self->enterprise_domain_timeout_id = 0; + } + + g_clear_object (&self->selected_realm); + clear_entry_validation_error (self->enterprise_domain_entry); + self->enterprise_domain_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT, (GSourceFunc) enterprise_domain_timeout, self); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE); + + self->enterprise_domain_chosen = TRUE; + dialog_validate (self); +} + +static gboolean +enterprise_domain_combo_focus_out_event_cb (CcAddUserDialog *self) +{ + if (self->enterprise_domain_timeout_id != 0) { + g_source_remove (self->enterprise_domain_timeout_id); + self->enterprise_domain_timeout_id = 0; + } + + if (self->selected_realm == NULL) { + enterprise_check_domain (self); + } + + return FALSE; +} + +static void +enterprise_login_entry_changed_cb (CcAddUserDialog *self) +{ + dialog_validate (self); + clear_entry_validation_error (self->enterprise_login_entry); + clear_entry_validation_error (self->enterprise_password_entry); +} + +static void +enterprise_password_entry_changed_cb (CcAddUserDialog *self) +{ + dialog_validate (self); + clear_entry_validation_error (self->enterprise_password_entry); +} + +static void +dialog_validate (CcAddUserDialog *self) +{ + gboolean valid = FALSE; + + switch (self->mode) { + case MODE_LOCAL: + valid = local_validate (self); + break; + case MODE_ENTERPRISE: + valid = enterprise_validate (self); + break; + default: + valid = FALSE; + break; + } + + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), valid); +} + +static void +mode_change (CcAddUserDialog *self, + AccountMode mode) +{ + gboolean available; + GNetworkMonitor *monitor; + + if (mode != MODE_LOCAL) { + monitor = g_network_monitor_get_default (); + available = g_network_monitor_get_network_available (monitor); + mode = available ? MODE_ENTERPRISE : MODE_OFFLINE; + } + + switch (mode) { + default: + case MODE_LOCAL: + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->local_grid)); + gtk_widget_grab_focus (GTK_WIDGET (self->local_name_entry)); + gtk_toggle_button_set_active (self->enterprise_button, FALSE); + break; + case MODE_ENTERPRISE: + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->enterprise_grid)); + gtk_widget_grab_focus (GTK_WIDGET (self->enterprise_domain_entry)); + gtk_toggle_button_set_active (self->enterprise_button, TRUE); + break; + case MODE_OFFLINE: + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->offline_grid)); + gtk_toggle_button_set_active (self->enterprise_button, TRUE); + break; + } + + self->mode = mode; + dialog_validate (self); +} + +static void +enterprise_button_toggled_cb (CcAddUserDialog *self) +{ + AccountMode mode; + + mode = gtk_toggle_button_get_active (self->enterprise_button) ? MODE_ENTERPRISE : MODE_LOCAL; + mode_change (self, mode); +} + +static void +cc_add_user_dialog_init (CcAddUserDialog *self) +{ + GNetworkMonitor *monitor; + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->cancellable = g_cancellable_new (); + + self->local_password_mode = ACT_USER_PASSWORD_MODE_SET_AT_LOGIN; + dialog_validate (self); + update_password_strength (self); + local_username_timeout (self); + + enterprise_check_domain (self); + + self->realmd_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM, "org.freedesktop.realmd", + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + on_realmd_appeared, on_realmd_disappeared, + self, NULL); + + monitor = g_network_monitor_get_default (); + g_signal_connect_object (monitor, "network-changed", G_CALLBACK (on_network_changed), self, 0); + + join_init (self); + + mode_change (self, MODE_LOCAL); +} + +static void +on_permission_acquired (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data); + GError *error = NULL; + + /* Paired with begin_action in cc_add_user_dialog_response () */ + finish_action (self); + + if (g_permission_acquire_finish (self->permission, res, &error)) { + g_return_if_fail (g_permission_get_allowed (self->permission)); + add_button_clicked_cb (self); + } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to acquire permission: %s", error->message); + } + + g_clear_error (&error); + g_object_unref (self); +} + +static void +add_button_clicked_cb (CcAddUserDialog *self) +{ + /* We don't (or no longer) have necessary permissions */ + if (self->permission && !g_permission_get_allowed (self->permission)) { + begin_action (self); + g_permission_acquire_async (self->permission, self->cancellable, + on_permission_acquired, g_object_ref (self)); + return; + } + + switch (self->mode) { + case MODE_LOCAL: + local_create_user (self); + break; + case MODE_ENTERPRISE: + enterprise_add_user (self); + break; + default: + g_assert_not_reached (); + } +} + +static void +cc_add_user_dialog_dispose (GObject *obj) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (obj); + + if (self->cancellable) + g_cancellable_cancel (self->cancellable); + + g_clear_object (&self->user); + + if (self->realmd_watch) + g_bus_unwatch_name (self->realmd_watch); + self->realmd_watch = 0; + + if (self->realm_manager) { + g_signal_handlers_disconnect_by_func (self->realm_manager, + on_manager_realm_added, + self); + g_object_unref (self->realm_manager); + self->realm_manager = NULL; + } + + if (self->local_password_timeout_id != 0) { + g_source_remove (self->local_password_timeout_id); + self->local_password_timeout_id = 0; + } + + if (self->local_name_timeout_id != 0) { + g_source_remove (self->local_name_timeout_id); + self->local_name_timeout_id = 0; + } + + if (self->local_username_timeout_id != 0) { + g_source_remove (self->local_username_timeout_id); + self->local_username_timeout_id = 0; + } + + if (self->enterprise_domain_timeout_id != 0) { + g_source_remove (self->enterprise_domain_timeout_id); + self->enterprise_domain_timeout_id = 0; + } + + g_clear_pointer ((GtkWidget **)&self->join_dialog, gtk_widget_destroy); + + G_OBJECT_CLASS (cc_add_user_dialog_parent_class)->dispose (obj); +} + +static void +cc_add_user_dialog_finalize (GObject *obj) +{ + CcAddUserDialog *self = CC_ADD_USER_DIALOG (obj); + + if (self->cancellable) + g_object_unref (self->cancellable); + g_clear_object (&self->permission); + + G_OBJECT_CLASS (cc_add_user_dialog_parent_class)->finalize (obj); +} + +static void +cc_add_user_dialog_class_init (CcAddUserDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = cc_add_user_dialog_dispose; + object_class->finalize = cc_add_user_dialog_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/user-accounts/cc-add-user-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, add_button); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, enterprise_button); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, enterprise_domain_combo); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, enterprise_domain_entry); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, enterprise_domain_hint_label); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, enterprise_grid); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, enterprise_hint_label); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, enterprise_login_entry); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, enterprise_password_entry); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, enterprise_realm_model); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_account_type_standard); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_grid); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_hint_label); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_name_entry); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_username_combo); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_username_model); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_password_entry); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_password_radio); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_username_entry); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_username_hint_label); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_strength_indicator); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_verify_entry); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, local_verify_hint_label); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, offline_grid); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, spinner); + gtk_widget_class_bind_template_child (widget_class, CcAddUserDialog, stack); + + gtk_widget_class_bind_template_callback (widget_class, add_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, dialog_validate); + gtk_widget_class_bind_template_callback (widget_class, enterprise_button_toggled_cb); + gtk_widget_class_bind_template_callback (widget_class, enterprise_domain_combo_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, enterprise_domain_combo_focus_out_event_cb); + gtk_widget_class_bind_template_callback (widget_class, enterprise_login_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, enterprise_password_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, local_name_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, local_name_entry_focus_out_event_cb); + gtk_widget_class_bind_template_callback (widget_class, local_password_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, local_password_entry_icon_press_cb); + gtk_widget_class_bind_template_callback (widget_class, local_password_entry_key_press_event_cb); + gtk_widget_class_bind_template_callback (widget_class, local_password_radio_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, local_username_combo_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, local_username_combo_focus_out_event_cb); + gtk_widget_class_bind_template_callback (widget_class, local_verify_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, password_focus_out_event_cb); +} + +CcAddUserDialog * +cc_add_user_dialog_new (GPermission *permission) +{ + CcAddUserDialog *self; + + self = g_object_new (CC_TYPE_ADD_USER_DIALOG, "use-header-bar", TRUE, NULL); + + if (permission != NULL) + self->permission = g_object_ref (permission); + + return self; +} + +ActUser * +cc_add_user_dialog_get_user (CcAddUserDialog *self) +{ + g_return_val_if_fail (CC_IS_ADD_USER_DIALOG (self), NULL); + return self->user; +} diff --git a/panels/user-accounts/cc-add-user-dialog.h b/panels/user-accounts/cc-add-user-dialog.h new file mode 100644 index 0000000..c666d4d --- /dev/null +++ b/panels/user-accounts/cc-add-user-dialog.h @@ -0,0 +1,34 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#pragma once + +#include <act/act.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_ADD_USER_DIALOG (cc_add_user_dialog_get_type ()) +G_DECLARE_FINAL_TYPE (CcAddUserDialog, cc_add_user_dialog, CC, ADD_USER_DIALOG, GtkDialog) + +CcAddUserDialog *cc_add_user_dialog_new (GPermission *permission); +ActUser *cc_add_user_dialog_get_user (CcAddUserDialog *dialog); + +G_END_DECLS diff --git a/panels/user-accounts/cc-add-user-dialog.ui b/panels/user-accounts/cc-add-user-dialog.ui new file mode 100644 index 0000000..12a253a --- /dev/null +++ b/panels/user-accounts/cc-add-user-dialog.ui @@ -0,0 +1,860 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 3.8 --> + <!-- interface-naming-policy toplevel-contextual --> + <object class="GtkListStore" id="local_username_model"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + </object> + <template class="CcAddUserDialog" parent="GtkDialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="destroy_with_parent">True</property> + <property name="type_hint">dialog</property> + <property name="title" translatable="yes">Add User</property> + <property name="icon_name">system-users</property> + <property name="use_header_bar">1</property> + <child internal-child="headerbar"> + <object class="GtkHeaderBar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="show_close_button">False</property> + <child> + <object class="GtkButton" id="cancel_button"> + <property name="label" translatable="yes">_Cancel</property> + <property name="visible">True</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="valign">center</property> + <style> + <class name="text-button"/> + </style> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkButton" id="add_button"> + <property name="label" translatable="yes">_Add</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="valign">center</property> + <signal name="clicked" handler="add_button_clicked_cb" object="CcAddUserDialog" swapped="yes"/> + <style> + <class name="text-button"/> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + <child> + <object class="GtkSpinner" id="spinner"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + <child internal-child="vbox"> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="transition-type">none</property> + <child> + <object class="GtkGrid" id="local_grid"> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="column_spacing">6</property> + <property name="row_spacing">8</property> + <property name="border_width">20</property> + <property name="margin_end">20</property> + <child> + <object class="GtkComboBoxText" id="local_username_combo"> + <property name="visible">True</property> + <property name="has_entry">True</property> + <property name="entry_text_column">0</property> + <property name="model">local_username_model</property> + <property name="hexpand">True</property> + <signal name="changed" handler="local_username_combo_changed_cb" object="CcAddUserDialog" swapped="yes"/> + <signal name="focus-out-event" handler="local_username_combo_focus_out_event_cb" after="yes" object="CcAddUserDialog" swapped="yes"/> + <child internal-child="entry"> + <object class="GtkEntry" id="local_username_entry"> + <property name="activates_default">True</property> + <signal name="activate" handler="dialog_validate" object="CcAddUserDialog" swapped="yes"/> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="local_username_hint_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="yalign">0</property> + <property name="xalign">0</property> + <property name="label"></property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="height-request">50</property> + <property name="wrap">True</property> + <property name="wrap_mode">word-char</property> + <property name="hexpand">True</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.83"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="local_username_label"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Username</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">local_username_combo</property> + <property name="margin_start">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="local_name_entry"> + <property name="visible">True</property> + <property name="max-length">255</property> + <property name="can_focus">True</property> + <property name="activates_default">True</property> + <property name="hexpand">True</property> + <property name="has_focus">True</property> + <signal name="changed" handler="local_name_entry_changed_cb" object="CcAddUserDialog" swapped="yes"/> + <signal name="focus-out-event" handler="local_name_entry_focus_out_event_cb" after="yes" object="CcAddUserDialog" swapped="yes"/> + <signal name="activate" handler="dialog_validate" object="CcAddUserDialog" swapped="yes"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="local_name_label"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Full Name</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">local_name_entry</property> + <property name="margin_start">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="local_account_type_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <style> + <class name="linked"/> + </style> + <child> + <object class="GtkRadioButton" id="local_account_type_standard"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Standard</property> + <property name="draw_indicator">False</property> + <property name="height_request">35</property> + </object> + </child> + <child> + <object class="GtkRadioButton" id="account_type_admin"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Administrator</property> + <property name="draw_indicator">False</property> + <property name="height_request">35</property> + <property name="group">local_account_type_standard</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="local_account_type_label"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Account _Type</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">local_account_type_box</property> + <property name="margin_start">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Password</property> + <property name="margin_top">12</property> + <property name="halign">start</property> + <property name="margin_start">20</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="margin_start">20</property> + <child> + <object class="GtkRadioButton" id="local_password_login_radio"> + <property name="label" translatable="yes">Allow user to set a password when they next _login</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="local_password_radio"> + <property name="label" translatable="yes">Set a password _now</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <property name="group">local_password_login_radio</property> + <property name="use_underline">True</property> + <signal name="toggled" handler="local_password_radio_changed_cb" object="CcAddUserDialog" swapped="yes"/> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">5</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="local_password_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">local_password_entry</property> + <property name="margin_start">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">6</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="local_password_entry"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="has_tooltip">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <property name="hexpand">True</property> + <property name="activates_default">True</property> + <property name="input_purpose">password</property> + <signal name="notify::text" handler="local_password_entry_changed_cb" object="CcAddUserDialog" swapped="yes"/> + <signal name="icon-press" handler="local_password_entry_icon_press_cb" object="CcAddUserDialog" swapped="yes"/> + <signal name="focus-out-event" handler="password_focus_out_event_cb" after="yes" object="CcAddUserDialog" swapped="yes"/> + <signal name="key-press-event" handler="local_password_entry_key_press_event_cb" object="CcAddUserDialog" swapped="yes"/> + <signal name="activate" handler="dialog_validate" object="CcAddUserDialog" swapped="yes"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">6</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLevelBar" id="local_strength_indicator"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="mode">discrete</property> + <property name="max-value">5</property> + <property name="hexpand">True</property> + <offsets> + <offset name="strength-weak" value="1"/> + <offset name="strength-low" value="2"/> + <offset name="strength-medium" value="3"/> + <offset name="strength-good" value="4"/> + <offset name="strength-high" value="5"/> + </offsets> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">7</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="local_hint_label"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="yalign">0</property> + <property name="xalign">0</property> + <property name="label"></property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="height-request">50</property> + <property name="wrap">True</property> + <property name="wrap_mode">word-char</property> + <property name="hexpand">True</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.83"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">8</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="local_verify_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Confirm</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">local_verify_entry</property> + <property name="margin_start">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">9</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="local_verify_entry"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <property name="hexpand">True</property> + <property name="input_purpose">password</property> + <signal name="notify::text" handler="local_verify_entry_changed_cb" object="CcAddUserDialog" swapped="yes"/> + <signal name="focus-out-event" handler="password_focus_out_event_cb" after="yes" object="CcAddUserDialog" swapped="yes"/> + <signal name="activate" handler="dialog_validate" object="CcAddUserDialog" swapped="yes"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">9</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="local_verify_hint_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="yalign">0</property> + <property name="xalign">0</property> + <property name="label"></property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="wrap">True</property> + <property name="wrap_mode">word-char</property> + <property name="hexpand">True</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.83"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">10</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">_local</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="enterprise_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="row_spacing">8</property> + <property name="column_spacing">6</property> + <property name="border_width">20</property> + <property name="margin_end">20</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Enterprise login allows an existing centrally managed user account to be used on this device. You can also use this account to access company resources on the internet.</property> + <property name="wrap">True</property> + <property name="wrap_mode">word-char</property> + <property name="margin_bottom">20</property> + <property name="max_width_chars">55</property> + <property name="xalign">0</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="enterprise_domain_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Domain</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">enterprise_domain_combo</property> + <property name="margin_start">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="enterprise_login_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Username</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">enterprise_login_entry</property> + <property name="margin_start">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="enterprise_password_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">enterprise_password_entry</property> + <property name="margin_start">20</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="enterprise_hint_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="yalign">0</property> + <property name="xalign">0</property> + <property name="label"></property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="height-request">50</property> + <property name="wrap">True</property> + <property name="wrap_mode">word-char</property> + <property name="hexpand">True</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.83"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">5</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="enterprise_domain_combo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="has_entry">True</property> + <property name="entry_text_column">0</property> + <property name="model">enterprise_realm_model</property> + <signal name="changed" handler="enterprise_domain_combo_changed_cb" object="CcAddUserDialog" swapped="yes"/> + <signal name="focus-out-event" handler="enterprise_domain_combo_focus_out_event_cb" after="yes" object="CcAddUserDialog" swapped="yes"/> + <child internal-child="entry"> + <object class="GtkEntry" id="enterprise_domain_entry"> + <property name="can_focus">True</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="enterprise_domain_hint_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="yalign">0</property> + <property name="xalign">0</property> + <property name="label"></property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="height-request">50</property> + <property name="wrap">True</property> + <property name="wrap_mode">word-char</property> + <property name="hexpand">True</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.83"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + + <child> + <object class="GtkEntry" id="enterprise_login_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="activates_default">True</property> + <property name="invisible_char_set">True</property> + <property name="input_purpose">password</property> + <signal name="changed" handler="enterprise_login_entry_changed_cb" object="CcAddUserDialog" swapped="yes"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="enterprise_password_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <property name="activates_default">True</property> + <property name="invisible_char_set">True</property> + <property name="input_purpose">password</property> + <signal name="changed" handler="enterprise_password_entry_changed_cb" object="CcAddUserDialog" swapped="yes"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + </child> + <child> + <object class="GtkGrid" id="offline_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="column_spacing">6</property> + <property name="row_spacing">8</property> + <property name="border_width">20</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Enterprise login allows an existing centrally managed user account to be used on this device. You can also use this account to access company resources on the internet.</property> + <property name="wrap">True</property> + <property name="wrap_mode">word-char</property> + <property name="margin_bottom">20</property> + <property name="margin_end">20</property> + <property name="max_width_chars">55</property> + <property name="xalign">0</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">network-offline-symbolic</property> + <property name="pixel_size">160</property> + <property name="vexpand">True</property> + <property name="hexpand">True</property> + <property name="margin_bottom">6</property> + <property name="valign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">You are Offline</property> + <property name="yalign">0</property> + <property name="justify">center</property> + <property name="hexpand">True</property> + <attributes> + <attribute name="scale" value="1.6"/> + </attributes> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">You must be online in order to add enterprise users.</property> + <property name="yalign">0</property> + <property name="justify">center</property> + <property name="vexpand">True</property> + <property name="hexpand">True</property> + <attributes> + <attribute name="scale" value="1.2"/> + </attributes> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkToggleButton" id="enterprise_button"> + <property name="label" translatable="yes">_Enterprise Login</property> + <property name="visible">False</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <property name="border_width">20</property> + <signal name="toggled" handler="enterprise_button_toggled_cb" object="CcAddUserDialog" swapped="yes"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">cancel_button</action-widget> + </action-widgets> + </template> + <object class="GtkSizeGroup"> + <widgets> + <widget name="local_username_label"/> + <widget name="local_name_label"/> + <widget name="local_account_type_label"/> + <widget name="enterprise_domain_label"/> + <widget name="enterprise_login_label"/> + <widget name="enterprise_password_label"/> + <widget name="local_password_label"/> + <widget name="local_verify_label"/> + </widgets> + </object> + <object class="GtkSizeGroup"> + <widgets> + <widget name="local_username_combo"/> + <widget name="local_username_hint_label"/> + <widget name="local_name_entry"/> + <widget name="local_account_type_box"/> + <widget name="local_password_entry"/> + <widget name="local_hint_label"/> + <widget name="local_verify_entry"/> + <widget name="local_verify_hint_label"/> + <widget name="enterprise_domain_combo"/> + <widget name="enterprise_domain_hint_label"/> + <widget name="enterprise_login_entry"/> + <widget name="enterprise_password_entry"/> + <widget name="enterprise_hint_label"/> + </widgets> + </object> + <object class="GtkSizeGroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="local_account_type_standard"/> + <widget name="account_type_admin"/> + </widgets> + </object> + <object class="GtkSizeGroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="add_button"/> + <widget name="cancel_button"/> + </widgets> + </object> + <object class="GtkListStore" id="enterprise_realm_model"> + <columns> + <!-- column-name title --> + <column type="gchararray"/> + <!-- column-name realm --> + <column type="GObject"/> + </columns> + </object> +</interface> diff --git a/panels/user-accounts/cc-avatar-chooser.c b/panels/user-accounts/cc-avatar-chooser.c new file mode 100644 index 0000000..d0d4e1b --- /dev/null +++ b/panels/user-accounts/cc-avatar-chooser.c @@ -0,0 +1,663 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <stdlib.h> + +#include <gio/gunixoutputstream.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> +#include <act/act.h> +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-desktop-thumbnail.h> + +#ifdef HAVE_CHEESE +#include <cheese-avatar-chooser.h> +#include <cheese-camera-device.h> +#include <cheese-camera-device-monitor.h> +#endif /* HAVE_CHEESE */ + +#include "cc-avatar-chooser.h" +#include "cc-crop-area.h" +#include "user-utils.h" + +#define ROW_SPAN 5 +#define AVATAR_CHOOSER_PIXEL_SIZE 80 +#define PIXEL_SIZE 512 + +struct _CcAvatarChooser { + GtkPopover parent; + + GtkWidget *popup_button; + GtkWidget *crop_area; + GtkWidget *user_flowbox; + GtkWidget *flowbox; + GtkWidget *take_picture_button; + +#ifdef HAVE_CHEESE + CheeseCameraDeviceMonitor *monitor; + GCancellable *cancellable; + guint num_cameras; +#endif /* HAVE_CHEESE */ + + GnomeDesktopThumbnailFactory *thumb_factory; + GListStore *faces; + + ActUser *user; +}; + +G_DEFINE_TYPE (CcAvatarChooser, cc_avatar_chooser, GTK_TYPE_POPOVER) + +static void +crop_dialog_response (CcAvatarChooser *self, + gint response_id, + GtkWidget *dialog) +{ + GdkPixbuf *pb, *pb2; + + if (response_id != GTK_RESPONSE_ACCEPT) { + self->crop_area = NULL; + gtk_widget_destroy (dialog); + return; + } + + pb = cc_crop_area_get_picture (CC_CROP_AREA (self->crop_area)); + pb2 = gdk_pixbuf_scale_simple (pb, PIXEL_SIZE, PIXEL_SIZE, GDK_INTERP_BILINEAR); + + set_user_icon_data (self->user, pb2); + + g_object_unref (pb2); + g_object_unref (pb); + + self->crop_area = NULL; + gtk_widget_destroy (dialog); + + gtk_popover_popdown (GTK_POPOVER (self)); +} + +static void +cc_avatar_chooser_crop (CcAvatarChooser *self, + GdkPixbuf *pixbuf) +{ + GtkWidget *dialog; + + dialog = gtk_dialog_new_with_buttons ("", + GTK_WINDOW (gtk_widget_get_toplevel (self->popup_button)), + GTK_DIALOG_USE_HEADER_BAR, + _("_Cancel"), + GTK_RESPONSE_CANCEL, + _("Select"), + GTK_RESPONSE_ACCEPT, + NULL); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users"); + + g_signal_connect_object (G_OBJECT (dialog), "response", + G_CALLBACK (crop_dialog_response), self, G_CONNECT_SWAPPED); + + /* Content */ + self->crop_area = cc_crop_area_new (); + gtk_widget_show (self->crop_area); + cc_crop_area_set_min_size (CC_CROP_AREA (self->crop_area), 48, 48); + cc_crop_area_set_constrain_aspect (CC_CROP_AREA (self->crop_area), TRUE); + cc_crop_area_set_picture (CC_CROP_AREA (self->crop_area), pixbuf); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + self->crop_area, + TRUE, TRUE, 8); + + gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 300); + + gtk_widget_show (dialog); +} + +static void +file_chooser_response (CcAvatarChooser *self, + gint response, + GtkDialog *chooser) +{ + gchar *filename; + GError *error; + GdkPixbuf *pixbuf, *pixbuf2; + + if (response != GTK_RESPONSE_ACCEPT) { + gtk_widget_destroy (GTK_WIDGET (chooser)); + return; + } + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser)); + + error = NULL; + pixbuf = gdk_pixbuf_new_from_file (filename, &error); + if (pixbuf == NULL) { + g_warning ("Failed to load %s: %s", filename, error->message); + g_error_free (error); + } + g_free (filename); + + pixbuf2 = gdk_pixbuf_apply_embedded_orientation (pixbuf); + g_object_unref (pixbuf); + + gtk_widget_destroy (GTK_WIDGET (chooser)); + + cc_avatar_chooser_crop (self, pixbuf2); + g_object_unref (pixbuf2); +} + +static void +update_preview (GtkFileChooser *chooser, + GnomeDesktopThumbnailFactory *thumb_factory) +{ + gchar *uri; + + uri = gtk_file_chooser_get_uri (chooser); + + if (uri) { + GdkPixbuf *pixbuf = NULL; + char *mime_type = NULL; + GFile *file; + GFileInfo *file_info; + GtkWidget *preview; + + preview = gtk_file_chooser_get_preview_widget (chooser); + + file = g_file_new_for_uri (uri); + file_info = g_file_query_info (file, + "standard::*", + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + g_object_unref (file); + + if (file_info != NULL && + g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY) { + mime_type = g_strdup (g_file_info_get_content_type (file_info)); + g_object_unref (file_info); + } + + if (mime_type) { + pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumb_factory, + uri, + mime_type); + g_free (mime_type); + } + + gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), + GTK_RESPONSE_ACCEPT, + (pixbuf != NULL)); + + if (pixbuf != NULL) { + gtk_image_set_from_pixbuf (GTK_IMAGE (preview), pixbuf); + g_object_unref (pixbuf); + } + else { + gtk_image_set_from_icon_name (GTK_IMAGE (preview), + "dialog-question", + GTK_ICON_SIZE_DIALOG); + } + + g_free (uri); + } + + gtk_file_chooser_set_preview_widget_active (chooser, TRUE); +} + +static void +cc_avatar_chooser_select_file (CcAvatarChooser *self) +{ + GtkWidget *chooser; + const gchar *folder; + GtkWidget *preview; + GtkFileFilter *filter; + + chooser = gtk_file_chooser_dialog_new (_("Browse for more pictures"), + GTK_WINDOW (gtk_widget_get_toplevel (self->popup_button)), + GTK_FILE_CHOOSER_ACTION_OPEN, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_ACCEPT, + NULL); + + gtk_window_set_modal (GTK_WINDOW (chooser), TRUE); + + preview = gtk_image_new (); + gtk_widget_set_size_request (preview, 128, -1); + gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (chooser), preview); + gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (chooser), FALSE); + gtk_widget_show (preview); + + /* Preview has to be generated after default handler of "selection-changed" + * signal, otherwise dialog response sensitivity is rewritten (Bug 547988). + * Preview also has to be generated on "selection-changed" signal to reflect + * all changes (Bug 660877). */ + g_signal_connect_after (chooser, "selection-changed", + G_CALLBACK (update_preview), self->thumb_factory); + + folder = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); + if (folder) + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser), + folder); + + filter = gtk_file_filter_new (); + gtk_file_filter_add_pixbuf_formats (filter); + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (chooser), filter); + + g_signal_connect_object (chooser, "response", + G_CALLBACK (file_chooser_response), self, G_CONNECT_SWAPPED); + + gtk_window_present (GTK_WINDOW (chooser)); +} + +#ifdef HAVE_CHEESE +static gboolean +destroy_chooser (GtkWidget *chooser) +{ + gtk_widget_destroy (chooser); + return FALSE; +} + +static void +webcam_response_cb (CcAvatarChooser *self, + int response, + GtkDialog *dialog) +{ + if (response == GTK_RESPONSE_ACCEPT) { + GdkPixbuf *pb, *pb2; + + g_object_get (G_OBJECT (dialog), "pixbuf", &pb, NULL); + pb2 = gdk_pixbuf_scale_simple (pb, PIXEL_SIZE, PIXEL_SIZE, GDK_INTERP_BILINEAR); + + set_user_icon_data (self->user, pb2); + + g_object_unref (pb2); + g_object_unref (pb); + } + if (response != GTK_RESPONSE_DELETE_EVENT && + response != GTK_RESPONSE_NONE) + g_idle_add ((GSourceFunc) destroy_chooser, dialog); + + gtk_popover_popdown (GTK_POPOVER (self)); +} + +static void +webcam_icon_selected (CcAvatarChooser *self) +{ + GtkWidget *window; + + window = cheese_avatar_chooser_new (); + gtk_window_set_transient_for (GTK_WINDOW (window), + GTK_WINDOW (gtk_widget_get_toplevel (self->popup_button))); + gtk_window_set_modal (GTK_WINDOW (window), TRUE); + g_signal_connect_object (G_OBJECT (window), "response", + G_CALLBACK (webcam_response_cb), self, G_CONNECT_SWAPPED); + gtk_widget_show (window); +} + +static void +update_photo_menu_status (CcAvatarChooser *self) +{ + if (self->num_cameras == 0) + gtk_widget_set_visible (self->take_picture_button, FALSE); + else + gtk_widget_set_sensitive (self->take_picture_button, TRUE); +} + +static void +device_added (CcAvatarChooser *self) +{ + self->num_cameras++; + update_photo_menu_status (self); +} + +static void +device_removed (CcAvatarChooser *self) +{ + self->num_cameras--; + update_photo_menu_status (self); +} + +#endif /* HAVE_CHEESE */ + +static void +face_widget_activated (CcAvatarChooser *self, + GtkFlowBoxChild *child) +{ + const gchar *filename; + GtkWidget *image; + + image = gtk_bin_get_child (GTK_BIN (child)); + filename = g_object_get_data (G_OBJECT (image), "filename"); + + act_user_set_icon_file (self->user, filename); + + gtk_popover_popdown (GTK_POPOVER (self)); +} + +static GtkWidget * +create_face_widget (gpointer item, + gpointer user_data) +{ + g_autofree gchar *image_path = NULL; + g_autoptr(GdkPixbuf) source_pixbuf = NULL; + g_autoptr(GdkPixbuf) pixbuf = NULL; + GtkWidget *image; + + image_path = g_file_get_path (G_FILE (item)); + + source_pixbuf = gdk_pixbuf_new_from_file_at_size (image_path, + AVATAR_CHOOSER_PIXEL_SIZE, + AVATAR_CHOOSER_PIXEL_SIZE, + NULL); + if (source_pixbuf == NULL) + return NULL; + + pixbuf = round_image (source_pixbuf); + image = gtk_image_new_from_pixbuf (pixbuf); + gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_CHOOSER_PIXEL_SIZE); + gtk_widget_show (image); + + g_object_set_data_full (G_OBJECT (image), + "filename", g_steal_pointer (&image_path), g_free); + + return image; +} + +#ifdef HAVE_CHEESE +static void +setup_cheese_camera_device_monitor (CcAvatarChooser *self) +{ + g_signal_connect_object (G_OBJECT (self->monitor), "added", G_CALLBACK (device_added), self, G_CONNECT_SWAPPED); + g_signal_connect_object (G_OBJECT (self->monitor), "removed", G_CALLBACK (device_removed), self, G_CONNECT_SWAPPED); + cheese_camera_device_monitor_coldplug (self->monitor); + update_photo_menu_status (self); +} + +static void +cheese_camera_device_monitor_new_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + CcAvatarChooser *self = user_data; + GObject *ret; + + ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, NULL); + if (ret == NULL) + return; + + self->monitor = CHEESE_CAMERA_DEVICE_MONITOR (ret); + setup_cheese_camera_device_monitor (self); +} +#endif /* HAVE_CHEESE */ + +static GStrv +get_settings_facesdirs (void) +{ + g_autoptr(GSettings) settings = g_settings_new ("org.gnome.desktop.interface"); + g_auto(GStrv) settings_dirs = g_settings_get_strv (settings, "avatar-directories"); + GPtrArray *facesdirs = g_ptr_array_new (); + + if (settings_dirs != NULL) { + int i; + for (i = 0; settings_dirs[i] != NULL; i++) { + char *path = settings_dirs[i]; + if (g_strcmp0 (path, "") != 0) + g_ptr_array_add (facesdirs, g_strdup (path)); + } + } + g_ptr_array_add (facesdirs, NULL); + + return (GStrv) g_ptr_array_steal (facesdirs, NULL); +} + +static GStrv +get_system_facesdirs (void) +{ + const char * const * data_dirs; + GPtrArray *facesdirs; + int i; + + facesdirs = g_ptr_array_new (); + + data_dirs = g_get_system_data_dirs (); + for (i = 0; data_dirs[i] != NULL; i++) { + char *path = g_build_filename (data_dirs[i], "pixmaps", "faces", NULL); + g_ptr_array_add (facesdirs, path); + } + g_ptr_array_add (facesdirs, NULL); + return (GStrv) g_ptr_array_steal (facesdirs, NULL); +} + +static gboolean +add_faces_from_dirs (GListStore *faces, GStrv facesdirs, gboolean add_all) +{ + GFile *file, *dir; + GFileInfo *info; + GFileEnumerator *enumerator; + GFileType type; + const gchar *target; + guint i; + gboolean added_faces = FALSE; + + for (i = 0; facesdirs[i] != NULL; i++) { + dir = g_file_new_for_path (facesdirs[i]); + + enumerator = g_file_enumerate_children (dir, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," + G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + if (enumerator == NULL) { + g_object_unref (dir); + continue; + } + + while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) { + type = g_file_info_get_file_type (info); + if (type != G_FILE_TYPE_REGULAR && + type != G_FILE_TYPE_SYMBOLIC_LINK) { + g_object_unref (info); + continue; + } + + target = g_file_info_get_symlink_target (info); + if (target != NULL && g_str_has_prefix (target , "legacy/")) { + g_object_unref (info); + continue; + } + + file = g_file_get_child (dir, g_file_info_get_name (info)); + g_list_store_append (faces, file); + + g_object_unref (info); + added_faces = TRUE; + } + + g_file_enumerator_close (enumerator, NULL, NULL); + g_object_unref (enumerator); + g_object_unref (dir); + + if (added_faces && !add_all) + break; + } + return added_faces; +} + + +static void +setup_photo_popup (CcAvatarChooser *self) +{ + g_auto(GStrv) settings_facesdirs = NULL; + + self->faces = g_list_store_new (G_TYPE_FILE); + gtk_flow_box_bind_model (GTK_FLOW_BOX (self->flowbox), + G_LIST_MODEL (self->faces), + create_face_widget, + self, + NULL); + + g_signal_connect_object (self->flowbox, "child-activated", + G_CALLBACK (face_widget_activated), self, G_CONNECT_SWAPPED); + + settings_facesdirs = get_settings_facesdirs (); + + if (!add_faces_from_dirs (self->faces, settings_facesdirs, TRUE)) { + g_auto(GStrv) system_facesdirs = get_system_facesdirs (); + add_faces_from_dirs (self->faces, system_facesdirs, FALSE); + } + +#ifdef HAVE_CHEESE + gtk_widget_set_visible (self->take_picture_button, TRUE); + + self->cancellable = g_cancellable_new (); + g_async_initable_new_async (CHEESE_TYPE_CAMERA_DEVICE_MONITOR, + G_PRIORITY_DEFAULT, + self->cancellable, + cheese_camera_device_monitor_new_cb, + self, + NULL); +#endif /* HAVE_CHEESE */ +} + +static void +popup_icon_menu (CcAvatarChooser *self) +{ + gtk_popover_popup (GTK_POPOVER (self)); +} + +static gboolean +on_popup_button_button_pressed (CcAvatarChooser *self, + GdkEventButton *event) +{ + if (event->button == 1) { + if (!gtk_widget_get_visible (GTK_WIDGET (self))) { + popup_icon_menu (self); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->popup_button), TRUE); + } else { + gtk_popover_popdown (GTK_POPOVER (self)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->popup_button), FALSE); + } + + return TRUE; + } + + return FALSE; +} + +CcAvatarChooser * +cc_avatar_chooser_new (GtkWidget *button) +{ + CcAvatarChooser *self; + + self = g_object_new (CC_TYPE_AVATAR_CHOOSER, + "relative-to", button, + NULL); + + self->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL); + + /* Set up the popup */ + self->popup_button = button; + setup_photo_popup (self); + g_signal_connect_object (button, "toggled", + G_CALLBACK (popup_icon_menu), self, G_CONNECT_SWAPPED); + g_signal_connect_object (button, "button-press-event", + G_CALLBACK (on_popup_button_button_pressed), self, G_CONNECT_SWAPPED); + + return self; +} + +static void +cc_avatar_chooser_dispose (GObject *object) +{ + CcAvatarChooser *self = CC_AVATAR_CHOOSER (object); + + g_clear_object (&self->thumb_factory); +#ifdef HAVE_CHEESE + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + g_clear_object (&self->monitor); +#endif + g_clear_object (&self->user); + + G_OBJECT_CLASS (cc_avatar_chooser_parent_class)->dispose (object); +} + +static void +cc_avatar_chooser_init (CcAvatarChooser *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +cc_avatar_chooser_class_init (CcAvatarChooserClass *klass) +{ + GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass); + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (wclass, "/org/gnome/control-center/user-accounts/cc-avatar-chooser.ui"); + + gtk_widget_class_bind_template_child (wclass, CcAvatarChooser, user_flowbox); + gtk_widget_class_bind_template_child (wclass, CcAvatarChooser, flowbox); + gtk_widget_class_bind_template_child (wclass, CcAvatarChooser, take_picture_button); + + gtk_widget_class_bind_template_callback (wclass, cc_avatar_chooser_select_file); +#ifdef HAVE_CHEESE + gtk_widget_class_bind_template_callback (wclass, webcam_icon_selected); +#endif + + oclass->dispose = cc_avatar_chooser_dispose; +} + +static void +user_flowbox_activated (CcAvatarChooser *self) +{ + set_default_avatar (self->user); + + gtk_popover_popdown (GTK_POPOVER (self)); +} + +void +cc_avatar_chooser_set_user (CcAvatarChooser *self, + ActUser *user) +{ + g_autoptr(GdkPixbuf) source_pixbuf = NULL; + g_autoptr(GdkPixbuf) pixbuf = NULL; + GtkWidget *image; + + g_return_if_fail (self != NULL); + + if (self->user) { + gtk_container_foreach (GTK_CONTAINER (self->user_flowbox), (GtkCallback) gtk_widget_destroy, NULL); + g_object_unref (self->user); + self->user = NULL; + } + self->user = g_object_ref (user); + + source_pixbuf = generate_default_avatar (user, AVATAR_CHOOSER_PIXEL_SIZE); + pixbuf = round_image (source_pixbuf); + image = gtk_image_new_from_pixbuf (pixbuf); + gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_CHOOSER_PIXEL_SIZE); + gtk_widget_show (image); + gtk_container_add (GTK_CONTAINER (self->user_flowbox), image); + g_signal_connect_object (self->user_flowbox, "child-activated", G_CALLBACK (user_flowbox_activated), self, G_CONNECT_SWAPPED); +} + diff --git a/panels/user-accounts/cc-avatar-chooser.h b/panels/user-accounts/cc-avatar-chooser.h new file mode 100644 index 0000000..56a1699 --- /dev/null +++ b/panels/user-accounts/cc-avatar-chooser.h @@ -0,0 +1,39 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#pragma once + +#include <gtk/gtk.h> +#include <act/act.h> + +G_BEGIN_DECLS + +#define CC_TYPE_AVATAR_CHOOSER (cc_avatar_chooser_get_type()) + +G_DECLARE_FINAL_TYPE (CcAvatarChooser, cc_avatar_chooser, CC, AVATAR_CHOOSER, GtkPopover) + +typedef struct _CcAvatarChooser CcAvatarChooser; + +CcAvatarChooser *cc_avatar_chooser_new (GtkWidget *button); +void cc_avatar_chooser_free (CcAvatarChooser *dialog); +void cc_avatar_chooser_set_user (CcAvatarChooser *dialog, + ActUser *user); + +G_END_DECLS diff --git a/panels/user-accounts/cc-avatar-chooser.ui b/panels/user-accounts/cc-avatar-chooser.ui new file mode 100644 index 0000000..45bcbab --- /dev/null +++ b/panels/user-accounts/cc-avatar-chooser.ui @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 3.8 --> + <template class="CcAvatarChooser" parent="GtkPopover"> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="orientation">GTK_ORIENTATION_VERTICAL</property> + <property name="border-width">10</property> + <child> + <object class="GtkFlowBox" id="user_flowbox"> + <property name="visible">True</property> + <property name="border-width">10</property> + <property name="selection-mode">none</property> + <property name="homogeneous">True</property> + <property name="max-children-per-line">5</property> + <property name="column-spacing">10</property> + </object> + </child> + <child> + <object class="GtkFlowBox" id="flowbox"> + <property name="visible">True</property> + <property name="border-width">10</property> + <property name="selection-mode">none</property> + <property name="homogeneous">True</property> + <property name="max-children-per-line">5</property> + <property name="column-spacing">10</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="halign">GTK_ALIGN_CENTER</property> + <property name="border-width">10</property> + <property name="spacing">10</property> + <child> + <object class="GtkButton" id="take_picture_button"> + <property name="visible">True</property> + <property name="label" translatable="yes">Take a Picture…</property> + <signal name="clicked" handler="webcam_icon_selected" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkButton"> + <property name="visible">True</property> + <property name="label" translatable="yes">Select a File…</property> + <signal name="clicked" handler="cc_avatar_chooser_select_file" swapped="yes"/> + </object> + </child> + </object> + <packing> + <property name="pack-type">GTK_PACK_END</property> + </packing> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/user-accounts/cc-carousel.c b/panels/user-accounts/cc-carousel.c new file mode 100644 index 0000000..2c3cd99 --- /dev/null +++ b/panels/user-accounts/cc-carousel.c @@ -0,0 +1,438 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2016 (c) 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, see <http://www.gnu.org/licenses/>. + * + * Author: Felipe Borges <felipeborges@gnome.org> + */ + +#include "cc-carousel.h" + +#include <glib-object.h> +#include <gtk/gtk.h> + +#define ARROW_SIZE 20 + +struct _CcCarouselItem { + GtkRadioButton parent; + + gint page; +}; + +G_DEFINE_TYPE (CcCarouselItem, cc_carousel_item, GTK_TYPE_RADIO_BUTTON) + +GtkWidget * +cc_carousel_item_new (void) +{ + return g_object_new (CC_TYPE_CAROUSEL_ITEM, NULL); +} + +static void +cc_carousel_item_class_init (CcCarouselItemClass *klass) +{ +} + +static void +cc_carousel_item_init (CcCarouselItem *self) +{ + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (self), FALSE); + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)), + "carousel-item"); +} + +struct _CcCarousel { + GtkRevealer parent; + + GList *children; + gint visible_page; + CcCarouselItem *selected_item; + GtkWidget *last_box; + GtkWidget *arrow; + gint arrow_start_x; + + /* Widgets */ + GtkStack *stack; + GtkWidget *go_back_button; + GtkWidget *go_next_button; + + GtkStyleProvider *provider; +}; + +G_DEFINE_TYPE (CcCarousel, cc_carousel, GTK_TYPE_REVEALER) + +enum { + ITEM_ACTIVATED, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS] = { 0, }; + +#define ITEMS_PER_PAGE 3 + +static gint +cc_carousel_item_get_x (CcCarouselItem *item, + CcCarousel *carousel) +{ + GtkWidget *widget, *parent; + gint width; + gint dest_x = 0; + + parent = GTK_WIDGET (carousel->stack); + widget = GTK_WIDGET (item); + + width = gtk_widget_get_allocated_width (widget); + if (!gtk_widget_translate_coordinates (widget, + parent, + width / 2, + 0, + &dest_x, + NULL)) + return 0; + + return CLAMP (dest_x - ARROW_SIZE, + 0, + gtk_widget_get_allocated_width (parent)); +} + +static void +cc_carousel_move_arrow (CcCarousel *self) +{ + GtkStyleContext *context; + gchar *css; + gint end_x; + GtkSettings *settings; + gboolean animations; + + if (!self->selected_item) + return; + + end_x = cc_carousel_item_get_x (self->selected_item, self); + + context = gtk_widget_get_style_context (self->arrow); + if (self->provider) + gtk_style_context_remove_provider (context, self->provider); + g_clear_object (&self->provider); + + settings = gtk_widget_get_settings (GTK_WIDGET (self)); + g_object_get (settings, "gtk-enable-animations", &animations, NULL); + + /* Animate the arrow movement if animations are enabled. Otherwise, + * jump the arrow to the right location instantly. */ + if (animations) + { + css = g_strdup_printf ("@keyframes arrow_keyframes-%d {\n" + " from { margin-left: %dpx; }\n" + " to { margin-left: %dpx; }\n" + "}\n" + "* {\n" + " animation-name: arrow_keyframes-%d;\n" + "}\n", + end_x, self->arrow_start_x, end_x, end_x); + } + else + { + css = g_strdup_printf ("* { margin-left: %dpx }", end_x); + } + + self->provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ()); + gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (self->provider), css, -1, NULL); + gtk_style_context_add_provider (context, self->provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + g_free (css); +} + +static gint +get_last_page_number (CcCarousel *self) +{ + if (g_list_length (self->children) == 0) + return 0; + + return ((g_list_length (self->children) - 1) / ITEMS_PER_PAGE); +} + +static void +update_buttons_visibility (CcCarousel *self) +{ + gtk_widget_set_visible (self->go_back_button, (self->visible_page > 0)); + gtk_widget_set_visible (self->go_next_button, (self->visible_page < get_last_page_number (self))); +} + +/** + * cc_carousel_find_item: + * @carousel: an CcCarousel instance + * @data: user data passed to the comparison function + * @func: the function to call for each element. + * It should return 0 when the desired element is found + * + * Finds an CcCarousel item using the supplied function to find the + * desired element. + * Ideally useful for matching a model object and its correspondent + * widget. + * + * Returns: the found CcCarouselItem, or %NULL if it is not found + */ +CcCarouselItem * +cc_carousel_find_item (CcCarousel *self, + gconstpointer data, + GCompareFunc func) +{ + GList *list; + + list = self->children; + while (list != NULL) + { + if (!func (list->data, data)) + return list->data; + list = list->next; + } + + return NULL; +} + +static void +on_item_toggled (CcCarousel *self, + GdkEvent *event, + CcCarouselItem *item) +{ + cc_carousel_select_item (self, item); +} + +void +cc_carousel_select_item (CcCarousel *self, + CcCarouselItem *item) +{ + gboolean page_changed = TRUE; + GList *children; + + /* Select first user if none is specified */ + if (item == NULL) + { + if (self->children != NULL) + item = self->children->data; + else + return; + } + + if (self->selected_item != NULL) + { + page_changed = (self->selected_item->page != item->page); + self->arrow_start_x = cc_carousel_item_get_x (self->selected_item, self); + } + + self->selected_item = item; + self->visible_page = item->page; + g_signal_emit (self, signals[ITEM_ACTIVATED], 0, item); + + if (!page_changed) + { + cc_carousel_move_arrow (self); + return; + } + + children = gtk_container_get_children (GTK_CONTAINER (self->stack)); + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (g_list_nth_data (children, self->visible_page))); + + update_buttons_visibility (self); + + /* cc_carousel_move_arrow is called from on_transition_running */ +} + +static void +cc_carousel_select_item_at_index (CcCarousel *self, + gint index) +{ + GList *l = NULL; + + l = g_list_nth (self->children, index); + cc_carousel_select_item (self, l->data); +} + +static void +cc_carousel_goto_previous_page (GtkWidget *button, + gpointer user_data) +{ + CcCarousel *self = CC_CAROUSEL (user_data); + + self->visible_page--; + if (self->visible_page < 0) + self->visible_page = 0; + + /* Select first item of the page */ + cc_carousel_select_item_at_index (self, self->visible_page * ITEMS_PER_PAGE); +} + +static void +cc_carousel_goto_next_page (GtkWidget *button, + gpointer user_data) +{ + CcCarousel *self = CC_CAROUSEL (user_data); + gint last_page; + + last_page = get_last_page_number (self); + + self->visible_page++; + if (self->visible_page > last_page) + self->visible_page = last_page; + + /* Select first item of the page */ + cc_carousel_select_item_at_index (self, self->visible_page * ITEMS_PER_PAGE); +} + +static void +cc_carousel_add (GtkContainer *container, + GtkWidget *widget) +{ + CcCarousel *self = CC_CAROUSEL (container); + gboolean last_box_is_full; + + if (!CC_IS_CAROUSEL_ITEM (widget)) { + GTK_CONTAINER_CLASS (cc_carousel_parent_class)->add (container, widget); + return; + } + + gtk_style_context_add_class (gtk_widget_get_style_context (widget), "menu"); + gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE); + + self->children = g_list_append (self->children, widget); + CC_CAROUSEL_ITEM (widget)->page = get_last_page_number (self); + if (self->selected_item != NULL) + gtk_radio_button_join_group (GTK_RADIO_BUTTON (widget), GTK_RADIO_BUTTON (self->selected_item)); + g_signal_connect_object (widget, "button-press-event", G_CALLBACK (on_item_toggled), self, G_CONNECT_SWAPPED); + + last_box_is_full = ((g_list_length (self->children) - 1) % ITEMS_PER_PAGE == 0); + if (last_box_is_full) { + self->last_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_show (self->last_box); + gtk_widget_set_valign (self->last_box, GTK_ALIGN_CENTER); + gtk_container_add (GTK_CONTAINER (self->stack), self->last_box); + } + + gtk_widget_show_all (widget); + gtk_box_pack_start (GTK_BOX (self->last_box), widget, TRUE, FALSE, 10); + + update_buttons_visibility (self); +} + +void +cc_carousel_purge_items (CcCarousel *self) +{ + gtk_container_forall (GTK_CONTAINER (self->stack), + (GtkCallback) gtk_widget_destroy, + NULL); + + g_list_free (self->children); + self->children = NULL; + self->visible_page = 0; + self->selected_item = NULL; +} + +CcCarousel * +cc_carousel_new (void) +{ + return g_object_new (CC_TYPE_CAROUSEL, NULL); +} + +static void +cc_carousel_dispose (GObject *object) +{ + CcCarousel *self = CC_CAROUSEL (object); + + g_clear_object (&self->provider); + if (self->children != NULL) { + g_list_free (self->children); + self->children = NULL; + } + + G_OBJECT_CLASS (cc_carousel_parent_class)->dispose (object); +} + +static void +cc_carousel_class_init (CcCarouselClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + gtk_widget_class_set_template_from_resource (wclass, + "/org/gnome/control-center/user-accounts/cc-carousel.ui"); + + gtk_widget_class_bind_template_child (wclass, CcCarousel, stack); + gtk_widget_class_bind_template_child (wclass, CcCarousel, go_back_button); + gtk_widget_class_bind_template_child (wclass, CcCarousel, go_next_button); + gtk_widget_class_bind_template_child (wclass, CcCarousel, arrow); + + gtk_widget_class_bind_template_callback (wclass, cc_carousel_goto_previous_page); + gtk_widget_class_bind_template_callback (wclass, cc_carousel_goto_next_page); + + object_class->dispose = cc_carousel_dispose; + + container_class->add = cc_carousel_add; + + signals[ITEM_ACTIVATED] = g_signal_new ("item-activated", + CC_TYPE_CAROUSEL, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + CC_TYPE_CAROUSEL_ITEM); +} + +static void +on_size_allocate (CcCarousel *self) +{ + if (self->selected_item == NULL) + return; + + if (gtk_stack_get_transition_running (self->stack)) + return; + + self->arrow_start_x = cc_carousel_item_get_x (self->selected_item, self); + cc_carousel_move_arrow (self); +} + +static void +on_transition_running (CcCarousel *self) +{ + if (!gtk_stack_get_transition_running (self->stack)) + cc_carousel_move_arrow (self); +} + +static void +cc_carousel_init (CcCarousel *self) +{ + GtkStyleProvider *provider; + + gtk_widget_init_template (GTK_WIDGET (self)); + + provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ()); + gtk_css_provider_load_from_resource (GTK_CSS_PROVIDER (provider), + "/org/gnome/control-center/user-accounts/carousel.css"); + + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + provider, + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + g_object_unref (provider); + + g_signal_connect_object (self->stack, "size-allocate", G_CALLBACK (on_size_allocate), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->stack, "notify::transition-running", G_CALLBACK (on_transition_running), self, G_CONNECT_SWAPPED); +} + +guint +cc_carousel_get_item_count (CcCarousel *self) +{ + return g_list_length (self->children); +} diff --git a/panels/user-accounts/cc-carousel.h b/panels/user-accounts/cc-carousel.h new file mode 100644 index 0000000..8cd3f9a --- /dev/null +++ b/panels/user-accounts/cc-carousel.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2016 (c) 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, see <http://www.gnu.org/licenses/>. + * + * Author: Felipe Borges <felipeborges@gnome.org> + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_CAROUSEL_ITEM (cc_carousel_item_get_type ()) + +G_DECLARE_FINAL_TYPE (CcCarouselItem, cc_carousel_item, CC, CAROUSEL_ITEM, GtkRadioButton) + +#define CC_TYPE_CAROUSEL (cc_carousel_get_type ()) + +G_DECLARE_FINAL_TYPE (CcCarousel, cc_carousel, CC, CAROUSEL, GtkRevealer) + +GtkWidget *cc_carousel_item_new (void); + +CcCarousel *cc_carousel_new (void); + +void cc_carousel_purge_items (CcCarousel *self); + +CcCarouselItem *cc_carousel_find_item (CcCarousel *self, + gconstpointer data, + GCompareFunc func); + +void cc_carousel_select_item (CcCarousel *self, + CcCarouselItem *item); + +guint cc_carousel_get_item_count (CcCarousel *self); + +G_END_DECLS diff --git a/panels/user-accounts/cc-carousel.ui b/panels/user-accounts/cc-carousel.ui new file mode 100644 index 0000000..77ba44b --- /dev/null +++ b/panels/user-accounts/cc-carousel.ui @@ -0,0 +1,118 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 3.8 --> + <template class="CcCarousel" parent="GtkRevealer"> + <property name="transition_duration">400</property> + <property name="reveal-child">True</property> + <child> + <object class="GtkOverlay"> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="border_width">16</property> + <child> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="transition_duration">400</property> + <property name="transition_type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property> + <style> + <class name="location-bar"/> + </style> + </object> + </child> + <child type="overlay"> + <object class="GtkOverlay"> + <property name="visible">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> + <property name="border_width">12</property> + <child> + <object class="GtkButton" id="go_back_button"> + <property name="visible">False</property> + <property name="can_focus">True</property> + <property name="valign">center</property> + <style> + <class name="circular"/> + </style> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon-size">4</property> + <property name="icon_name">go-previous-symbolic</property> + </object> + </child> + <signal name="clicked" handler="cc_carousel_goto_previous_page" object="CcCarousel" swapped="no"/> + </object> + <packing> + <property name="pack_type">GTK_PACK_START</property> + </packing> + </child> + <child> + <object class="GtkButton" id="go_next_button"> + <property name="can_focus">True</property> + <property name="valign">center</property> + <style> + <class name="circular"/> + </style> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon-size">4</property> + <property name="icon_name">go-next-symbolic</property> + </object> + </child> + <signal name="clicked" handler="cc_carousel_goto_next_page" object="CcCarousel" swapped="no"/> + </object> + <packing> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </object> + </child> + <child type="overlay"> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="valign">GTK_ALIGN_END</property> + <style> + <class name="carousel-arrow-container"/> + </style> + <child> + <object class="GtkOverlay"> + <property name="visible">True</property> + <child> + <object class="GtkBox" id="arrow"> + <property name="visible">True</property> + <property name="halign">GTK_ALIGN_END</property> + <style> + <class name="carousel-arrow"/> + </style> + </object> + </child> + <child type="overlay"> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="halign">GTK_ALIGN_END</property> + <style> + <class name="carousel-inner-arrow"/> + </style> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="pass-through">True</property> + </packing> + </child> + </object> + <packing> + <property name="pass-through">True</property> + </packing> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/user-accounts/cc-crop-area.c b/panels/user-accounts/cc-crop-area.c new file mode 100644 index 0000000..c4a04a6 --- /dev/null +++ b/panels/user-accounts/cc-crop-area.c @@ -0,0 +1,819 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <stdlib.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "cc-crop-area.h" + +struct _CcCropArea { + GtkDrawingArea parent_instance; + + GdkPixbuf *browse_pixbuf; + GdkPixbuf *pixbuf; + GdkPixbuf *color_shifted; + gdouble scale; + GdkRectangle image; + GdkCursorType current_cursor; + GdkRectangle crop; + gint active_region; + gint last_press_x; + gint last_press_y; + gint base_width; + gint base_height; + gdouble aspect; +}; + +G_DEFINE_TYPE (CcCropArea, cc_crop_area, GTK_TYPE_DRAWING_AREA); + +static inline guchar +shift_color_byte (guchar b, + int shift) +{ + return CLAMP(b + shift, 0, 255); +} + +static void +shift_colors (GdkPixbuf *pixbuf, + gint red, + gint green, + gint blue, + gint alpha) +{ + gint x, y, offset, y_offset, rowstride, width, height; + guchar *pixels; + gint channels; + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + pixels = gdk_pixbuf_get_pixels (pixbuf); + channels = gdk_pixbuf_get_n_channels (pixbuf); + + for (y = 0; y < height; y++) { + y_offset = y * rowstride; + for (x = 0; x < width; x++) { + offset = y_offset + x * channels; + if (red != 0) + pixels[offset] = shift_color_byte (pixels[offset], red); + if (green != 0) + pixels[offset + 1] = shift_color_byte (pixels[offset + 1], green); + if (blue != 0) + pixels[offset + 2] = shift_color_byte (pixels[offset + 2], blue); + if (alpha != 0 && channels >= 4) + pixels[offset + 3] = shift_color_byte (pixels[offset + 3], blue); + } + } +} + +static void +update_pixbufs (CcCropArea *area) +{ + gint width; + gint height; + GtkAllocation allocation; + gdouble scale; + gint dest_width, dest_height; + GtkWidget *widget; + + widget = GTK_WIDGET (area); + gtk_widget_get_allocation (widget, &allocation); + + width = gdk_pixbuf_get_width (area->browse_pixbuf); + height = gdk_pixbuf_get_height (area->browse_pixbuf); + + scale = allocation.height / (gdouble)height; + if (scale * width > allocation.width) + scale = allocation.width / (gdouble)width; + + dest_width = width * scale; + dest_height = height * scale; + + if (area->pixbuf == NULL || + gdk_pixbuf_get_width (area->pixbuf) != allocation.width || + gdk_pixbuf_get_height (area->pixbuf) != allocation.height) { + g_clear_object (&area->pixbuf); + area->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (area->browse_pixbuf), + 8, + dest_width, dest_height); + gdk_pixbuf_fill (area->pixbuf, 0x0); + + gdk_pixbuf_scale (area->browse_pixbuf, + area->pixbuf, + 0, 0, + dest_width, dest_height, + 0, 0, + scale, scale, + GDK_INTERP_BILINEAR); + + g_clear_object (&area->color_shifted); + area->color_shifted = gdk_pixbuf_copy (area->pixbuf); + shift_colors (area->color_shifted, -32, -32, -32, 0); + + if (area->scale == 0.0) { + gdouble scale_to_80, scale_to_image, crop_scale; + + /* Scale the crop rectangle to 80% of the area, or less to fit the image */ + scale_to_80 = MIN ((gdouble)gdk_pixbuf_get_width (area->pixbuf) * 0.8 / area->base_width, + (gdouble)gdk_pixbuf_get_height (area->pixbuf) * 0.8 / area->base_height); + scale_to_image = MIN ((gdouble)dest_width / area->base_width, + (gdouble)dest_height / area->base_height); + crop_scale = MIN (scale_to_80, scale_to_image); + + area->crop.width = crop_scale * area->base_width / scale; + area->crop.height = crop_scale * area->base_height / scale; + area->crop.x = (gdk_pixbuf_get_width (area->browse_pixbuf) - area->crop.width) / 2; + area->crop.y = (gdk_pixbuf_get_height (area->browse_pixbuf) - area->crop.height) / 2; + } + + area->scale = scale; + area->image.x = (allocation.width - dest_width) / 2; + area->image.y = (allocation.height - dest_height) / 2; + area->image.width = dest_width; + area->image.height = dest_height; + } +} + +static void +crop_to_widget (CcCropArea *area, + GdkRectangle *crop) +{ + crop->x = area->image.x + area->crop.x * area->scale; + crop->y = area->image.y + area->crop.y * area->scale; + crop->width = area->crop.width * area->scale; + crop->height = area->crop.height * area->scale; +} + +typedef enum { + OUTSIDE, + INSIDE, + TOP, + TOP_LEFT, + TOP_RIGHT, + BOTTOM, + BOTTOM_LEFT, + BOTTOM_RIGHT, + LEFT, + RIGHT +} Location; + +static gboolean +cc_crop_area_draw (GtkWidget *widget, + cairo_t *cr) +{ + GdkRectangle crop; + gint width, height, ix, iy; + CcCropArea *uarea = CC_CROP_AREA (widget); + + if (uarea->browse_pixbuf == NULL) + return FALSE; + + update_pixbufs (uarea); + + width = gdk_pixbuf_get_width (uarea->pixbuf); + height = gdk_pixbuf_get_height (uarea->pixbuf); + crop_to_widget (uarea, &crop); + + ix = uarea->image.x; + iy = uarea->image.y; + + gdk_cairo_set_source_pixbuf (cr, uarea->color_shifted, ix, iy); + cairo_rectangle (cr, ix, iy, width, crop.y - iy); + cairo_rectangle (cr, ix, crop.y, crop.x - ix, crop.height); + cairo_rectangle (cr, crop.x + crop.width, crop.y, width - crop.width - (crop.x - ix), crop.height); + cairo_rectangle (cr, ix, crop.y + crop.height, width, height - crop.height - (crop.y - iy)); + cairo_fill (cr); + + gdk_cairo_set_source_pixbuf (cr, uarea->pixbuf, ix, iy); + cairo_rectangle (cr, crop.x, crop.y, crop.width, crop.height); + cairo_fill (cr); + + if (uarea->active_region != OUTSIDE) { + gint x1, x2, y1, y2; + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_set_line_width (cr, 1.0); + x1 = crop.x + crop.width / 3.0; + x2 = crop.x + 2 * crop.width / 3.0; + y1 = crop.y + crop.height / 3.0; + y2 = crop.y + 2 * crop.height / 3.0; + + cairo_move_to (cr, x1 + 0.5, crop.y); + cairo_line_to (cr, x1 + 0.5, crop.y + crop.height); + + cairo_move_to (cr, x2 + 0.5, crop.y); + cairo_line_to (cr, x2 + 0.5, crop.y + crop.height); + + cairo_move_to (cr, crop.x, y1 + 0.5); + cairo_line_to (cr, crop.x + crop.width, y1 + 0.5); + + cairo_move_to (cr, crop.x, y2 + 0.5); + cairo_line_to (cr, crop.x + crop.width, y2 + 0.5); + cairo_stroke (cr); + } + + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_line_width (cr, 1.0); + cairo_rectangle (cr, + crop.x + 0.5, + crop.y + 0.5, + crop.width - 1.0, + crop.height - 1.0); + cairo_stroke (cr); + + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_set_line_width (cr, 2.0); + cairo_rectangle (cr, + crop.x + 2.0, + crop.y + 2.0, + crop.width - 4.0, + crop.height - 4.0); + cairo_stroke (cr); + + return FALSE; +} + +typedef enum { + BELOW, + LOWER, + BETWEEN, + UPPER, + ABOVE +} Range; + +static Range +find_range (gint x, + gint min, + gint max) +{ + gint tolerance = 12; + + if (x < min - tolerance) + return BELOW; + if (x <= min + tolerance) + return LOWER; + if (x < max - tolerance) + return BETWEEN; + if (x <= max + tolerance) + return UPPER; + return ABOVE; +} + +static Location +find_location (GdkRectangle *rect, + gint x, + gint y) +{ + Range x_range, y_range; + Location location[5][5] = { + { OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE }, + { OUTSIDE, TOP_LEFT, TOP, TOP_RIGHT, OUTSIDE }, + { OUTSIDE, LEFT, INSIDE, RIGHT, OUTSIDE }, + { OUTSIDE, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT, OUTSIDE }, + { OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE } + }; + + x_range = find_range (x, rect->x, rect->x + rect->width); + y_range = find_range (y, rect->y, rect->y + rect->height); + + return location[y_range][x_range]; +} + +static void +update_cursor (CcCropArea *area, + gint x, + gint y) +{ + gint cursor_type; + GdkRectangle crop; + gint region; + + region = area->active_region; + if (region == OUTSIDE) { + crop_to_widget (area, &crop); + region = find_location (&crop, x, y); + } + + switch (region) { + case OUTSIDE: + cursor_type = GDK_LEFT_PTR; + break; + case TOP_LEFT: + cursor_type = GDK_TOP_LEFT_CORNER; + break; + case TOP: + cursor_type = GDK_TOP_SIDE; + break; + case TOP_RIGHT: + cursor_type = GDK_TOP_RIGHT_CORNER; + break; + case LEFT: + cursor_type = GDK_LEFT_SIDE; + break; + case INSIDE: + cursor_type = GDK_FLEUR; + break; + case RIGHT: + cursor_type = GDK_RIGHT_SIDE; + break; + case BOTTOM_LEFT: + cursor_type = GDK_BOTTOM_LEFT_CORNER; + break; + case BOTTOM: + cursor_type = GDK_BOTTOM_SIDE; + break; + case BOTTOM_RIGHT: + cursor_type = GDK_BOTTOM_RIGHT_CORNER; + break; + default: + g_assert_not_reached (); + } + + if (cursor_type != area->current_cursor) { + GdkCursor *cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (area)), + cursor_type); + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (area)), cursor); + g_object_unref (cursor); + area->current_cursor = cursor_type; + } +} + +static int +eval_radial_line (gdouble center_x, gdouble center_y, + gdouble bounds_x, gdouble bounds_y, + gdouble user_x) +{ + gdouble decision_slope; + gdouble decision_intercept; + + decision_slope = (bounds_y - center_y) / (bounds_x - center_x); + decision_intercept = -(decision_slope * bounds_x); + + return (int) (decision_slope * user_x + decision_intercept); +} + +static gboolean +cc_crop_area_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event) +{ + CcCropArea *area = CC_CROP_AREA (widget); + gint x, y; + gint delta_x, delta_y; + gint width, height; + gint adj_width, adj_height; + gint pb_width, pb_height; + GdkRectangle damage; + gint left, right, top, bottom; + gdouble new_width, new_height; + gdouble center_x, center_y; + gint min_width, min_height; + + if (area->browse_pixbuf == NULL) + return FALSE; + + update_cursor (area, event->x, event->y); + + crop_to_widget (area, &damage); + gtk_widget_queue_draw_area (widget, + damage.x - 1, damage.y - 1, + damage.width + 2, damage.height + 2); + + pb_width = gdk_pixbuf_get_width (area->browse_pixbuf); + pb_height = gdk_pixbuf_get_height (area->browse_pixbuf); + + x = (event->x - area->image.x) / area->scale; + y = (event->y - area->image.y) / area->scale; + + delta_x = x - area->last_press_x; + delta_y = y - area->last_press_y; + area->last_press_x = x; + area->last_press_y = y; + + left = area->crop.x; + right = area->crop.x + area->crop.width - 1; + top = area->crop.y; + bottom = area->crop.y + area->crop.height - 1; + + center_x = (left + right) / 2.0; + center_y = (top + bottom) / 2.0; + + switch (area->active_region) { + case INSIDE: + width = right - left + 1; + height = bottom - top + 1; + + left += delta_x; + right += delta_x; + top += delta_y; + bottom += delta_y; + + if (left < 0) + left = 0; + if (top < 0) + top = 0; + if (right > pb_width) + right = pb_width; + if (bottom > pb_height) + bottom = pb_height; + + adj_width = right - left + 1; + adj_height = bottom - top + 1; + if (adj_width != width) { + if (delta_x < 0) + right = left + width - 1; + else + left = right - width + 1; + } + if (adj_height != height) { + if (delta_y < 0) + bottom = top + height - 1; + else + top = bottom - height + 1; + } + + break; + + case TOP_LEFT: + if (area->aspect < 0) { + top = y; + left = x; + } + else if (y < eval_radial_line (center_x, center_y, left, top, x)) { + top = y; + new_width = (bottom - top) * area->aspect; + left = right - new_width; + } + else { + left = x; + new_height = (right - left) / area->aspect; + top = bottom - new_height; + } + break; + + case TOP: + top = y; + if (area->aspect > 0) { + new_width = (bottom - top) * area->aspect; + right = left + new_width; + } + break; + + case TOP_RIGHT: + if (area->aspect < 0) { + top = y; + right = x; + } + else if (y < eval_radial_line (center_x, center_y, right, top, x)) { + top = y; + new_width = (bottom - top) * area->aspect; + right = left + new_width; + } + else { + right = x; + new_height = (right - left) / area->aspect; + top = bottom - new_height; + } + break; + + case LEFT: + left = x; + if (area->aspect > 0) { + new_height = (right - left) / area->aspect; + bottom = top + new_height; + } + break; + + case BOTTOM_LEFT: + if (area->aspect < 0) { + bottom = y; + left = x; + } + else if (y < eval_radial_line (center_x, center_y, left, bottom, x)) { + left = x; + new_height = (right - left) / area->aspect; + bottom = top + new_height; + } + else { + bottom = y; + new_width = (bottom - top) * area->aspect; + left = right - new_width; + } + break; + + case RIGHT: + right = x; + if (area->aspect > 0) { + new_height = (right - left) / area->aspect; + bottom = top + new_height; + } + break; + + case BOTTOM_RIGHT: + if (area->aspect < 0) { + bottom = y; + right = x; + } + else if (y < eval_radial_line (center_x, center_y, right, bottom, x)) { + right = x; + new_height = (right - left) / area->aspect; + bottom = top + new_height; + } + else { + bottom = y; + new_width = (bottom - top) * area->aspect; + right = left + new_width; + } + break; + + case BOTTOM: + bottom = y; + if (area->aspect > 0) { + new_width = (bottom - top) * area->aspect; + right= left + new_width; + } + break; + + default: + return FALSE; + } + + min_width = area->base_width / area->scale; + min_height = area->base_height / area->scale; + + width = right - left + 1; + height = bottom - top + 1; + if (area->aspect < 0) { + if (left < 0) + left = 0; + if (top < 0) + top = 0; + if (right > pb_width) + right = pb_width; + if (bottom > pb_height) + bottom = pb_height; + + width = right - left + 1; + height = bottom - top + 1; + + switch (area->active_region) { + case LEFT: + case TOP_LEFT: + case BOTTOM_LEFT: + if (width < min_width) + left = right - min_width; + break; + case RIGHT: + case TOP_RIGHT: + case BOTTOM_RIGHT: + if (width < min_width) + right = left + min_width; + break; + + default: ; + } + + switch (area->active_region) { + case TOP: + case TOP_LEFT: + case TOP_RIGHT: + if (height < min_height) + top = bottom - min_height; + break; + case BOTTOM: + case BOTTOM_LEFT: + case BOTTOM_RIGHT: + if (height < min_height) + bottom = top + min_height; + break; + + default: ; + } + } + else { + if (left < 0 || top < 0 || + right > pb_width || bottom > pb_height || + width < min_width || height < min_height) { + left = area->crop.x; + right = area->crop.x + area->crop.width - 1; + top = area->crop.y; + bottom = area->crop.y + area->crop.height - 1; + } + } + + area->crop.x = left; + area->crop.y = top; + area->crop.width = right - left + 1; + area->crop.height = bottom - top + 1; + + crop_to_widget (area, &damage); + gtk_widget_queue_draw_area (widget, + damage.x - 1, damage.y - 1, + damage.width + 2, damage.height + 2); + + return FALSE; +} + +static gboolean +cc_crop_area_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + CcCropArea *area = CC_CROP_AREA (widget); + GdkRectangle crop; + + if (area->browse_pixbuf == NULL) + return FALSE; + + crop_to_widget (area, &crop); + + area->last_press_x = (event->x - area->image.x) / area->scale; + area->last_press_y = (event->y - area->image.y) / area->scale; + area->active_region = find_location (&crop, event->x, event->y); + + gtk_widget_queue_draw_area (widget, + crop.x - 1, crop.y - 1, + crop.width + 2, crop.height + 2); + + return FALSE; +} + +static gboolean +cc_crop_area_button_release_event (GtkWidget *widget, + GdkEventButton *event) +{ + CcCropArea *area = CC_CROP_AREA (widget); + GdkRectangle crop; + + if (area->browse_pixbuf == NULL) + return FALSE; + + crop_to_widget (area, &crop); + + area->last_press_x = -1; + area->last_press_y = -1; + area->active_region = OUTSIDE; + + gtk_widget_queue_draw_area (widget, + crop.x - 1, crop.y - 1, + crop.width + 2, crop.height + 2); + + return FALSE; +} + +static void +cc_crop_area_set_size_request (CcCropArea *area) +{ + gtk_widget_set_size_request (GTK_WIDGET (area), + area->base_width, + area->base_height); +} + +static void +cc_crop_area_finalize (GObject *object) +{ + CcCropArea *area = CC_CROP_AREA (object); + + g_clear_object (&area->browse_pixbuf); + g_clear_object (&area->pixbuf); + g_clear_object (&area->color_shifted); + + G_OBJECT_CLASS (cc_crop_area_parent_class)->finalize (object); +} + +static void +cc_crop_area_class_init (CcCropAreaClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = cc_crop_area_finalize; + widget_class->draw = cc_crop_area_draw; + widget_class->button_press_event = cc_crop_area_button_press_event; + widget_class->button_release_event = cc_crop_area_button_release_event; + widget_class->motion_notify_event = cc_crop_area_motion_notify_event; +} + +static void +cc_crop_area_init (CcCropArea *area) +{ + gtk_widget_add_events (GTK_WIDGET (area), GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK); + + area->scale = 0.0; + area->image.x = 0; + area->image.y = 0; + area->image.width = 0; + area->image.height = 0; + area->active_region = OUTSIDE; + area->base_width = 48; + area->base_height = 48; + area->aspect = 1; + + cc_crop_area_set_size_request (area); +} + +GtkWidget * +cc_crop_area_new (void) +{ + return g_object_new (CC_TYPE_CROP_AREA, NULL); +} + +GdkPixbuf * +cc_crop_area_get_picture (CcCropArea *area) +{ + gint width, height; + + width = gdk_pixbuf_get_width (area->browse_pixbuf); + height = gdk_pixbuf_get_height (area->browse_pixbuf); + width = MIN (area->crop.width, width - area->crop.x); + height = MIN (area->crop.height, height - area->crop.y); + + return gdk_pixbuf_new_subpixbuf (area->browse_pixbuf, + area->crop.x, + area->crop.y, + width, height); +} + +void +cc_crop_area_set_picture (CcCropArea *area, + GdkPixbuf *pixbuf) +{ + int width; + int height; + + if (area->browse_pixbuf) { + g_object_unref (area->browse_pixbuf); + area->browse_pixbuf = NULL; + } + if (pixbuf) { + area->browse_pixbuf = g_object_ref (pixbuf); + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + } else { + width = 0; + height = 0; + } + + area->crop.width = 2 * area->base_width; + area->crop.height = 2 * area->base_height; + area->crop.x = (width - area->crop.width) / 2; + area->crop.y = (height - area->crop.height) / 2; + + area->scale = 0.0; + area->image.x = 0; + area->image.y = 0; + area->image.width = 0; + area->image.height = 0; + + gtk_widget_queue_draw (GTK_WIDGET (area)); +} + +void +cc_crop_area_set_min_size (CcCropArea *area, + gint width, + gint height) +{ + area->base_width = width; + area->base_height = height; + + cc_crop_area_set_size_request (area); + + if (area->aspect > 0) { + area->aspect = area->base_width / (gdouble)area->base_height; + } +} + +void +cc_crop_area_set_constrain_aspect (CcCropArea *area, + gboolean constrain) +{ + if (constrain) { + area->aspect = area->base_width / (gdouble)area->base_height; + } + else { + area->aspect = -1; + } +} + diff --git a/panels/user-accounts/cc-crop-area.h b/panels/user-accounts/cc-crop-area.h new file mode 100644 index 0000000..d2bfab5 --- /dev/null +++ b/panels/user-accounts/cc-crop-area.h @@ -0,0 +1,40 @@ +/* + * Copyright © 2009 Bastien Nocera <hadess@hadess.net> + * + * Licensed under the GNU General Public License Version 2 + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_CROP_AREA (cc_crop_area_get_type ()) +G_DECLARE_FINAL_TYPE (CcCropArea, cc_crop_area, CC, CROP_AREA, GtkDrawingArea) + +GtkWidget *cc_crop_area_new (void); +GdkPixbuf *cc_crop_area_get_picture (CcCropArea *area); +void cc_crop_area_set_picture (CcCropArea *area, + GdkPixbuf *pixbuf); +void cc_crop_area_set_min_size (CcCropArea *area, + gint width, + gint height); +void cc_crop_area_set_constrain_aspect (CcCropArea *area, + gboolean constrain); + +G_END_DECLS diff --git a/panels/user-accounts/cc-fingerprint-dialog.c b/panels/user-accounts/cc-fingerprint-dialog.c new file mode 100644 index 0000000..b7894c0 --- /dev/null +++ b/panels/user-accounts/cc-fingerprint-dialog.c @@ -0,0 +1,1454 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authors: Marco Trevisan <marco.trevisan@canonical.com> + */ + +#include <glib/gi18n.h> +#include <cairo/cairo.h> + +#include "cc-fingerprint-dialog.h" + +#include "cc-fingerprint-manager.h" +#include "cc-fprintd-generated.h" +#include "cc-list-row.h" + +#include "config.h" + +/* Translate fprintd strings */ +#define TR(s) dgettext ("fprintd", s) +#include "fingerprint-strings.h" + +struct _CcFingerprintDialog +{ + GtkWindow parent_instance; + + GtkButton *back_button; + GtkButton *cancel_button; + GtkButton *delete_prints_button; + GtkButton *done_button; + GtkContainer *add_print_popover_box; + GtkEntry *enroll_print_entry; + GtkFlowBox *prints_gallery; + GtkHeaderBar *titlebar; + GtkImage *enroll_result_image; + GtkLabel *enroll_message; + GtkLabel *enroll_result_message; + GtkLabel *infobar_error; + GtkLabel *title; + GtkListBox *devices_list; + GtkPopoverMenu *add_print_popover; + GtkPopoverMenu *print_popover; + GtkSpinner *spinner; + GtkStack *stack; + GtkWidget *add_print_icon; + GtkWidget *delete_confirmation_infobar; + GtkWidget *device_selector; + GtkWidget *enroll_print_bin; + GtkWidget *enroll_result_icon; + GtkWidget *enrollment_view; + GtkWidget *error_infobar; + GtkWidget *no_devices_found; + GtkWidget *prints_manager; + + CcFingerprintManager *manager; + CcFprintdDevice *device; + gboolean claiming; + gboolean device_claimed; + gulong device_signal_id; + gulong device_name_owner_id; + GCancellable *cancellable; + GStrv enrolled_fingers; + const char *enrolling_finger; + guint enroll_stages_passed; + guint enroll_stage_passed_id; + gdouble enroll_progress; +}; + +/* TODO - fprintd and API changes required: + - Identify the finger when the enroll dialog is visible + + Only if device supports identification + · And only in such case support enrolling more than one finger + - Delete a single fingerprint | and remove the "Delete all" button + - Highlight the finger when the sensor is touched during enrollment + - Add customized labels to fingerprints + - Devices hotplug (object manager) + */ + +G_DEFINE_TYPE (CcFingerprintDialog, cc_fingerprint_dialog, GTK_TYPE_WINDOW) + +enum { + PROP_0, + PROP_MANAGER, + N_PROPS +}; + +#define N_VALID_FINGERS G_N_ELEMENTS (FINGER_IDS) - 1 +/* The order of the fingers here will affect the UI order */ +const char * FINGER_IDS[] = { + "right-index-finger", + "left-index-finger", + "right-thumb", + "right-middle-finger", + "right-ring-finger", + "right-little-finger", + "left-thumb", + "left-middle-finger", + "left-ring-finger", + "left-little-finger", + "any", +}; + +typedef enum { + ENROLL_STATE_NORMAL, + ENROLL_STATE_RETRY, + ENROLL_STATE_SUCCESS, + ENROLL_STATE_WARNING, + ENROLL_STATE_ERROR, + ENROLL_STATE_COMPLETED, + N_ENROLL_STATES, +} EnrollState; + +const char * ENROLL_STATE_CLASSES[N_ENROLL_STATES] = { + "", + "retry", + "success", + "warning", + "error", + "completed", +}; + +static GParamSpec *properties[N_PROPS]; + +CcFingerprintDialog * +cc_fingerprint_dialog_new (CcFingerprintManager *manager) +{ + return g_object_new (CC_TYPE_FINGERPRINT_DIALOG, + "fingerprint-manager", manager, + NULL); +} + +static void +disconnect_device_signals (CcFingerprintDialog *self) +{ + if (!self->device) + return; + + if (self->device_signal_id) + { + g_signal_handler_disconnect (self->device, self->device_signal_id); + self->device_signal_id = 0; + } + + if (self->device_name_owner_id) + { + g_signal_handler_disconnect (self->device, self->device_name_owner_id); + self->device_name_owner_id = 0; + } +} + +static void +cc_fingerprint_dialog_dispose (GObject *object) +{ + CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object); + + g_clear_handle_id (&self->enroll_stage_passed_id, g_source_remove); + + if (self->device && self->device_claimed) + { + disconnect_device_signals (self); + + if (self->enrolling_finger) + cc_fprintd_device_call_enroll_stop_sync (self->device, NULL, NULL); + cc_fprintd_device_call_release (self->device, NULL, NULL, NULL); + } + + g_clear_object (&self->manager); + g_clear_object (&self->device); + g_clear_pointer (&self->enrolled_fingers, g_strfreev); + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + + G_OBJECT_CLASS (cc_fingerprint_dialog_parent_class)->dispose (object); +} + +static void +cc_fingerprint_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object); + + switch (prop_id) + { + case PROP_MANAGER: + g_value_set_object (value, self->manager); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_fingerprint_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object); + + switch (prop_id) + { + case PROP_MANAGER: + g_set_object (&self->manager, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +notify_error (CcFingerprintDialog *self, + const char *error_message) +{ + if (error_message) + gtk_label_set_label (self->infobar_error, error_message); + + gtk_widget_set_visible (self->error_infobar, error_message != NULL); +} + +static gboolean +fingerprint_icon_draw (GtkWidget *widget, + cairo_t *cr, + gdouble *progress_data) +{ + gdouble progress = 0.0f; + + if (progress_data) + progress = *progress_data; + + if (G_APPROX_VALUE (progress, 0.f, FLT_EPSILON) || progress > 1) + return FALSE; + + GTK_WIDGET_GET_CLASS (widget)->draw (widget, cr); + + if (progress > 0) + { + g_autoptr(GdkRGBA) outline_color = NULL; + GtkStyleContext *context; + GtkStateFlags state; + int outline_width; + int outline_offset; + int width; + int height; + int radius; + int delta; + + context = gtk_widget_get_style_context (widget); + gtk_style_context_save (context); + + state = gtk_style_context_get_state (context); + + gtk_style_context_add_class (context, "progress"); + gtk_style_context_get (context, state, + "outline-width", &outline_width, + "outline-offset", &outline_offset, + "outline-color", &outline_color, + NULL); + + width = gtk_widget_get_allocated_width (widget); + height = gtk_widget_get_allocated_height (widget); + radius = MIN (width / 2, height / 2) + outline_offset; + delta = radius - outline_width / 2; + + cairo_arc (cr, width / 2., height / 2., delta, + 1.5 * G_PI, (1.5 + progress * 2) * G_PI); + gdk_cairo_set_source_rgba (cr, outline_color); + + cairo_set_line_width (cr, MIN (outline_width, radius)); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_stroke (cr); + + gtk_style_context_restore (context); + } + + return TRUE; +} + +static GtkWidget * +fingerprint_icon_new (const char *icon_name, + const char *label_text, + GType icon_widget_type, + gpointer progress_data, + GtkWidget **out_icon, + GtkWidget **out_label) +{ + GtkStyleContext *context; + GtkWidget *box; + GtkWidget *label; + GtkWidget *image; + GtkWidget *icon_widget; + + g_return_val_if_fail (g_type_is_a (icon_widget_type, GTK_TYPE_WIDGET), NULL); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); + gtk_widget_set_name (box, "fingerprint-box"); + gtk_widget_set_hexpand (box, TRUE); + + image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DND); + + if (icon_widget_type == GTK_TYPE_IMAGE) + icon_widget = image; + else + icon_widget = g_object_new (icon_widget_type, NULL); + + if (progress_data) + g_signal_connect (image, "draw", G_CALLBACK (fingerprint_icon_draw), + progress_data); + + if (g_type_is_a (icon_widget_type, GTK_TYPE_BUTTON)) + { + gtk_button_set_image (GTK_BUTTON (icon_widget), image); + gtk_button_set_relief (GTK_BUTTON (icon_widget), GTK_RELIEF_NONE); + gtk_widget_set_can_focus (icon_widget, FALSE); + } + + gtk_widget_set_halign (icon_widget, GTK_ALIGN_CENTER); + gtk_widget_set_valign (icon_widget, GTK_ALIGN_CENTER); + gtk_widget_set_name (icon_widget, "fingerprint-image"); + + gtk_container_add (GTK_CONTAINER (box), icon_widget); + + context = gtk_widget_get_style_context (icon_widget); + gtk_style_context_add_class (context, "fingerprint-image"); + + label = gtk_label_new_with_mnemonic (label_text); + gtk_container_add (GTK_CONTAINER (box), label); + + context = gtk_widget_get_style_context (box); + gtk_style_context_add_class (context, "fingerprint-icon"); + + if (out_icon) + *out_icon = icon_widget; + + if (out_label) + *out_label = label; + + return box; +} + +static GtkWidget * +fingerprint_menu_button (const char *icon_name, + const char *label_text) +{ + GtkWidget *flowbox_child; + GtkWidget *button; + GtkWidget *label; + GtkWidget *box; + + box = fingerprint_icon_new (icon_name, label_text, GTK_TYPE_MENU_BUTTON, NULL, + &button, &label); + + flowbox_child = gtk_flow_box_child_new (); + gtk_widget_set_focus_on_click (flowbox_child, FALSE); + gtk_widget_set_name (flowbox_child, "fingerprint-flowbox"); + + gtk_container_add (GTK_CONTAINER (flowbox_child), box); + + g_object_set_data (G_OBJECT (flowbox_child), "button", button); + g_object_set_data (G_OBJECT (flowbox_child), "icon", + gtk_button_get_image (GTK_BUTTON (button))); + g_object_set_data (G_OBJECT (flowbox_child), "label", label); + g_object_set_data (G_OBJECT (button), "flowbox-child", flowbox_child); + + return flowbox_child; +} + +static gboolean +prints_visibility_filter (GtkFlowBoxChild *child, + gpointer user_data) +{ + CcFingerprintDialog *self = user_data; + const char *finger_id; + + if (gtk_stack_get_visible_child (self->stack) != self->prints_manager) + return FALSE; + + finger_id = g_object_get_data (G_OBJECT (child), "finger-id"); + + if (!finger_id) + return TRUE; + + if (!self->enrolled_fingers) + return FALSE; + + return g_strv_contains ((const gchar **) self->enrolled_fingers, finger_id); +} + +static void +update_prints_to_add_visibility (CcFingerprintDialog *self) +{ + g_autoptr(GList) print_buttons = NULL; + GList *l; + guint i; + + print_buttons = gtk_container_get_children (self->add_print_popover_box); + + for (i = 0, l = print_buttons; i < N_VALID_FINGERS && l; ++i, l = l->next) + { + GtkWidget *button = l->data; + gboolean enrolled; + + enrolled = self->enrolled_fingers && + g_strv_contains ((const gchar **) self->enrolled_fingers, + FINGER_IDS[i]); + + gtk_widget_set_visible (button, !enrolled); + } +} + +static void +update_prints_visibility (CcFingerprintDialog *self) +{ + update_prints_to_add_visibility (self); + + gtk_flow_box_invalidate_filter (self->prints_gallery); +} + +static void +list_enrolled_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + g_auto(GStrv) enrolled_fingers = NULL; + g_autoptr(GError) error = NULL; + CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object); + CcFingerprintDialog *self = user_data; + guint n_enrolled_fingers = 0; + + cc_fprintd_device_call_list_enrolled_fingers_finish (fprintd_device, + &enrolled_fingers, + res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + gtk_spinner_stop (self->spinner); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_print_icon), TRUE); + + if (self->device_claimed) + gtk_widget_set_sensitive (GTK_WIDGET (self->prints_manager), TRUE); + + if (error) + { + g_autofree char *dbus_error = g_dbus_error_get_remote_error (error); + + if (!dbus_error || !g_str_has_suffix (dbus_error, ".Error.NoEnrolledPrints")) + { + g_autofree char *error_message = NULL; + + g_dbus_error_strip_remote_error (error); + error_message = g_strdup_printf (_("Failed to list fingerprints: %s"), + error->message); + g_warning ("Listing of fingerprints on device %s failed: %s", + cc_fprintd_device_get_name (self->device), error->message); + notify_error (self, error_message); + return; + } + } + else + { + n_enrolled_fingers = g_strv_length (enrolled_fingers); + } + + self->enrolled_fingers = g_steal_pointer (&enrolled_fingers); + gtk_flow_box_set_max_children_per_line (self->prints_gallery, + MIN (3, n_enrolled_fingers + 1)); + + update_prints_visibility (self); + + if (n_enrolled_fingers == N_VALID_FINGERS) + gtk_widget_set_sensitive (self->add_print_icon, FALSE); + + if (n_enrolled_fingers > 0) + gtk_widget_show (GTK_WIDGET (self->delete_prints_button)); +} + +static void +update_prints_store (CcFingerprintDialog *self) +{ + ActUser *user; + + g_assert_true (CC_FPRINTD_IS_DEVICE (self->device)); + + gtk_spinner_start (self->spinner); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_print_icon), FALSE); + gtk_widget_hide (GTK_WIDGET (self->delete_prints_button)); + + g_clear_pointer (&self->enrolled_fingers, g_strfreev); + + user = cc_fingerprint_manager_get_user (self->manager); + cc_fprintd_device_call_list_enrolled_fingers (self->device, + act_user_get_user_name (user), + self->cancellable, + list_enrolled_cb, + self); +} + +static void +delete_prints_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object); + CcFingerprintDialog *self = user_data; + + cc_fprintd_device_call_delete_enrolled_fingers2_finish (fprintd_device, res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + if (error) + { + g_autofree char *error_message = NULL; + + g_dbus_error_strip_remote_error (error); + error_message = g_strdup_printf (_("Failed to delete saved fingerprints: %s"), + error->message); + g_warning ("Deletion of fingerprints on device %s failed: %s", + cc_fprintd_device_get_name (self->device), error->message); + notify_error (self, error_message); + } + + update_prints_store (self); + cc_fingerprint_manager_update_state (self->manager, NULL, NULL); +} + +static void +delete_enrolled_prints (CcFingerprintDialog *self) +{ + g_return_if_fail (self->device_claimed); + + gtk_widget_set_sensitive (GTK_WIDGET (self->prints_manager), FALSE); + gtk_spinner_start (self->spinner); + + cc_fprintd_device_call_delete_enrolled_fingers2 (self->device, + self->cancellable, + delete_prints_cb, + self); +} + +static const char * +get_finger_name (const char *finger_id) +{ + if (g_str_equal (finger_id, "left-thumb")) + return _("Left thumb"); + if (g_str_equal (finger_id, "left-middle-finger")) + return _("Left middle finger"); + if (g_str_equal (finger_id, "left-index-finger")) + return _("_Left index finger"); + if (g_str_equal (finger_id, "left-ring-finger")) + return _("Left ring finger"); + if (g_str_equal (finger_id, "left-little-finger")) + return _("Left little finger"); + if (g_str_equal (finger_id, "right-thumb")) + return _("Right thumb"); + if (g_str_equal (finger_id, "right-middle-finger")) + return _("Right middle finger"); + if (g_str_equal (finger_id, "right-index-finger")) + return _("_Right index finger"); + if (g_str_equal (finger_id, "right-ring-finger")) + return _("Right ring finger"); + if (g_str_equal (finger_id, "right-little-finger")) + return _("Right little finger"); + + g_return_val_if_reached (_("Unknown Finger")); +} + +static gboolean +have_multiple_devices (CcFingerprintDialog *self) +{ + g_autoptr(GList) devices_rows = NULL; + + devices_rows = gtk_container_get_children (GTK_CONTAINER (self->devices_list)); + + return devices_rows && devices_rows->next; +} + +static void +set_enroll_result_message (CcFingerprintDialog *self, + EnrollState enroll_state, + const char *message) +{ + GtkStyleContext *style_context; + const char *icon_name; + guint i; + + g_return_if_fail (enroll_state >= 0 && enroll_state < N_ENROLL_STATES); + + style_context = gtk_widget_get_style_context (self->enroll_result_icon); + + switch (enroll_state) + { + case ENROLL_STATE_WARNING: + case ENROLL_STATE_ERROR: + icon_name = "fingerprint-detection-warning-symbolic"; + break; + case ENROLL_STATE_COMPLETED: + icon_name = "fingerprint-detection-complete-symbolic"; + break; + default: + icon_name = "fingerprint-detection-symbolic"; + } + + for (i = 0; i < N_ENROLL_STATES; ++i) + gtk_style_context_remove_class (style_context, ENROLL_STATE_CLASSES[i]); + + gtk_style_context_add_class (style_context, ENROLL_STATE_CLASSES[enroll_state]); + + gtk_image_set_from_icon_name (self->enroll_result_image, icon_name, GTK_ICON_SIZE_DND); + gtk_label_set_label (self->enroll_result_message, message); +} + +static gboolean +stage_passed_timeout_cb (gpointer user_data) +{ + CcFingerprintDialog *self = user_data; + const char *current_message; + + current_message = gtk_label_get_label (self->enroll_result_message); + set_enroll_result_message (self, ENROLL_STATE_NORMAL, current_message); + self->enroll_stage_passed_id = 0; + + return FALSE; +} + +static void +handle_enroll_signal (CcFingerprintDialog *self, + const char *result, + gboolean done) +{ + gboolean completed; + + g_return_if_fail (self->enrolling_finger); + + g_debug ("Device enroll result message: %s, done: %d", result, done); + + completed = g_str_equal (result, "enroll-completed"); + g_clear_handle_id (&self->enroll_stage_passed_id, g_source_remove); + + if (g_str_equal (result, "enroll-stage-passed") || completed) + { + guint enroll_stages; + + enroll_stages = cc_fprintd_device_get_num_enroll_stages (self->device); + + self->enroll_stages_passed++; + + if (enroll_stages > 0) + self->enroll_progress = + MIN (1.0f, self->enroll_stages_passed / (double) enroll_stages); + else + g_warning ("The device %s requires an invalid number of enroll stages (%u)", + cc_fprintd_device_get_name (self->device), enroll_stages); + + g_debug ("Enroll state passed, %u/%u (%.2f%%)", + self->enroll_stages_passed, (guint) enroll_stages, + self->enroll_progress); + + if (!completed) + { + set_enroll_result_message (self, ENROLL_STATE_SUCCESS, NULL); + + self->enroll_stage_passed_id = + g_timeout_add (750, stage_passed_timeout_cb, self); + } + else + { + if (!G_APPROX_VALUE (self->enroll_progress, 1.0f, FLT_EPSILON)) + { + g_warning ("Device marked enroll as completed, but progress is at %.2f", + self->enroll_progress); + self->enroll_progress = 1.0f; + } + } + } + else if (!done) + { + const char *scan_type; + const char *message; + gboolean is_swipe; + + scan_type = cc_fprintd_device_get_scan_type (self->device); + is_swipe = g_str_equal (scan_type, "swipe"); + + message = TR (enroll_result_str_to_msg (result, is_swipe)); + set_enroll_result_message (self, ENROLL_STATE_RETRY, message); + + self->enroll_stage_passed_id = + g_timeout_add (850, stage_passed_timeout_cb, self); + } + + if (done) + { + if (completed) + { + /* TRANSLATORS: This is the message shown when the fingerprint + * enrollment has been completed successfully */ + set_enroll_result_message (self, ENROLL_STATE_COMPLETED, + C_("Fingerprint enroll state", "Complete")); + gtk_widget_set_sensitive (GTK_WIDGET (self->cancel_button), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (self->done_button), TRUE); + gtk_widget_grab_focus (GTK_WIDGET (self->done_button)); + } + else + { + const char *message; + + if (g_str_equal (result, "enroll-disconnected")) + message = _("Fingerprint device disconnected"); + else if (g_str_equal (result, "enroll-data-full")) + message = _("Fingerprint device storage is full"); + else + message = _("Failed to enroll new fingerprint"); + + set_enroll_result_message (self, ENROLL_STATE_WARNING, message); + } + } +} + +static void +enroll_start_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object); + CcFingerprintDialog *self = user_data; + + cc_fprintd_device_call_enroll_start_finish (fprintd_device, res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + gtk_spinner_stop (self->spinner); + + if (error) + { + g_autofree char *error_message = NULL; + + self->enrolling_finger = NULL; + + g_dbus_error_strip_remote_error (error); + error_message = g_strdup_printf (_("Failed to start enrollment: %s"), + error->message); + g_warning ("Enrollment on device %s failed: %s", + cc_fprintd_device_get_name (self->device), error->message); + notify_error (self, error_message); + + set_enroll_result_message (self, ENROLL_STATE_ERROR, + C_("Fingerprint enroll state", + "Failed to enroll new fingerprint")); + gtk_widget_set_sensitive (self->enrollment_view, FALSE); + + return; + } +} + +static void +enroll_stop_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object); + CcFingerprintDialog *self = user_data; + + cc_fprintd_device_call_enroll_stop_finish (fprintd_device, res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self->enrolling_finger = NULL; + gtk_spinner_stop (self->spinner); + gtk_widget_set_sensitive (self->enrollment_view, TRUE); + gtk_stack_set_visible_child (self->stack, self->prints_manager); + + if (error) + { + g_autofree char *error_message = NULL; + + g_dbus_error_strip_remote_error (error); + error_message = g_strdup_printf (_("Failed to stop enrollment: %s"), + error->message); + g_warning ("Stopping enrollment on device %s failed: %s", + cc_fprintd_device_get_name (self->device), error->message); + notify_error (self, error_message); + + return; + } + + cc_fingerprint_manager_update_state (self->manager, NULL, NULL); +} + +static void +enroll_stop (CcFingerprintDialog *self) +{ + g_return_if_fail (self->enrolling_finger); + + gtk_spinner_start (self->spinner); + gtk_widget_set_sensitive (self->enrollment_view, FALSE); + cc_fprintd_device_call_enroll_stop (self->device, self->cancellable, + enroll_stop_cb, self); +} + +static char * +get_enrollment_string (CcFingerprintDialog *self, + const char *finger_id) +{ + char *ret; + const char *scan_type; + const char *device_name; + gboolean is_swipe; + + device_name = NULL; + scan_type = cc_fprintd_device_get_scan_type (self->device); + is_swipe = g_str_equal (scan_type, "swipe"); + + if (have_multiple_devices (self)) + device_name = cc_fprintd_device_get_name (self->device); + + ret = finger_str_to_msg (finger_id, device_name, is_swipe); + + if (ret) + return ret; + + return g_strdup (_("Repeatedly lift and place your finger on the reader to enroll your fingerprint")); +} + +static void +enroll_finger (CcFingerprintDialog *self, + const char *finger_id) +{ + g_auto(GStrv) tmp_finger_name = NULL; + g_autofree char *finger_name = NULL; + g_autofree char *enroll_message = NULL; + + g_return_if_fail (finger_id); + + self->enrolling_finger = finger_id; + self->enroll_progress = 0; + self->enroll_stages_passed = 0; + + g_debug ("Enrolling finger %s", finger_id); + + enroll_message = get_enrollment_string (self, finger_id); + tmp_finger_name = g_strsplit (get_finger_name (finger_id), "_", -1); + finger_name = g_strjoinv ("", tmp_finger_name); + + set_enroll_result_message (self, ENROLL_STATE_NORMAL, NULL); + gtk_stack_set_visible_child (self->stack, self->enrollment_view); + gtk_label_set_label (self->enroll_message, enroll_message); + gtk_entry_set_text (self->enroll_print_entry, finger_name); + gtk_spinner_start (self->spinner); + + cc_fprintd_device_call_enroll_start (self->device, finger_id, self->cancellable, + enroll_start_cb, self); +} + +static void +populate_enrollment_view (CcFingerprintDialog *self) +{ + GtkStyleContext *style_context; + + self->enroll_result_icon = + fingerprint_icon_new ("fingerprint-detection-symbolic", + NULL, + GTK_TYPE_IMAGE, + &self->enroll_progress, + (GtkWidget **) &self->enroll_result_image, + (GtkWidget **) &self->enroll_result_message); + + gtk_container_add (GTK_CONTAINER (self->enroll_print_bin), self->enroll_result_icon); + + style_context = gtk_widget_get_style_context (self->enroll_result_icon); + gtk_style_context_add_class (style_context, "enroll-status"); + + gtk_widget_show_all (self->enroll_print_bin); +} + +static void +reenroll_finger_cb (CcFingerprintDialog *self) +{ + GtkWidget *button; + GtkWidget *flowbox_child; + const char *finger_id; + + button = gtk_popover_get_relative_to (GTK_POPOVER (self->print_popover)); + flowbox_child = g_object_get_data (G_OBJECT (button), "flowbox-child"); + finger_id = g_object_get_data (G_OBJECT (flowbox_child), "finger-id"); + + enroll_finger (self, finger_id); +} + +static void +on_print_activated_cb (GtkFlowBox *flowbox, + GtkFlowBoxChild *child, + CcFingerprintDialog *self) +{ + GtkWidget *selected_button; + + selected_button = g_object_get_data (G_OBJECT (child), "button"); + gtk_button_clicked (GTK_BUTTON (selected_button)); +} + +static void +on_enroll_cb (CcFingerprintDialog *self, + GtkModelButton *button) +{ + const char *finger_id; + + finger_id = g_object_get_data (G_OBJECT (button), "finger-id"); + enroll_finger (self, finger_id); +} + +static void +populate_add_print_popover (CcFingerprintDialog *self) +{ + guint i; + + for (i = 0; i < N_VALID_FINGERS; ++i) + { + GtkWidget *finger_item; + + finger_item = gtk_model_button_new (); + gtk_button_set_label (GTK_BUTTON (finger_item), get_finger_name (FINGER_IDS[i])); + gtk_button_set_use_underline (GTK_BUTTON (finger_item), TRUE); + g_object_set_data (G_OBJECT (finger_item), "finger-id", (gpointer) FINGER_IDS[i]); + gtk_container_add (self->add_print_popover_box, finger_item); + + g_signal_connect_object (finger_item, "clicked", G_CALLBACK (on_enroll_cb), + self, G_CONNECT_SWAPPED); + } +} + +static void +populate_prints_gallery (CcFingerprintDialog *self) +{ + const char *add_print_label; + GtkWidget *button; + GtkStyleContext *style_context; + guint i; + + g_return_if_fail (!GTK_IS_WIDGET (self->add_print_icon)); + + for (i = 0; i < N_VALID_FINGERS; ++i) + { + GtkWidget *flowbox_child; + + flowbox_child = fingerprint_menu_button ("fingerprint-detection-symbolic", + get_finger_name (FINGER_IDS[i])); + + button = g_object_get_data (G_OBJECT (flowbox_child), "button"); + + gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), + GTK_WIDGET (self->print_popover)); + /* Move the popover on click, so we can just reuse the same instance */ + g_signal_connect_object (button, "clicked", + G_CALLBACK (gtk_popover_set_relative_to), + self->print_popover, G_CONNECT_SWAPPED); + + g_object_set_data (G_OBJECT (flowbox_child), "finger-id", + (gpointer) FINGER_IDS[i]); + + gtk_flow_box_insert (self->prints_gallery, flowbox_child, i); + } + + /* TRANSLATORS: This is the label for the button to enroll a new finger */ + add_print_label = _("Scan new fingerprint"); + self->add_print_icon = fingerprint_menu_button ("list-add-symbolic", + add_print_label); + style_context = gtk_widget_get_style_context (self->add_print_icon); + gtk_style_context_add_class (style_context, "fingerprint-print-add"); + + populate_add_print_popover (self); + button = g_object_get_data (G_OBJECT (self->add_print_icon), "button"); + gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), + GTK_WIDGET (self->add_print_popover)); + + gtk_flow_box_insert (self->prints_gallery, self->add_print_icon, -1); + gtk_flow_box_set_max_children_per_line (self->prints_gallery, 1); + + gtk_widget_show_all (GTK_WIDGET (self->prints_gallery)); + gtk_flow_box_set_filter_func (self->prints_gallery, prints_visibility_filter, + self, NULL); + + update_prints_visibility (self); +} + +static void +release_device_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object); + CcFingerprintDialog *self = user_data; + + cc_fprintd_device_call_release_finish (fprintd_device, res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + if (error) + { + g_autofree char *error_message = NULL; + + g_dbus_error_strip_remote_error (error); + error_message = g_strdup_printf (_("Failed to release fingerprint device %s: %s"), + cc_fprintd_device_get_name (fprintd_device), + error->message); + g_warning ("%s", error_message); + + notify_error (self, error_message); + return; + } + + self->device_claimed = FALSE; +} + +static void +release_device (CcFingerprintDialog *self) +{ + if (!self->device || !self->device_claimed) + return; + + disconnect_device_signals (self); + + cc_fprintd_device_call_release (self->device, + self->cancellable, + release_device_cb, + self); +} + +static void +on_device_signal (CcFingerprintDialog *self, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + if (g_str_equal (signal_name, "EnrollStatus")) + { + const char *result; + gboolean done; + + if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sb)"))) + { + g_warning ("Unexpected enroll parameters type %s", + g_variant_get_type_string (parameters)); + return; + } + + g_variant_get (parameters, "(&sb)", &result, &done); + handle_enroll_signal (self, result, done); + } +} + +static void claim_device (CcFingerprintDialog *self); + +static void +on_device_owner_changed (CcFprintdDevice *device, + GParamSpec *spec, + CcFingerprintDialog *self) +{ + g_autofree char *name_owner = NULL; + + name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (device)); + + if (!name_owner) + { + if (self->device_claimed) + { + disconnect_device_signals (self); + + if (self->enrolling_finger) + { + set_enroll_result_message (self, ENROLL_STATE_ERROR, + C_("Fingerprint enroll state", + "Problem Reading Device")); + self->enrolling_finger = NULL; + } + + self->device_claimed = FALSE; + claim_device (self); + } + } +} + +static void +claim_device_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object); + CcFingerprintDialog *self = user_data; + + cc_fprintd_device_call_claim_finish (fprintd_device, res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self->claiming = FALSE; + + if (error) + { + g_autofree char *dbus_error = g_dbus_error_get_remote_error (error); + g_autofree char *error_message = NULL; + + if (dbus_error && g_str_has_suffix (dbus_error, ".Error.AlreadyInUse") && + self->device_claimed) + return; + + g_dbus_error_strip_remote_error (error); + error_message = g_strdup_printf (_("Failed to claim fingerprint device %s: %s"), + cc_fprintd_device_get_name (self->device), + error->message); + g_warning ("%s", error_message); + notify_error (self, error_message); + return; + } + + gtk_widget_set_sensitive (self->prints_manager, TRUE); + self->device_claimed = TRUE; + self->device_signal_id = g_signal_connect_object (self->device, "g-signal", + G_CALLBACK (on_device_signal), + self, G_CONNECT_SWAPPED); + self->device_name_owner_id = g_signal_connect_object (self->device, "notify::g-name-owner", + G_CALLBACK (on_device_owner_changed), + self, 0); +} + +static void +claim_device (CcFingerprintDialog *self) +{ + ActUser *user; + + g_return_if_fail (!self->device_claimed); + + if (self->claiming) + return; + + user = cc_fingerprint_manager_get_user (self->manager); + gtk_widget_set_sensitive (self->prints_manager, FALSE); + self->claiming = TRUE; + + cc_fprintd_device_call_claim (self->device, + act_user_get_user_name (user), + self->cancellable, + claim_device_cb, + self); +} + +static void +on_stack_child_changed (CcFingerprintDialog *self) +{ + GtkWidget *visible_child = gtk_stack_get_visible_child (self->stack); + + g_debug ("Fingerprint dialog child changed: %s", + gtk_stack_get_visible_child_name (self->stack)); + + gtk_widget_hide (GTK_WIDGET (self->back_button)); + gtk_widget_hide (GTK_WIDGET (self->cancel_button)); + gtk_widget_hide (GTK_WIDGET (self->done_button)); + + gtk_header_bar_set_show_close_button (self->titlebar, TRUE); + gtk_flow_box_invalidate_filter (self->prints_gallery); + + if (visible_child == self->prints_manager) + { + gtk_widget_set_visible (GTK_WIDGET (self->back_button), + have_multiple_devices (self)); + notify_error (self, NULL); + update_prints_store (self); + + if (!self->device_claimed) + claim_device (self); + } + else if (visible_child == self->enrollment_view) + { + gtk_header_bar_set_show_close_button (self->titlebar, FALSE); + + gtk_widget_show (GTK_WIDGET (self->cancel_button)); + gtk_widget_set_sensitive (GTK_WIDGET (self->cancel_button), TRUE); + + gtk_widget_show (GTK_WIDGET (self->done_button)); + gtk_widget_set_sensitive (GTK_WIDGET (self->done_button), FALSE); + } + else + { + release_device (self); + g_clear_object (&self->device); + } +} + +static void +cc_fingerprint_dialog_init (CcFingerprintDialog *self) +{ + g_autoptr(GtkCssProvider) provider = NULL; + + self->cancellable = g_cancellable_new (); + + gtk_widget_init_template (GTK_WIDGET (self)); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, + "/org/gnome/control-center/user-accounts/cc-fingerprint-dialog.css"); + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + on_stack_child_changed (self); + g_signal_connect_object (self->stack, "notify::visible-child", + G_CALLBACK (on_stack_child_changed), self, + G_CONNECT_SWAPPED); + + g_object_bind_property (self->stack, "visible-child-name", + self->title, "label", G_BINDING_SYNC_CREATE); + + populate_prints_gallery (self); + populate_enrollment_view (self); +} + +static void +select_device_row (CcFingerprintDialog *self, + GtkListBoxRow *row, + GtkListBox *listbox) +{ + CcFprintdDevice *device = g_object_get_data (G_OBJECT (row), "device"); + + g_return_if_fail (CC_FPRINTD_DEVICE (device)); + + g_set_object (&self->device, device); + gtk_stack_set_visible_child (self->stack, self->prints_manager); +} + +static void +on_devices_list (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + g_autolist (CcFprintdDevice) fprintd_devices = NULL; + g_autoptr(GError) error = NULL; + CcFingerprintManager *fingerprint_manager = CC_FINGERPRINT_MANAGER (object); + CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (user_data); + + fprintd_devices = cc_fingerprint_manager_get_devices_finish (fingerprint_manager, + res, &error); + gtk_spinner_stop (self->spinner); + + if (fprintd_devices == NULL) + { + if (error) + { + g_autofree char *error_message = NULL; + + g_dbus_error_strip_remote_error (error); + error_message = g_strdup_printf (_("Failed to get fingerprint devices: %s"), + error->message); + g_warning ("%s", error_message); + notify_error (self, error_message); + } + + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->no_devices_found)); + } + else if (fprintd_devices->next == NULL) + { + /* We have just one device... Skip devices selection */ + self->device = g_object_ref (fprintd_devices->data); + gtk_stack_set_visible_child (self->stack, self->prints_manager); + } + else + { + GList *l; + + for (l = fprintd_devices; l; l = l->next) + { + CcFprintdDevice *device = l->data; + CcListRow *device_row; + + device_row = g_object_new (CC_TYPE_LIST_ROW, + "visible", TRUE, + "icon-name", "go-next-symbolic", + "title", cc_fprintd_device_get_name (device), + NULL); + + gtk_list_box_insert (self->devices_list, GTK_WIDGET (device_row), -1); + g_object_set_data_full (G_OBJECT (device_row), "device", + g_object_ref (device), g_object_unref); + } + + gtk_stack_set_visible_child (self->stack, self->device_selector); + } +} + +static void +cc_fingerprint_dialog_constructed (GObject *object) +{ + CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object); + + bindtextdomain ("fprintd", GNOMELOCALEDIR); + bind_textdomain_codeset ("fprintd", "UTF-8"); + + gtk_spinner_start (self->spinner); + cc_fingerprint_manager_get_devices (self->manager, self->cancellable, + on_devices_list, self); +} + +static void +back_button_clicked_cb (CcFingerprintDialog *self) +{ + if (gtk_stack_get_visible_child (self->stack) == self->prints_manager) + { + notify_error (self, NULL); + gtk_stack_set_visible_child (self->stack, self->device_selector); + return; + } + + g_return_if_reached (); +} + +static void +confirm_deletion_button_clicked_cb (CcFingerprintDialog *self) +{ + gtk_widget_hide (self->delete_confirmation_infobar); + delete_enrolled_prints (self); +} + +static void +cancel_deletion_button_clicked_cb (CcFingerprintDialog *self) +{ + gtk_widget_set_sensitive (self->prints_manager, TRUE); + gtk_widget_hide (self->delete_confirmation_infobar); +} + +static void +delete_prints_button_clicked_cb (CcFingerprintDialog *self) +{ + gtk_widget_set_sensitive (self->prints_manager, FALSE); + gtk_widget_show (self->delete_confirmation_infobar); +} + +static void +cancel_button_clicked_cb (CcFingerprintDialog *self) +{ + if (self->enrolling_finger) + { + g_cancellable_cancel (self->cancellable); + g_set_object (&self->cancellable, g_cancellable_new ()); + + g_debug ("Cancelling enroll operation"); + enroll_stop (self); + } + else + { + gtk_stack_set_visible_child (self->stack, self->prints_manager); + } +} + +static void +done_button_clicked_cb (CcFingerprintDialog *self) +{ + g_return_if_fail (self->enrolling_finger); + + g_debug ("Completing enroll operation"); + enroll_stop (self); +} + +static void +fingerprint_dialog_delete_cb (CcFingerprintDialog *self) +{ + cc_fingerprint_manager_update_state (self->manager, NULL, NULL); + gtk_widget_destroy (GTK_WIDGET (self)); +} + +static void +cc_fingerprint_dialog_class_init (CcFingerprintDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/control-center/user-accounts/cc-fingerprint-dialog.ui"); + + object_class->constructed = cc_fingerprint_dialog_constructed; + object_class->dispose = cc_fingerprint_dialog_dispose; + object_class->get_property = cc_fingerprint_dialog_get_property; + object_class->set_property = cc_fingerprint_dialog_set_property; + + properties[PROP_MANAGER] = + g_param_spec_object ("fingerprint-manager", + "FingerprintManager", + "The CC fingerprint manager", + CC_TYPE_FINGERPRINT_MANAGER, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, add_print_popover); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, add_print_popover_box); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, back_button); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, cancel_button); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, delete_confirmation_infobar); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, delete_prints_button); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, device_selector); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, devices_list); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, done_button); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enroll_message); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enroll_print_bin); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enroll_print_entry); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enrollment_view); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, error_infobar); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, infobar_error); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, no_devices_found); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, print_popover); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, prints_gallery); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, prints_manager); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, spinner); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, stack); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, title); + gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, titlebar); + + gtk_widget_class_bind_template_callback (widget_class, back_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, cancel_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, cancel_deletion_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, confirm_deletion_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, delete_prints_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, done_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, fingerprint_dialog_delete_cb); + gtk_widget_class_bind_template_callback (widget_class, on_print_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, reenroll_finger_cb); + gtk_widget_class_bind_template_callback (widget_class, select_device_row); +} diff --git a/panels/user-accounts/cc-fingerprint-dialog.h b/panels/user-accounts/cc-fingerprint-dialog.h new file mode 100644 index 0000000..9afac0b --- /dev/null +++ b/panels/user-accounts/cc-fingerprint-dialog.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authors: Marco Trevisan <marco.trevisan@canonical.com> + */ + +#pragma once + +#include <gtk/gtk.h> +#include "cc-fingerprint-manager.h" + +G_BEGIN_DECLS + +#define CC_TYPE_FINGERPRINT_DIALOG (cc_fingerprint_dialog_get_type ()) + +G_DECLARE_FINAL_TYPE (CcFingerprintDialog, cc_fingerprint_dialog, + CC, FINGERPRINT_DIALOG, GtkWindow) + +CcFingerprintDialog *cc_fingerprint_dialog_new (CcFingerprintManager *manager); + +G_END_DECLS diff --git a/panels/user-accounts/cc-fingerprint-dialog.ui b/panels/user-accounts/cc-fingerprint-dialog.ui new file mode 100644 index 0000000..2613d5e --- /dev/null +++ b/panels/user-accounts/cc-fingerprint-dialog.ui @@ -0,0 +1,462 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcFingerprintDialog" parent="GtkWindow"> + <style> + <class name="fingerprint" /> + </style> + <property name="name">fingerprint-dialog</property> + <property name="title" translatable="yes">Fingerprint Manager</property> + <property name="type-hint">dialog</property> + <property name="window-position">center-on-parent</property> + <property name="destroy-with-parent">True</property> + <property name="default-width">600</property> + <property name="default-height">400</property> + <property name="modal">True</property> + <signal name="delete-event" handler="fingerprint_dialog_delete_cb"/> + <child type="titlebar"> + <object class="GtkHeaderBar" id="titlebar"> + <property name="visible">True</property> + <property name="show-close-button">True</property> + <child type="title"> + <object class="GtkLabel" id="title"> + <property name="visible">True</property> + <property name="label" translatable="yes">Fingerprint</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkButton" id="cancel_button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="valign">center</property> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Cancel</property> + <signal name="clicked" handler="cancel_button_clicked_cb" object="CcFingerprintDialog" swapped="yes" /> + </object> + </child> + <child> + <object class="GtkButton" id="back_button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="valign">center</property> + <property name="use-underline">True</property> + <signal name="clicked" handler="back_button_clicked_cb" object="CcFingerprintDialog" swapped="yes" /> + <style> + <class name="image-button"/> + </style> + <child internal-child="accessible"> + <object class="AtkObject" id="a11y-back"> + <property name="accessible-name" translatable="yes">Back</property> + </object> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">go-previous-symbolic</property> + <property name="icon_size">1</property> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkButton" id="done_button"> + <property name="use-underline">True</property> + <property name="can-default">True</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="label" translatable="yes">_Done</property> + <signal name="clicked" handler="done_button_clicked_cb" object="CcFingerprintDialog" swapped="yes" /> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + + <child> + <object class="GtkSpinner" id="spinner"> + <property name="visible">True</property> + <property name="active">False</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + </object> + </child> + + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + + <child> + <object class="GtkInfoBar" id="delete_confirmation_infobar"> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="border_width">0</property> + <property name="orientation">vertical</property> + <property name="spacing">5</property> + <child internal-child="action_area"> + <object class="GtkButtonBox"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton"> + <signal name="clicked" handler="cancel_deletion_button_clicked_cb" object="CcFingerprintDialog" swapped="yes"/> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_No</property> + <property name="use-underline">True</property> + </object> + </child> + <child> + <object class="GtkButton"> + <signal name="clicked" handler="confirm_deletion_button_clicked_cb" object="CcFingerprintDialog" swapped="yes"/> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="label" translatable="yes">_Yes</property> + <property name="use-underline">True</property> + <style> + <class name="destructive-action"/> + </style> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child internal-child="content_area"> + <object class="GtkBox"> + <property name="can_focus">False</property> + <property name="spacing">16</property> + <property name="margin-start">12</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="hexpand">False</property> + <property name="wrap">True</property> + <property name="label" translatable="yes">Do you want to delete your registered fingerprints so fingerprint login is disabled?</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + + <child> + <object class="GtkInfoBar" id="error_infobar"> + <property name="name">error_infobar</property> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="border_width">0</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <style> + <class name="error"/> + </style> + <child internal-child="content_area"> + <object class="GtkBox"> + <property name="can_focus">False</property> + <property name="spacing">16</property> + <child> + <object class="GtkLabel" id="infobar_error"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="hexpand">False</property> + <property name="wrap">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="halign">fill</property> + <property name="valign">fill</property> + <property name="propagate-natural-width">True</property> + <property name="can-focus">False</property> + <property name="hscrollbar-policy">never</property> + + <child> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="transition_duration">300</property> + <property name="margin-start">20</property> + <property name="margin-end">20</property> + <property name="margin-top">30</property> + <property name="margin-bottom">30</property> + <property name="width_request">360</property> + <property name="halign">center</property> + + <child> + <object class="GtkBox" id="no_devices_found"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="valign">center</property> + <property name="spacing">12</property> + <style> + <class name="dim-label"/> + </style> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="icon_name">fingerprint-detection-symbolic</property> + <property name="pixel_size">192</property> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes" comments="Translators: This is the empty state page label which states that there are no devices ready.">No Fingerprint device</property> + <attributes> + <attribute name="weight" value="bold"/> + <attribute name="scale" value="1.6"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Ensure the device is properly connected.</property> + </object> + </child> + </object> + <packing> + <property name="name" translatable="yes">No fingerprint device</property> + </packing> + </child> + + <child> + <object class="GtkBox" id="device_selector"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="spacing">10</property> + <property name="orientation">vertical</property> + + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Choose the fingerprint device you want to configure</property> + <property name="halign">start</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="hscrollbar-policy">never</property> + <property name="propagate-natural-height">True</property> + <child> + <object class="GtkListBox" id="devices_list"> + <property name="visible">True</property> + <property name="selection-mode">none</property> + <property name="valign">center</property> + <signal name="row-activated" handler="select_device_row" object="CcFingerprintDialog" swapped="yes"/> + <style> + <class name="frame" /> + </style> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="name" translatable="yes">Fingerprint Device</property> + </packing> + </child> + + <child> + <object class="GtkBox" id="prints_manager"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="valign">fill</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Fingerprint login allows you to unlock and log into your computer with your finger</property> + </object> + </child> + <child> + <object class="GtkFlowBox" id="prints_gallery"> + <style> + <class name="prints-gallery" /> + </style> + <property name="visible">True</property> + <property name="margin">12</property> + <property name="column-spacing">12</property> + <property name="row-spacing">12</property> + <property name="homogeneous">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="vexpand">True</property> + <property name="min-children-per-line">1</property> + <property name="max-children-per-line">3</property> + <property name="activate-on-single-click">True</property> + <property name="selection-mode">none</property> + <signal name="child-activated" handler="on_print_activated_cb" object="CcFingerprintDialog" swapped="no" /> + </object> + </child> + + <child> + <object class="GtkButton" id="delete_prints_button"> + <property name="visible">False</property> + <property name="can_focus">True</property> + <property name="halign">end</property> + <property name="use-underline">True</property> + <property name="label" translatable="yes">_Delete Fingerprints</property> + <property name="margin-top">10</property> + <property name="margin-bottom">10</property> + <signal name="clicked" handler="delete_prints_button_clicked_cb" object="CcFingerprintDialog" swapped="yes"/> + <style> + <class name="destructive-action"/> + </style> + </object> + </child> + </object> + <packing> + <property name="name" translatable="yes">Fingerprint Login</property> + </packing> + </child> + + <child> + <object class="GtkBox" id="enrollment_view"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="valign">fill</property> + <property name="spacing">12</property> + <style> + <class name="enrollment" /> + </style> + <child> + <object class="GtkLabel" id="enroll_message"> + <property name="visible">True</property> + <property name="wrap">True</property> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="margin">12</property> + <property name="spacing">12</property> + <property name="halign">fill</property> + <property name="valign">center</property> + <property name="vexpand">True</property> + <property name="hexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="enroll_print_bin"> + <property name="hexpand">True</property> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GtkEntry" id="enroll_print_entry"> + <property name="valign">end</property> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="editable">False</property> + <property name="sensitive">False</property> + <property name="width-request">200</property> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="name" translatable="yes">Fingerprint Enroll</property> + </packing> + </child> + + </object> + </child> + </object> + </child> + + </object> + </child> + </template> + + <object class="GtkPopoverMenu" id="print_popover"> + <property name="position">bottom</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin">12</property> + <property name="spacing">6</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkModelButton"> + <property name="label" translatable="yes">_Re-enroll this finger…</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <property name="xalign">0.0</property> + <signal name="clicked" handler="reenroll_finger_cb" object="CcFingerprintDialog" swapped="yes"/> + </object> + </child> + </object> + </child> + </object> + + <object class="GtkPopoverMenu" id="add_print_popover"> + <property name="position">bottom</property> + <child> + <object class="GtkBox" id="add_print_popover_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin">12</property> + <property name="spacing">6</property> + <property name="orientation">vertical</property> + </object> + </child> + </object> + +</interface> diff --git a/panels/user-accounts/cc-fingerprint-manager.c b/panels/user-accounts/cc-fingerprint-manager.c new file mode 100644 index 0000000..07a50e7 --- /dev/null +++ b/panels/user-accounts/cc-fingerprint-manager.c @@ -0,0 +1,597 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authors: Marco Trevisan <marco.trevisan@canonical.com> + */ + +#include "cc-fingerprint-manager.h" + +#include "cc-fprintd-generated.h" +#include "cc-user-accounts-enum-types.h" + +#define CC_FPRINTD_NAME "net.reactivated.Fprint" +#define CC_FPRINTD_MANAGER_PATH "/net/reactivated/Fprint/Manager" + +struct _CcFingerprintManager +{ + GObject parent_instance; +}; + +typedef struct +{ + ActUser *user; + GTask *current_task; + CcFingerprintState state; + GList *cached_devices; +} CcFingerprintManagerPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (CcFingerprintManager, cc_fingerprint_manager, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_USER, + PROP_STATE, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static void cleanup_cached_devices (CcFingerprintManager *self); + +CcFingerprintManager * +cc_fingerprint_manager_new (ActUser *user) +{ + return g_object_new (CC_TYPE_FINGERPRINT_MANAGER, "user", user, NULL); +} + +static void +cc_fingerprint_manager_dispose (GObject *object) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + if (priv->current_task) + { + g_cancellable_cancel (g_task_get_cancellable (priv->current_task)); + priv->current_task = NULL; + } + + g_clear_object (&priv->user); + cleanup_cached_devices (self); + + G_OBJECT_CLASS (cc_fingerprint_manager_parent_class)->dispose (object); +} + +static void +cc_fingerprint_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + switch (prop_id) + { + case PROP_STATE: + g_value_set_enum (value, priv->state); + break; + + case PROP_USER: + g_value_set_object (value, priv->user); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_fingerprint_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + switch (prop_id) + { + case PROP_USER: + g_set_object (&priv->user, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_fingerprint_manager_constructed (GObject *object) +{ + cc_fingerprint_manager_update_state (CC_FINGERPRINT_MANAGER (object), NULL, NULL); +} + +static void +cc_fingerprint_manager_class_init (CcFingerprintManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = cc_fingerprint_manager_constructed; + object_class->dispose = cc_fingerprint_manager_dispose; + object_class->get_property = cc_fingerprint_manager_get_property; + object_class->set_property = cc_fingerprint_manager_set_property; + + properties[PROP_USER] = + g_param_spec_object ("user", + "User", + "The user account we manage the fingerprint for", + ACT_TYPE_USER, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + properties[PROP_STATE] = + g_param_spec_enum ("state", + "State", + "The state of the fingerprint for the user", + CC_TYPE_FINGERPRINT_STATE, CC_FINGERPRINT_STATE_NONE, + G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +cc_fingerprint_manager_init (CcFingerprintManager *self) +{ +} + +typedef struct +{ + guint waiting_devices; + GList *devices; +} DeviceListData; + +static void +object_list_destroy_notify (gpointer data) +{ + GList *list = data; + g_list_free_full (list, g_object_unref); +} + +static void +on_device_owner_changed (CcFingerprintManager *self, + GParamSpec *spec, + CcFprintdDevice *device) +{ + g_autofree char *name_owner = NULL; + + name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (device)); + + if (!name_owner) + { + g_debug ("Fprintd daemon disappeared, cleaning cache..."); + cleanup_cached_devices (self); + } +} + +static void +cleanup_cached_devices (CcFingerprintManager *self) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + CcFprintdDevice *target_device; + + if (!priv->cached_devices) + return; + + g_return_if_fail (CC_FPRINTD_IS_DEVICE (priv->cached_devices->data)); + + target_device = CC_FPRINTD_DEVICE (priv->cached_devices->data); + + g_signal_handlers_disconnect_by_func (target_device, on_device_owner_changed, self); + g_list_free_full (g_steal_pointer (&priv->cached_devices), g_object_unref); +} + +static void +cache_devices (CcFingerprintManager *self, + GList *devices) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + CcFprintdDevice *target_device; + + g_return_if_fail (devices && CC_FPRINTD_IS_DEVICE (devices->data)); + + cleanup_cached_devices (self); + priv->cached_devices = g_list_copy_deep (devices, (GCopyFunc) g_object_ref, NULL); + + /* We can monitor just the first device name, as the owner is just the same */ + target_device = CC_FPRINTD_DEVICE (priv->cached_devices->data); + + g_signal_connect_object (target_device, "notify::g-name-owner", + G_CALLBACK (on_device_owner_changed), self, + G_CONNECT_SWAPPED); +} + +static void +on_device_proxy (GObject *object, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(CcFprintdDevice) fprintd_device = NULL; + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(GError) error = NULL; + CcFingerprintManager *self = g_task_get_source_object (task); + DeviceListData *list_data = g_task_get_task_data (task); + + fprintd_device = cc_fprintd_device_proxy_new_for_bus_finish (res, &error); + list_data->waiting_devices--; + + if (error) + { + if (list_data->waiting_devices == 0) + g_task_return_error (task, g_steal_pointer (&error)); + else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Impossible to ge the device proxy: %s", error->message); + + return; + } + + g_debug ("Got fingerprint device %s", cc_fprintd_device_get_name (fprintd_device)); + + list_data->devices = g_list_append (list_data->devices, g_steal_pointer (&fprintd_device)); + + if (list_data->waiting_devices == 0) + { + cache_devices (self, list_data->devices); + g_task_return_pointer (task, g_steal_pointer (&list_data->devices), object_list_destroy_notify); + } +} + +static void +on_devices_list (GObject *object, GAsyncResult *res, gpointer user_data) +{ + CcFprintdManager *fprintd_manager = CC_FPRINTD_MANAGER (object); + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(GError) error = NULL; + g_auto(GStrv) devices_list = NULL; + DeviceListData *list_data; + guint i; + + cc_fprintd_manager_call_get_devices_finish (fprintd_manager, &devices_list, res, &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + if (!devices_list || !devices_list[0]) + { + g_task_return_pointer (task, NULL, NULL); + return; + } + + list_data = g_new0 (DeviceListData, 1); + g_task_set_task_data (task, list_data, g_free); + + g_debug ("Fprintd replied with %u device(s)", g_strv_length (devices_list)); + + for (i = 0; devices_list[i] != NULL; ++i) + { + const char *device_path = devices_list[i]; + + list_data->waiting_devices++; + + cc_fprintd_device_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + CC_FPRINTD_NAME, + device_path, + g_task_get_cancellable (task), + on_device_proxy, + g_object_ref (task)); + } +} + +static void +on_manager_proxy (GObject *object, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(CcFprintdManager) fprintd_manager = NULL; + g_autoptr(GError) error = NULL; + + fprintd_manager = cc_fprintd_manager_proxy_new_for_bus_finish (res, &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + g_debug ("Fprintd manager connected"); + + cc_fprintd_manager_call_get_devices (fprintd_manager, + g_task_get_cancellable (task), + on_devices_list, + g_object_ref (task)); +} + +static void +fprintd_manager_connect (CcFingerprintManager *self, + GAsyncReadyCallback callback, + GTask *task) +{ + g_assert (G_IS_TASK (task)); + + cc_fprintd_manager_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, + CC_FPRINTD_NAME, CC_FPRINTD_MANAGER_PATH, + g_task_get_cancellable (task), + callback, + task); +} + +void +cc_fingerprint_manager_get_devices (CcFingerprintManager *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + g_autoptr(GTask) task = NULL; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, cc_fingerprint_manager_get_devices); + + if (priv->cached_devices) + { + GList *devices; + + devices = g_list_copy_deep (priv->cached_devices, (GCopyFunc) g_object_ref, NULL); + g_task_return_pointer (task, devices, object_list_destroy_notify); + return; + } + + fprintd_manager_connect (self, on_manager_proxy, g_steal_pointer (&task)); +} + +/** + * cc_fingerprint_manager_get_devices_finish: + * @self: The #CcFingerprintManager + * @result: A #GAsyncResult + * @error: Return location for errors, or %NULL to ignore + * + * Finish an asynchronous operation to list all devices. + * + * Returns: (element-type CcFprintdDevice) (transfer full): List of prints or %NULL on error + */ +GList * +cc_fingerprint_manager_get_devices_finish (CcFingerprintManager *self, + GAsyncResult *res, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (res, self), NULL); + + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +set_state (CcFingerprintManager *self, + CcFingerprintState state) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + if (priv->state == state) + return; + + g_debug ("Fingerprint manager state changed to %d", state); + + priv->state = state; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATE]); +} + +typedef struct +{ + guint waiting_devices; + CcFingerprintStateUpdated callback; + gpointer user_data; +} UpdateStateData; + +static void +update_state_callback (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + g_autoptr(GError) error = NULL; + CcFingerprintState state; + UpdateStateData *data; + GTask *task; + + g_return_if_fail (g_task_is_valid (res, self)); + + task = G_TASK (res); + g_assert (g_steal_pointer (&priv->current_task) == task); + + state = g_task_propagate_int (task, &error); + data = g_task_get_task_data (task); + + if (error) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_warning ("Impossible to update fingerprint manager state: %s", + error->message); + + state = CC_FINGERPRINT_STATE_NONE; + } + + set_state (self, state); + + if (data->callback) + data->callback (self, state, data->user_data, error); +} + +static void +on_device_list_enrolled (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object); + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(GError) error = NULL; + g_auto(GStrv) enrolled_fingers = NULL; + UpdateStateData *data = g_task_get_task_data (task); + guint num_enrolled_fingers; + + cc_fprintd_device_call_list_enrolled_fingers_finish (fprintd_device, + &enrolled_fingers, + res, &error); + + if (data->waiting_devices == 0) + return; + + data->waiting_devices--; + + if (error) + { + g_autofree char *dbus_error = g_dbus_error_get_remote_error (error); + + if (!g_str_equal (dbus_error, CC_FPRINTD_NAME ".Error.NoEnrolledPrints")) + { + if (data->waiting_devices == 0) + g_task_return_error (task, g_steal_pointer (&error)); + else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Impossible to list enrolled fingers: %s", error->message); + + return; + } + } + + num_enrolled_fingers = enrolled_fingers ? g_strv_length (enrolled_fingers) : 0; + + g_debug ("Device %s has %u enrolled fingers", + cc_fprintd_device_get_name (fprintd_device), + num_enrolled_fingers); + + if (num_enrolled_fingers > 0) + { + data->waiting_devices = 0; + g_task_return_int (task, CC_FINGERPRINT_STATE_ENABLED); + } + else if (data->waiting_devices == 0) + { + g_task_return_int (task, CC_FINGERPRINT_STATE_DISABLED); + } +} + +static void +on_manager_devices_list (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + CcFingerprintManager *self = CC_FINGERPRINT_MANAGER (object); + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + g_autolist(CcFprintdDevice) fprintd_devices = NULL; + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(GError) error = NULL; + UpdateStateData *data = g_task_get_task_data (task); + const char *user_name; + GList *l; + + fprintd_devices = cc_fingerprint_manager_get_devices_finish (self, res, &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + if (fprintd_devices == NULL) + { + g_debug ("No fingerprint devices found"); + g_task_return_int (task, CC_FINGERPRINT_STATE_NONE); + return; + } + + user_name = act_user_get_user_name (priv->user); + + for (l = fprintd_devices; l; l = l->next) + { + CcFprintdDevice *device = l->data; + + g_debug ("Connected to device %s, looking for enrolled fingers", + cc_fprintd_device_get_name (device)); + + data->waiting_devices++; + cc_fprintd_device_call_list_enrolled_fingers (device, user_name, + g_task_get_cancellable (task), + on_device_list_enrolled, + g_object_ref (task)); + } +} + +void +cc_fingerprint_manager_update_state (CcFingerprintManager *self, + CcFingerprintStateUpdated callback, + gpointer user_data) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + g_autoptr(GCancellable) cancellable = NULL; + UpdateStateData *data; + + g_return_if_fail (priv->current_task == NULL); + + if (act_user_get_uid (priv->user) != getuid () || + !act_user_is_local_account (priv->user)) + { + set_state (self, CC_FINGERPRINT_STATE_NONE); + return; + } + + cancellable = g_cancellable_new (); + data = g_new0 (UpdateStateData, 1); + data->callback = callback; + data->user_data = user_data; + + priv->current_task = g_task_new (self, cancellable, update_state_callback, NULL); + g_task_set_source_tag (priv->current_task, cc_fingerprint_manager_update_state); + g_task_set_task_data (priv->current_task, data, g_free); + + set_state (self, CC_FINGERPRINT_STATE_UPDATING); + + cc_fingerprint_manager_get_devices (self, cancellable, on_manager_devices_list, + priv->current_task); +} + +CcFingerprintState +cc_fingerprint_manager_get_state (CcFingerprintManager *self) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + g_return_val_if_fail (CC_IS_FINGERPRINT_MANAGER (self), CC_FINGERPRINT_STATE_NONE); + + return priv->state; +} + +ActUser * +cc_fingerprint_manager_get_user (CcFingerprintManager *self) +{ + CcFingerprintManagerPrivate *priv = cc_fingerprint_manager_get_instance_private (self); + + g_return_val_if_fail (CC_IS_FINGERPRINT_MANAGER (self), NULL); + + return priv->user; +} diff --git a/panels/user-accounts/cc-fingerprint-manager.h b/panels/user-accounts/cc-fingerprint-manager.h new file mode 100644 index 0000000..d12f52c --- /dev/null +++ b/panels/user-accounts/cc-fingerprint-manager.h @@ -0,0 +1,74 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2020 Canonical Ltd. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authors: Marco Trevisan <marco.trevisan@canonical.com> + */ + +#pragma once + +#include <glib-object.h> +#include <act/act.h> + +G_BEGIN_DECLS + +#define CC_TYPE_FINGERPRINT_MANAGER (cc_fingerprint_manager_get_type ()) + +G_DECLARE_FINAL_TYPE (CcFingerprintManager, cc_fingerprint_manager, CC, FINGERPRINT_MANAGER, GObject) + +/** + * CcFingerprintManager: + * @CC_FINGERPRINT_STATE_NONE: Fingerprint recognition is not available + * @CC_FINGERPRINT_STATE_UPDATING: Fingerprint recognition is being fetched + * @CC_FINGERPRINT_STATE_ENABLED: Fingerprint recognition is enabled + * @CC_FINGERPRINT_STATE_DISABLED: Fingerprint recognition is disabled + * + * The status of the fingerprint support. + */ +typedef enum { + CC_FINGERPRINT_STATE_NONE, + CC_FINGERPRINT_STATE_UPDATING, + CC_FINGERPRINT_STATE_ENABLED, + CC_FINGERPRINT_STATE_DISABLED, +} CcFingerprintState; + +typedef void (*CcFingerprintStateUpdated) (CcFingerprintManager *fp_manager, + CcFingerprintState state, + gpointer user_data, + GError *error); + +CcFingerprintManager * cc_fingerprint_manager_new (ActUser *user); + +CcFingerprintState cc_fingerprint_manager_get_state (CcFingerprintManager *fp_manager); + +ActUser * cc_fingerprint_manager_get_user (CcFingerprintManager *fp_manager); + +void cc_fingerprint_manager_update_state (CcFingerprintManager *fp_manager, + CcFingerprintStateUpdated callback, + gpointer user_data); + +void cc_fingerprint_manager_get_devices (CcFingerprintManager *fp_manager, + GCancellable *cancellable, + GAsyncReadyCallback res, + gpointer user_data); + +GList *cc_fingerprint_manager_get_devices_finish (CcFingerprintManager *fp_manager, + GAsyncResult *res, + GError **error); + +G_END_DECLS diff --git a/panels/user-accounts/cc-login-history-dialog.c b/panels/user-accounts/cc-login-history-dialog.c new file mode 100644 index 0000000..6670f75 --- /dev/null +++ b/panels/user-accounts/cc-login-history-dialog.c @@ -0,0 +1,350 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Ondrej Holy <oholy@redhat.com> + */ + +#include "config.h" + +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <act/act.h> + +#include "cc-login-history-dialog.h" +#include "cc-user-accounts-resources.h" +#include "cc-util.h" +#include "user-utils.h" + +struct _CcLoginHistoryDialog +{ + GtkDialog parent_instance; + + GtkHeaderBar *header_bar; + GtkListBox *history_box; + GtkButton *next_button; + GtkButton *previous_button; + + GDateTime *week; + GDateTime *current_week; + + ActUser *user; +}; + +G_DEFINE_TYPE (CcLoginHistoryDialog, cc_login_history_dialog, GTK_TYPE_DIALOG) + +typedef struct { + gint64 login_time; + gint64 logout_time; + const gchar *type; +} CcLoginHistory; + +static void +show_week_label (CcLoginHistoryDialog *self) +{ + g_autofree gchar *label = NULL; + GTimeSpan span; + + span = g_date_time_difference (self->current_week, self->week); + if (span == 0) { + label = g_strdup (_("This Week")); + } + else if (span == G_TIME_SPAN_DAY * 7) { + label = g_strdup (_("Last Week")); + } + else { + g_autofree gchar *from = NULL; + g_autofree gchar *to = NULL; + g_autoptr(GDateTime) date = NULL; + + date = g_date_time_add_days (self->week, 6); + /* Translators: This is a date format string in the style of "Feb 18", + shown as the first day of a week on login history dialog. */ + from = g_date_time_format (self->week, C_("login history week label","%b %e")); + if (g_date_time_get_year (self->week) == g_date_time_get_year (self->current_week)) { + /* Translators: This is a date format string in the style of "Feb 24", + shown as the last day of a week on login history dialog. */ + to = g_date_time_format (date, C_("login history week label","%b %e")); + } + else { + /* Translators: This is a date format string in the style of "Feb 24, 2013", + shown as the last day of a week on login history dialog. */ + to = g_date_time_format (date, C_("login history week label","%b %e, %Y")); + } + + /* Translators: This indicates a week label on a login history. + The first %s is the first day of a week, and the second %s the last day. */ + label = g_strdup_printf(C_("login history week label", "%s — %s"), from, to); + } + + gtk_header_bar_set_subtitle (self->header_bar, label); +} + +static void +clear_history (CcLoginHistoryDialog *self) +{ + g_autoptr(GList) list = NULL; + GList *it; + + list = gtk_container_get_children (GTK_CONTAINER (self->history_box)); + for (it = list; it != NULL; it = it->next) { + gtk_container_remove (GTK_CONTAINER (self->history_box), GTK_WIDGET (it->data)); + } +} + +static GArray * +get_login_history (ActUser *user) +{ + GArray *login_history; + GVariantIter *iter, *iter2; + GVariant *variant; + const GVariant *value; + const gchar *key; + CcLoginHistory history; + + login_history = NULL; + value = act_user_get_login_history (user); + g_variant_get ((GVariant *) value, "a(xxa{sv})", &iter); + while (g_variant_iter_loop (iter, "(xxa{sv})", &history.login_time, &history.logout_time, &iter2)) { + while (g_variant_iter_loop (iter2, "{&sv}", &key, &variant)) { + if (g_strcmp0 (key, "type") == 0) { + history.type = g_variant_get_string (variant, NULL); + } + } + + if (login_history == NULL) { + login_history = g_array_new (FALSE, TRUE, sizeof (CcLoginHistory)); + } + + g_array_append_val (login_history, history); + } + + return login_history; +} + +static void +set_sensitivity (CcLoginHistoryDialog *self) +{ + g_autoptr(GArray) login_history = NULL; + CcLoginHistory history; + gboolean sensitive = FALSE; + + login_history = get_login_history (self->user); + if (login_history != NULL) { + history = g_array_index (login_history, CcLoginHistory, 0); + sensitive = g_date_time_to_unix (self->week) > history.login_time; + } + gtk_widget_set_sensitive (GTK_WIDGET (self->previous_button), sensitive); + + sensitive = (g_date_time_compare (self->current_week, self->week) == 1); + gtk_widget_set_sensitive (GTK_WIDGET (self->next_button), sensitive); +} + +static void +add_record (CcLoginHistoryDialog *self, GDateTime *datetime, gchar *record_string, gint line) +{ + g_autofree gchar *date = NULL; + g_autofree gchar *time = NULL; + g_autofree gchar *str = NULL; + GtkWidget *label, *row; + + date = cc_util_get_smart_date (datetime); + /* Translators: This is a time format string in the style of "22:58". + It indicates a login time which follows a date. */ + time = g_date_time_format (datetime, 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. */ + str = g_strdup_printf(C_("login date-time", "%s, %s"), date, time); + + row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_show (row); + gtk_box_set_homogeneous (GTK_BOX (row), TRUE); + gtk_container_set_border_width (GTK_CONTAINER (row), 6); + + label = gtk_label_new (record_string); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_pack_start (GTK_BOX (row), label, TRUE, TRUE, 0); + + label = gtk_label_new (str); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_pack_start (GTK_BOX (row), label, TRUE, TRUE, 0); + + gtk_list_box_insert (self->history_box, row, line); +} + +static void +show_week (CcLoginHistoryDialog *self) +{ + g_autoptr(GArray) login_history = NULL; + g_autoptr(GDateTime) datetime = NULL; + g_autoptr(GDateTime) temp = NULL; + gint64 from, to; + gint i, line; + CcLoginHistory history; + + show_week_label (self); + clear_history (self); + set_sensitivity (self); + + login_history = get_login_history (self->user); + if (login_history == NULL) { + return; + } + + /* Find first record for week */ + from = g_date_time_to_unix (self->week); + temp = g_date_time_add_weeks (self->week, 1); + to = g_date_time_to_unix (temp); + for (i = login_history->len - 1; i >= 0; i--) { + history = g_array_index (login_history, CcLoginHistory, i); + if (history.login_time < to) { + break; + } + } + + /* Add new session records */ + line = 0; + for (;i >= 0; i--) { + history = g_array_index (login_history, CcLoginHistory, i); + + /* Display only x-session and tty records */ + if (!g_str_has_prefix (history.type, ":") && + !g_str_has_prefix (history.type, "tty")) { + continue; + } + + if (history.logout_time > 0 && history.logout_time < from) { + break; + } + + if (history.logout_time > 0 && history.logout_time < to) { + datetime = g_date_time_new_from_unix_local (history.logout_time); + add_record (self, datetime, _("Session Ended"), line); + line++; + } + + if (history.login_time >= from) { + datetime = g_date_time_new_from_unix_local (history.login_time); + add_record (self, datetime, _("Session Started"), line); + line++; + } + } +} + +static void +previous_button_clicked_cb (CcLoginHistoryDialog *self) +{ + g_autoptr(GDateTime) temp = NULL; + + temp = self->week; + self->week = g_date_time_add_weeks (self->week, -1); + + show_week (self); +} + +static void +next_button_clicked_cb (CcLoginHistoryDialog *self) +{ + g_autoptr(GDateTime) temp = NULL; + + temp = self->week; + self->week = g_date_time_add_weeks (self->week, 1); + + show_week (self); +} + +static void +cc_login_history_dialog_dispose (GObject *object) +{ + CcLoginHistoryDialog *self = CC_LOGIN_HISTORY_DIALOG (object); + + g_clear_object (&self->user); + g_clear_pointer (&self->week, g_date_time_unref); + g_clear_pointer (&self->current_week, g_date_time_unref); + + G_OBJECT_CLASS (cc_login_history_dialog_parent_class)->dispose (object); +} + +void +cc_login_history_dialog_class_init (CcLoginHistoryDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = cc_login_history_dialog_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/user-accounts/cc-login-history-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcLoginHistoryDialog, header_bar); + gtk_widget_class_bind_template_child (widget_class, CcLoginHistoryDialog, history_box); + gtk_widget_class_bind_template_child (widget_class, CcLoginHistoryDialog, next_button); + gtk_widget_class_bind_template_child (widget_class, CcLoginHistoryDialog, previous_button); + + gtk_widget_class_bind_template_callback (widget_class, next_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, previous_button_clicked_cb); +} + +void +cc_login_history_dialog_init (CcLoginHistoryDialog *self) +{ + g_resources_register (cc_user_accounts_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +CcLoginHistoryDialog * +cc_login_history_dialog_new (ActUser *user) +{ + CcLoginHistoryDialog *self; + g_autoptr(GDateTime) temp = NULL; + g_autoptr(GDateTime) local = NULL; + g_autofree gchar *title = NULL; + + g_return_val_if_fail (ACT_IS_USER (user), NULL); + + self = g_object_new (CC_TYPE_LOGIN_HISTORY_DIALOG, + "use-header-bar", 1, + NULL); + + self->user = g_object_ref (user); + + /* Set the first day of this week */ + local = g_date_time_new_now_local (); + temp = g_date_time_new_local (g_date_time_get_year (local), + g_date_time_get_month (local), + g_date_time_get_day_of_month (local), + 0, 0, 0); + self->week = g_date_time_add_days (temp, 1 - g_date_time_get_day_of_week (temp)); + self->current_week = g_date_time_ref (self->week); + + /* Translators: This is the title of the "Account Activity" dialog. + The %s is the user real name. */ + title = g_strdup_printf (_("%s — Account Activity"), + act_user_get_real_name (self->user)); + gtk_header_bar_set_title (self->header_bar, title); + + show_week (self); + + return self; +} diff --git a/panels/user-accounts/cc-login-history-dialog.h b/panels/user-accounts/cc-login-history-dialog.h new file mode 100644 index 0000000..e71f160 --- /dev/null +++ b/panels/user-accounts/cc-login-history-dialog.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Ondrej Holy <oholy@redhat.com> + */ + +#pragma once + +#include <gtk/gtk.h> +#include <act/act-user.h> + +G_BEGIN_DECLS + +#define CC_TYPE_LOGIN_HISTORY_DIALOG (cc_login_history_dialog_get_type ()) +G_DECLARE_FINAL_TYPE (CcLoginHistoryDialog, cc_login_history_dialog, CC, LOGIN_HISTORY_DIALOG, GtkDialog) + +CcLoginHistoryDialog *cc_login_history_dialog_new (ActUser *user); + +G_END_DECLS diff --git a/panels/user-accounts/cc-login-history-dialog.ui b/panels/user-accounts/cc-login-history-dialog.ui new file mode 100644 index 0000000..95b24e7 --- /dev/null +++ b/panels/user-accounts/cc-login-history-dialog.ui @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.8 --> + <template class="CcLoginHistoryDialog" parent="GtkDialog"> + <property name="can_focus">False</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="icon_name">system-users</property> + <child internal-child="headerbar"> + <object class="GtkHeaderBar" id="header_bar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="show_close_button">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <style> + <class name="linked"/> + </style> + <child> + <object class="GtkButton" id="previous_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="valign">center</property> + <signal name="clicked" handler="previous_button_clicked_cb" object="CcLoginHistoryDialog" swapped="yes"/> + <style> + <class name="image-button"/> + </style> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">go-previous-symbolic</property> + <property name="pixel_size">16</property> + </object> + </child> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkButton" id="next_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="valign">center</property> + <signal name="clicked" handler="next_button_clicked_cb" object="CcLoginHistoryDialog" swapped="yes"/> + <style> + <class name="image-button"/> + </style> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">go-next-symbolic</property> + <property name="pixel_size">16</property> + </object> + </child> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child internal-child="vbox"> + <object class="GtkBox"> + <property name="border_width">0</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow"> + <property name="width_request">350</property> + <property name="height_request">300</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="hscrollbar_policy">never</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkListBox" id="history_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="border_width">12</property> + <property name="selection_mode">none</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/user-accounts/cc-password-dialog.c b/panels/user-accounts/cc-password-dialog.c new file mode 100644 index 0000000..b199999 --- /dev/null +++ b/panels/user-accounts/cc-password-dialog.c @@ -0,0 +1,538 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <act/act.h> + +#include "cc-password-dialog.h" +#include "cc-user-accounts-resources.h" +#include "pw-utils.h" +#include "run-passwd.h" +#include "user-utils.h" + +#define PASSWORD_CHECK_TIMEOUT 600 + +struct _CcPasswordDialog +{ + GtkDialog parent_instance; + + GtkBox *action_radio_box; + GtkRadioButton *action_now_radio; + GtkRadioButton *action_login_radio; + GtkButton *ok_button; + GtkLabel *old_password_label; + GtkEntry *old_password_entry; + GtkEntry *password_entry; + GtkLabel *password_hint_label; + GtkLevelBar *strength_indicator; + GtkEntry *verify_entry; + GtkLabel *verify_hint_label; + + gint password_entry_timeout_id; + + ActUser *user; + ActUserPasswordMode password_mode; + + gboolean old_password_ok; + gint old_password_entry_timeout_id; + + PasswdHandler *passwd_handler; +}; + +G_DEFINE_TYPE (CcPasswordDialog, cc_password_dialog, GTK_TYPE_DIALOG) + +static gint +update_password_strength (CcPasswordDialog *self) +{ + const gchar *password; + const gchar *old_password; + const gchar *username; + gint strength_level; + const gchar *hint; + const gchar *verify; + + password = gtk_entry_get_text (self->password_entry); + old_password = gtk_entry_get_text (self->old_password_entry); + username = act_user_get_user_name (self->user); + + pw_strength (password, old_password, username, + &hint, &strength_level); + + gtk_level_bar_set_value (self->strength_indicator, strength_level); + gtk_label_set_label (self->password_hint_label, hint); + + if (strength_level > 1) { + set_entry_validation_checkmark (self->password_entry); + } else if (strlen (password) == 0) { + set_entry_generation_icon (self->password_entry); + } else { + clear_entry_validation_error (self->password_entry); + } + + verify = gtk_entry_get_text (self->verify_entry); + if (strlen (verify) == 0) { + gtk_widget_set_sensitive (GTK_WIDGET (self->verify_entry), strength_level > 1); + } + + return strength_level; +} + +static void +password_changed_cb (PasswdHandler *handler, + GError *error, + CcPasswordDialog *self) +{ + GtkWidget *dialog; + const gchar *primary_text; + const gchar *secondary_text; + + gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE); + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (self)), NULL); + + if (!error) { + gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT); + return; + } + + if (error->code == PASSWD_ERROR_REJECTED) { + primary_text = error->message; + secondary_text = _("Please choose another password."); + + gtk_entry_set_text (self->password_entry, ""); + gtk_widget_grab_focus (GTK_WIDGET (self->password_entry)); + + gtk_entry_set_text (self->verify_entry, ""); + } + else if (error->code == PASSWD_ERROR_AUTH_FAILED) { + primary_text = error->message; + secondary_text = _("Please type your current password again."); + + gtk_entry_set_text (self->old_password_entry, ""); + gtk_widget_grab_focus (GTK_WIDGET (self->old_password_entry)); + } + else { + primary_text = _("Password could not be changed"); + secondary_text = error->message; + } + + dialog = gtk_message_dialog_new (GTK_WINDOW (self), + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", primary_text); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", secondary_text); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +static void +ok_button_clicked_cb (CcPasswordDialog *self) +{ + const gchar *password; + + password = gtk_entry_get_text (self->password_entry); + + switch (self->password_mode) { + case ACT_USER_PASSWORD_MODE_REGULAR: + if (act_user_get_uid (self->user) == getuid ()) { + GdkDisplay *display; + g_autoptr(GdkCursor) cursor = NULL; + + /* When setting a password for the current user, + * use passwd directly, to preserve the audit trail + * and to e.g. update the keyring password. + */ + passwd_change_password (self->passwd_handler, password, + (PasswdCallback) password_changed_cb, self); + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + display = gtk_widget_get_display (GTK_WIDGET (self)); + cursor = gdk_cursor_new_for_display (display, GDK_WATCH); + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (self)), cursor); + gdk_display_flush (display); + return; + } + + act_user_set_password_mode (self->user, ACT_USER_PASSWORD_MODE_REGULAR); + act_user_set_password (self->user, password, ""); + break; + + case ACT_USER_PASSWORD_MODE_SET_AT_LOGIN: + act_user_set_password_mode (self->user, self->password_mode); + act_user_set_automatic_login (self->user, FALSE); + break; + + default: + g_assert_not_reached (); + } + + gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT); +} + +static void +update_sensitivity (CcPasswordDialog *self) +{ + const gchar *password, *verify; + gboolean can_change; + int strength; + + password = gtk_entry_get_text (self->password_entry); + verify = gtk_entry_get_text (self->verify_entry); + + if (self->password_mode == ACT_USER_PASSWORD_MODE_REGULAR) { + strength = update_password_strength (self); + can_change = strength > 1 && strcmp (password, verify) == 0 && + (self->old_password_ok || !gtk_widget_get_visible (GTK_WIDGET (self->old_password_entry))); + } + else { + can_change = TRUE; + } + + gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), can_change); +} + +static void +mode_change (CcPasswordDialog *self, + ActUserPasswordMode mode) +{ + gboolean active; + + active = (mode == ACT_USER_PASSWORD_MODE_REGULAR); + gtk_widget_set_sensitive (GTK_WIDGET (self->password_entry), active); + gtk_widget_set_sensitive (GTK_WIDGET (self->verify_entry), active); + gtk_widget_set_sensitive (GTK_WIDGET (self->old_password_entry), active); + gtk_widget_set_sensitive (GTK_WIDGET (self->password_hint_label), active); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->action_now_radio), active); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->action_login_radio), !active); + + self->password_mode = mode; + update_sensitivity (self); +} + +static void +action_now_radio_toggled_cb (CcPasswordDialog *self) +{ + gint active; + ActUserPasswordMode mode; + + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->action_now_radio)); + mode = active ? ACT_USER_PASSWORD_MODE_REGULAR : ACT_USER_PASSWORD_MODE_SET_AT_LOGIN; + mode_change (self, mode); +} + +static void +update_password_match (CcPasswordDialog *self) +{ + const gchar *password; + const gchar *verify; + const gchar *message = ""; + + password = gtk_entry_get_text (self->password_entry); + verify = gtk_entry_get_text (self->verify_entry); + + if (strlen (verify) > 0) { + if (strcmp (password, verify) != 0) { + message = _("The passwords do not match."); + } + else { + set_entry_validation_checkmark (self->verify_entry); + } + } + gtk_label_set_label (self->verify_hint_label, message); +} + +static gboolean +password_entry_timeout (CcPasswordDialog *self) +{ + update_password_strength (self); + update_sensitivity (self); + update_password_match (self); + + self->password_entry_timeout_id = 0; + + return FALSE; +} + +static void +recheck_password_match (CcPasswordDialog *self) +{ + const gchar *password; + + if (self->password_entry_timeout_id != 0) { + g_source_remove (self->password_entry_timeout_id); + self->password_entry_timeout_id = 0; + } + + gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), FALSE); + + password = gtk_entry_get_text (self->password_entry); + if (strlen (password) == 0) { + gtk_entry_set_visibility (self->password_entry, FALSE); + } + + self->password_entry_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT, + (GSourceFunc) password_entry_timeout, + self); +} + +static void +password_entry_changed (CcPasswordDialog *self) +{ + clear_entry_validation_error (self->password_entry); + clear_entry_validation_error (self->verify_entry); + recheck_password_match (self); +} + +static void +verify_entry_changed (CcPasswordDialog *self) +{ + clear_entry_validation_error (self->verify_entry); + recheck_password_match (self); +} + +static gboolean +password_entry_focus_out_cb (CcPasswordDialog *self) +{ + if (self->password_entry_timeout_id != 0) { + g_source_remove (self->password_entry_timeout_id); + self->password_entry_timeout_id = 0; + } + + if (self->user != NULL) + password_entry_timeout (self); + + return FALSE; +} + +static gboolean +password_entry_key_press_cb (CcPasswordDialog *self, + GdkEvent *event) +{ + GdkEventKey *key = (GdkEventKey *)event; + + if (self->password_entry_timeout_id != 0) { + g_source_remove (self->password_entry_timeout_id); + self->password_entry_timeout_id = 0; + } + + if (key->keyval == GDK_KEY_Tab) + password_entry_timeout (self); + + return FALSE; +} + +static void +auth_cb (PasswdHandler *handler, + GError *error, + CcPasswordDialog *self) +{ + if (error) { + self->old_password_ok = FALSE; + } + else { + self->old_password_ok = TRUE; + set_entry_validation_checkmark (self->old_password_entry); + } + + update_sensitivity (self); +} + +static gboolean +old_password_entry_timeout (CcPasswordDialog *self) +{ + const gchar *text; + + update_sensitivity (self); + + text = gtk_entry_get_text (self->old_password_entry); + if (!self->old_password_ok) { + passwd_authenticate (self->passwd_handler, text, (PasswdCallback)auth_cb, self); + } + + self->old_password_entry_timeout_id = 0; + + return FALSE; +} + +static gboolean +old_password_entry_focus_out_cb (CcPasswordDialog *self) +{ + if (self->old_password_entry_timeout_id != 0) { + g_source_remove (self->old_password_entry_timeout_id); + self->old_password_entry_timeout_id = 0; + } + + if (self->user != NULL) + old_password_entry_timeout (self); + + return FALSE; +} + +static void +old_password_entry_changed (CcPasswordDialog *self) +{ + if (self->old_password_entry_timeout_id != 0) { + g_source_remove (self->old_password_entry_timeout_id); + self->old_password_entry_timeout_id = 0; + } + + clear_entry_validation_error (self->old_password_entry); + gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), FALSE); + + self->old_password_ok = FALSE; + self->old_password_entry_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT, + (GSourceFunc) old_password_entry_timeout, + self); +} + +static void +password_entry_icon_press_cb (CcPasswordDialog *self) +{ + g_autofree gchar *pwd = NULL; + + pwd = pw_generate (); + if (pwd == NULL) + return; + + gtk_entry_set_text (self->password_entry, pwd); + gtk_entry_set_text (self->verify_entry, pwd); + gtk_entry_set_visibility (self->password_entry, TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (self->verify_entry), TRUE); +} + +static void +cc_password_dialog_dispose (GObject *object) +{ + CcPasswordDialog *self = CC_PASSWORD_DIALOG (object); + + g_clear_object (&self->user); + + if (self->passwd_handler) { + passwd_destroy (self->passwd_handler); + self->passwd_handler = NULL; + } + + if (self->old_password_entry_timeout_id != 0) { + g_source_remove (self->old_password_entry_timeout_id); + self->old_password_entry_timeout_id = 0; + } + + if (self->password_entry_timeout_id != 0) { + g_source_remove (self->password_entry_timeout_id); + self->password_entry_timeout_id = 0; + } + + G_OBJECT_CLASS (cc_password_dialog_parent_class)->dispose (object); +} + +static void +cc_password_dialog_class_init (CcPasswordDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = cc_password_dialog_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/user-accounts/cc-password-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, action_radio_box); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, action_now_radio); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, action_login_radio); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, ok_button); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, old_password_label); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, old_password_entry); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, password_entry); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, password_hint_label); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, strength_indicator); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, verify_entry); + gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, verify_hint_label); + + gtk_widget_class_bind_template_callback (widget_class, action_now_radio_toggled_cb); + gtk_widget_class_bind_template_callback (widget_class, old_password_entry_changed); + gtk_widget_class_bind_template_callback (widget_class, old_password_entry_focus_out_cb); + gtk_widget_class_bind_template_callback (widget_class, ok_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, password_entry_changed); + gtk_widget_class_bind_template_callback (widget_class, password_entry_focus_out_cb); + gtk_widget_class_bind_template_callback (widget_class, password_entry_icon_press_cb); + gtk_widget_class_bind_template_callback (widget_class, password_entry_key_press_cb); + gtk_widget_class_bind_template_callback (widget_class, verify_entry_changed); +} + +static void +cc_password_dialog_init (CcPasswordDialog *self) +{ + g_resources_register (cc_user_accounts_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +CcPasswordDialog * +cc_password_dialog_new (ActUser *user) +{ + CcPasswordDialog *self; + + g_return_val_if_fail (ACT_IS_USER (user), NULL); + + self = g_object_new (CC_TYPE_PASSWORD_DIALOG, + "use-header-bar", 1, + NULL); + + self->user = g_object_ref (user); + + if (act_user_get_uid (self->user) == getuid ()) { + gboolean visible; + + mode_change (self, ACT_USER_PASSWORD_MODE_REGULAR); + gtk_widget_hide (GTK_WIDGET (self->action_radio_box)); + + visible = (act_user_get_password_mode (user) != ACT_USER_PASSWORD_MODE_NONE); + gtk_widget_set_visible (GTK_WIDGET (self->old_password_label), visible); + gtk_widget_set_visible (GTK_WIDGET (self->old_password_entry), visible); + self->old_password_ok = !visible; + + self->passwd_handler = passwd_init (); + } + else { + mode_change (self, ACT_USER_PASSWORD_MODE_SET_AT_LOGIN); + gtk_widget_show (GTK_WIDGET (self->action_radio_box)); + + gtk_widget_hide (GTK_WIDGET (self->old_password_label)); + gtk_widget_hide (GTK_WIDGET (self->old_password_entry)); + self->old_password_ok = TRUE; + } + + if (self->old_password_ok == FALSE) + gtk_widget_grab_focus (GTK_WIDGET (self->old_password_entry)); + else + gtk_widget_grab_focus (GTK_WIDGET (self->password_entry)); + + gtk_widget_grab_default (GTK_WIDGET (self->ok_button)); + + return self; +} diff --git a/panels/user-accounts/cc-password-dialog.h b/panels/user-accounts/cc-password-dialog.h new file mode 100644 index 0000000..958366b --- /dev/null +++ b/panels/user-accounts/cc-password-dialog.h @@ -0,0 +1,33 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#pragma once + +#include <gtk/gtk.h> +#include <act/act.h> + +G_BEGIN_DECLS + +#define CC_TYPE_PASSWORD_DIALOG (cc_password_dialog_get_type ()) +G_DECLARE_FINAL_TYPE (CcPasswordDialog, cc_password_dialog, CC, PASSWORD_DIALOG, GtkDialog) + +CcPasswordDialog *cc_password_dialog_new (ActUser *user); + +G_END_DECLS diff --git a/panels/user-accounts/cc-password-dialog.ui b/panels/user-accounts/cc-password-dialog.ui new file mode 100644 index 0000000..bfcc758 --- /dev/null +++ b/panels/user-accounts/cc-password-dialog.ui @@ -0,0 +1,305 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 2.12 --> + <!-- interface-naming-policy toplevel-contextual --> + <template class="CcPasswordDialog" parent="GtkDialog"> + <property name="border_width">6</property> + <property name="title" translatable="yes">Change Password</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="window_position">center-on-parent</property> + <property name="icon_name">system-users</property> + <property name="type_hint">dialog</property> + <child internal-child="headerbar"> + <object class="GtkHeaderBar" id="dialog-header-bar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="show_close_button">False</property> + <child> + <object class="GtkButton" id="cancel_button"> + <property name="label" translatable="yes">_Cancel</property> + <property name="visible">True</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <property name="valign">center</property> + <style> + <class name="text-button"/> + </style> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkButton" id="ok_button"> + <property name="label" translatable="yes">Ch_ange</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <property name="valign">center</property> + <signal name="clicked" handler="ok_button_clicked_cb" object="CcPasswordDialog" swapped="yes"/> + <style> + <class name="text-button"/> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + <child internal-child="vbox"> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">6</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <property name="hexpand">True</property> + <child> + <object class="GtkEntry" id="verify_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="hexpand">True</property> + <property name="activates_default">True</property> + <property name="input_purpose">password</property> + <signal name="notify::text" handler="verify_entry_changed" object="CcPasswordDialog" swapped="yes"/> + <signal name="activate" handler="password_entry_focus_out_cb" object="CcPasswordDialog" swapped="yes"/> + <signal name="focus-out-event" handler="password_entry_focus_out_cb" after="yes" object="CcPasswordDialog" swapped="yes"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">7</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="password_hint_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label" translatable="yes"></property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="height-request">50</property> + <property name="wrap">True</property> + <property name="hexpand">True</property> + <property name="wrap_mode">word-char</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.83"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">6</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="verify_hint_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label" translatable="yes"></property> + <property name="width-chars">35</property> + <property name="max-width-chars">35</property> + <property name="wrap">True</property> + <property name="hexpand">True</property> + <property name="wrap_mode">word-char</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.83"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">8</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Confirm New Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">verify_entry</property> + <property name="margin_start">25</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">7</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_New Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">password_entry</property> + <property name="margin_start">25</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="password_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="hexpand">True</property> + <property name="activates_default">True</property> + <property name="input_purpose">password</property> + <signal name="notify::text" handler="password_entry_changed" object="CcPasswordDialog" swapped="yes"/> + <signal name="activate" handler="password_entry_focus_out_cb" object="CcPasswordDialog" swapped="yes"/> + <signal name="focus-out-event" handler="password_entry_focus_out_cb" after="yes" object="CcPasswordDialog" swapped="yes"/> + <signal name="key-press-event" handler="password_entry_key_press_cb" object="CcPasswordDialog" swapped="yes"/> + <signal name="icon-press" handler="password_entry_icon_press_cb" object="CcPasswordDialog" swapped="yes"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + </packing> + </child> + <child> + <object class="GtkLevelBar" id="strength_indicator"> + <property name="visible">True</property> + <property name="mode">discrete</property> + <property name="max-value">5</property> + <offsets> + <offset name="strength-weak" value="1"/> + <offset name="strength-low" value="2"/> + <offset name="strength-medium" value="3"/> + <offset name="strength-good" value="4"/> + <offset name="strength-high" value="5"/> + </offsets> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">5</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="old_password_label"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Current _Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">old_password_entry</property> + <property name="margin_start">25</property> + <property name="margin_bottom">12</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="old_password_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="hexpand">True</property> + <property name="activates_default">True</property> + <property name="margin_bottom">12</property> + <property name="input_purpose">password</property> + <signal name="notify::text" handler="old_password_entry_changed" object="CcPasswordDialog" swapped="yes"/> + <signal name="activate" handler="old_password_entry_focus_out_cb" object="CcPasswordDialog" swapped="yes"/> + <signal name="focus-out-event" handler="old_password_entry_focus_out_cb" after="yes" object="CcPasswordDialog" swapped="yes"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkBox" id="action_radio_box"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkRadioButton" id="action_login_radio"> + <property name="label" translatable="yes">Allow user to change their password on next login</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="action_now_radio"> + <property name="label" translatable="yes">Set a password now</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <property name="group">action_login_radio</property> + <signal name="toggled" handler="action_now_radio_toggled_cb" object="CcPasswordDialog" swapped="yes"/> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">3</property> + </packing> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">cancel_button</action-widget> + </action-widgets> + </template> +</interface> diff --git a/panels/user-accounts/cc-realm-manager.c b/panels/user-accounts/cc-realm-manager.c new file mode 100644 index 0000000..9b8077e --- /dev/null +++ b/panels/user-accounts/cc-realm-manager.c @@ -0,0 +1,812 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2012 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Stef Walter <stefw@gnome.org> + */ + +#include "config.h" + +#include "cc-realm-manager.h" + +#include <krb5/krb5.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> + + +struct _CcRealmManager { + CcRealmObjectManagerClient parent_instance; + + CcRealmProvider *provider; + guint diagnostics_sig; +}; + +enum { + REALM_ADDED, + NUM_SIGNALS, +}; + +static gint signals[NUM_SIGNALS] = { 0, }; + +G_DEFINE_TYPE (CcRealmManager, cc_realm_manager, CC_REALM_TYPE_OBJECT_MANAGER_CLIENT); + +GQuark +cc_realm_error_get_quark (void) +{ + static GQuark quark = 0; + if (quark == 0) + quark = g_quark_from_static_string ("cc-realm-error"); + return quark; +} + +static gboolean +is_realm_with_kerberos_and_membership (gpointer object) +{ + GDBusInterface *interface; + + if (!G_IS_DBUS_OBJECT (object)) + return FALSE; + + interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.Kerberos"); + if (interface == NULL) + return FALSE; + g_object_unref (interface); + + interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.KerberosMembership"); + if (interface == NULL) + return FALSE; + g_object_unref (interface); + + return TRUE; +} + +static void +on_interface_added (CcRealmManager *self, + GDBusObject *object, + GDBusInterface *interface) +{ + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (interface), G_MAXINT); +} + +static void +on_object_added (CcRealmManager *self, + GDBusObject *object) +{ + GList *interfaces, *l; + + interfaces = g_dbus_object_get_interfaces (object); + for (l = interfaces; l != NULL; l = g_list_next (l)) + on_interface_added (self, object, l->data); + g_list_free_full (interfaces, g_object_unref); + + if (is_realm_with_kerberos_and_membership (object)) { + g_debug ("Saw realm: %s", g_dbus_object_get_object_path (object)); + g_signal_emit (self, signals[REALM_ADDED], 0, object); + } +} + +static void +cc_realm_manager_init (CcRealmManager *self) +{ + g_signal_connect (self, "object-added", G_CALLBACK (on_object_added), NULL); + g_signal_connect (self, "interface-added", G_CALLBACK (on_interface_added), NULL); +} + +static void +cc_realm_manager_dispose (GObject *obj) +{ + CcRealmManager *self = CC_REALM_MANAGER (obj); + GDBusConnection *connection; + + g_clear_object (&self->provider); + + if (self->diagnostics_sig) { + connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (self)); + if (connection != NULL) + g_dbus_connection_signal_unsubscribe (connection, self->diagnostics_sig); + self->diagnostics_sig = 0; + } + + G_OBJECT_CLASS (cc_realm_manager_parent_class)->dispose (obj); +} + +static void +cc_realm_manager_class_init (CcRealmManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = cc_realm_manager_dispose; + + signals[REALM_ADDED] = g_signal_new ("realm-added", CC_TYPE_REALM_MANAGER, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 1, CC_REALM_TYPE_OBJECT); +} + +static void +on_realm_diagnostics (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + const gchar *message; + const gchar *unused; + + if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ss)"))) { + /* Data is already formatted appropriately for stderr */ + g_variant_get (parameters, "(&s&s)", &message, &unused); + g_printerr ("%s", message); + } +} + +static void +on_provider_new (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + CcRealmManager *manager = g_task_get_task_data (task); + GError *error = NULL; + + manager->provider = cc_realm_provider_proxy_new_finish (result, &error); + if (error == NULL) { + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (manager->provider), -1); + g_debug ("Created realm manager"); + g_task_return_pointer (task, g_object_ref (manager), g_object_unref); + } else { + g_task_return_error (task, error); + } + + g_object_unref (task); +} + +static void +on_manager_new (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + CcRealmManager *manager; + GDBusConnection *connection; + GError *error = NULL; + GObject *object; + guint sig; + + object = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, &error); + if (error == NULL) { + manager = CC_REALM_MANAGER (object); + connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object)); + + g_debug ("Connected to realmd"); + + sig = g_dbus_connection_signal_subscribe (connection, + "org.freedesktop.realmd", + "org.freedesktop.realmd.Service", + "Diagnostics", + NULL, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + on_realm_diagnostics, + NULL, + NULL); + manager->diagnostics_sig = sig; + + g_task_set_task_data (task, manager, g_object_unref); + + cc_realm_provider_proxy_new (connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + "org.freedesktop.realmd", + "/org/freedesktop/realmd", + g_task_get_cancellable (task), + on_provider_new, task); + } else { + g_task_return_error (task, error); + g_object_unref (task); + } +} + +void +cc_realm_manager_new (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + g_debug ("Connecting to realmd..."); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, cc_realm_manager_new); + + g_async_initable_new_async (CC_TYPE_REALM_MANAGER, G_PRIORITY_DEFAULT, + cancellable, on_manager_new, task, + "flags", G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, + "name", "org.freedesktop.realmd", + "bus-type", G_BUS_TYPE_SYSTEM, + "object-path", "/org/freedesktop/realmd", + "get-proxy-type-func", cc_realm_object_manager_client_get_proxy_type, + NULL); +} + +CcRealmManager * +cc_realm_manager_new_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, NULL), NULL); + g_return_val_if_fail (g_async_result_is_tagged (result, cc_realm_manager_new), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +realms_free (gpointer data) +{ + g_list_free_full (data, g_object_unref); +} + +static void +on_provider_discover (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + CcRealmManager *manager = g_task_get_source_object (task); + GDBusObject *object; + GError *error = NULL; + gboolean no_membership = FALSE; + gchar **realms; + gint relevance; + gint i; + GList *kerberos_realms = NULL; + + cc_realm_provider_call_discover_finish (CC_REALM_PROVIDER (source), &relevance, + &realms, result, &error); + if (error == NULL) { + for (i = 0; realms[i]; i++) { + object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (manager), realms[i]); + if (object == NULL) { + g_warning ("Realm is not in object manager: %s", realms[i]); + } else { + if (is_realm_with_kerberos_and_membership (object)) { + g_debug ("Discovered realm: %s", realms[i]); + kerberos_realms = g_list_prepend (kerberos_realms, object); + } else { + g_debug ("Realm does not support kerberos membership: %s", realms[i]); + no_membership = TRUE; + g_object_unref (object); + } + } + } + g_strfreev (realms); + + if (!kerberos_realms && no_membership) { + g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_GENERIC, + _("Cannot automatically join this type of domain")); + } else if (!kerberos_realms) { + g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_GENERIC, + _("No such domain or realm found")); + } else { + kerberos_realms = g_list_reverse (kerberos_realms); + g_task_return_pointer (task, kerberos_realms, realms_free); + } + } else { + g_task_return_error (task, error); + } + + g_object_unref (task); +} + +void +cc_realm_manager_discover (CcRealmManager *self, + const gchar *input, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + GVariant *options; + + g_return_if_fail (CC_IS_REALM_MANAGER (self)); + g_return_if_fail (input != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + g_debug ("Discovering realms for: %s", input); + + task = g_task_new (G_OBJECT (self), cancellable, callback, user_data); + g_task_set_source_tag (task, cc_realm_manager_discover); + + options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0); + + cc_realm_provider_call_discover (self->provider, input, options, cancellable, + on_provider_discover, task); +} + +GList * +cc_realm_manager_discover_finish (CcRealmManager *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (CC_IS_REALM_MANAGER (self), NULL); + g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (self)), NULL); + g_return_val_if_fail (g_async_result_is_tagged (result, cc_realm_manager_discover), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +GList * +cc_realm_manager_get_realms (CcRealmManager *self) +{ + GList *objects; + GList *realms = NULL; + GList *l; + + g_return_val_if_fail (CC_IS_REALM_MANAGER (self), NULL); + + objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self)); + for (l = objects; l != NULL; l = g_list_next (l)) { + if (is_realm_with_kerberos_and_membership (l->data)) + realms = g_list_prepend (realms, g_object_ref (l->data)); + } + + g_list_free_full (objects, g_object_unref); + return realms; +} + +static void +string_replace (GString *string, + const gchar *find, + const gchar *replace) +{ + const gchar *at; + gssize pos; + + at = strstr (string->str, find); + if (at != NULL) { + pos = at - string->str; + g_string_erase (string, pos, strlen (find)); + g_string_insert (string, pos, replace); + } +} + +gchar * +cc_realm_calculate_login (CcRealmCommon *realm, + const gchar *username) +{ + GString *string; + const gchar *const *formats; + gchar *login = NULL; + + formats = cc_realm_common_get_login_formats (realm); + if (formats[0] != NULL) { + string = g_string_new (formats[0]); + string_replace (string, "%U", username); + string_replace (string, "%D", cc_realm_common_get_name (realm)); + login = g_string_free (string, FALSE); + } + + return login; + +} + +gboolean +cc_realm_is_configured (CcRealmObject *realm) +{ + CcRealmCommon *common; + const gchar *configured; + gboolean is = FALSE; + + common = cc_realm_object_get_common (realm); + if (common != NULL) { + configured = cc_realm_common_get_configured (common); + is = configured != NULL && !g_str_equal (configured, ""); + g_object_unref (common); + } + + return is; +} + +static const gchar * +find_supported_credentials (CcRealmKerberosMembership *membership, + const gchar *owner) +{ + const gchar *cred_owner; + const gchar *cred_type; + GVariant *supported; + GVariantIter iter; + + supported = cc_realm_kerberos_membership_get_supported_join_credentials (membership); + g_return_val_if_fail (supported != NULL, NULL); + + g_variant_iter_init (&iter, supported); + while (g_variant_iter_loop (&iter, "(&s&s)", &cred_type, &cred_owner)) { + if (g_str_equal (owner, cred_owner)) { + if (g_str_equal (cred_type, "ccache") || + g_str_equal (cred_type, "password")) { + return g_intern_string (cred_type); + } + } + } + + return NULL; +} + +static gboolean +realm_join_as_owner (CcRealmObject *realm, + const gchar *owner, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + CcRealmKerberosMembership *membership; + GVariant *contents; + GVariant *options; + GVariant *option; + GVariant *creds; + const gchar *type; + + membership = cc_realm_object_get_kerberos_membership (realm); + g_return_val_if_fail (membership != NULL, FALSE); + + type = find_supported_credentials (membership, owner); + if (type == NULL) { + g_debug ("Couldn't find supported credential type for owner: %s", owner); + g_object_unref (membership); + return FALSE; + } + + if (g_str_equal (type, "ccache")) { + g_debug ("Using a kerberos credential cache to join the realm"); + contents = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), + g_bytes_get_data (credentials, NULL), + g_bytes_get_size (credentials), + TRUE, (GDestroyNotify)g_bytes_unref, credentials); + + } else if (g_str_equal (type, "password")) { + g_debug ("Using a user/password to join the realm"); + contents = g_variant_new ("(ss)", login, password); + + } else { + g_assert_not_reached (); + } + + creds = g_variant_new ("(ssv)", type, owner, contents); + option = g_variant_new ("{sv}", "manage-system", g_variant_new_boolean (FALSE)); + options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), &option, 1); + + g_debug ("Calling the Join() method with %s credentials", owner); + + cc_realm_kerberos_membership_call_join (membership, creds, options, + cancellable, callback, user_data); + g_object_unref (membership); + + return TRUE; +} + +gboolean +cc_realm_join_as_user (CcRealmObject *realm, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_val_if_fail (CC_REALM_IS_OBJECT (realm), FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (login != NULL, FALSE); + g_return_val_if_fail (password != NULL, FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + + return realm_join_as_owner (realm, "user", login, password, + credentials, cancellable, callback, user_data); +} + +gboolean +cc_realm_join_as_admin (CcRealmObject *realm, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_val_if_fail (CC_REALM_IS_OBJECT (realm), FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (login != NULL, FALSE); + g_return_val_if_fail (password != NULL, FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + + return realm_join_as_owner (realm, "administrator", login, password, credentials, + cancellable, callback, user_data); +} + +gboolean +cc_realm_join_finish (CcRealmObject *realm, + GAsyncResult *result, + GError **error) +{ + CcRealmKerberosMembership *membership; + GError *call_error = NULL; + gchar *dbus_error; + + g_return_val_if_fail (CC_REALM_IS_OBJECT (realm), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + membership = cc_realm_object_get_kerberos_membership (realm); + g_return_val_if_fail (membership != NULL, FALSE); + + cc_realm_kerberos_membership_call_join_finish (membership, result, &call_error); + g_object_unref (membership); + + if (call_error == NULL) { + g_debug ("Completed Join() method call"); + return TRUE; + } + + dbus_error = g_dbus_error_get_remote_error (call_error); + if (dbus_error == NULL) { + g_debug ("Join() failed because of %s", call_error->message); + g_propagate_error (error, call_error); + return FALSE; + } + + g_dbus_error_strip_remote_error (call_error); + + if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.AuthenticationFailed")) { + g_debug ("Join() failed because of invalid/insufficient credentials"); + g_set_error (error, CC_REALM_ERROR, CC_REALM_ERROR_BAD_LOGIN, + "%s", call_error->message); + g_error_free (call_error); + } else { + g_debug ("Join() failed because of %s", call_error->message); + g_propagate_error (error, call_error); + } + + g_free (dbus_error); + return FALSE; +} + +typedef struct { + gchar *domain; + gchar *realm; + gchar *user; + gchar *password; +} LoginClosure; + +static void +login_closure_free (gpointer data) +{ + LoginClosure *login = data; + g_free (login->domain); + g_free (login->realm); + g_free (login->user); + g_free (login->password); + g_slice_free (LoginClosure, login); +} + +static krb5_error_code +login_perform_kinit (krb5_context k5, + const gchar *realm, + const gchar *login, + const gchar *password, + const gchar *filename) +{ + krb5_get_init_creds_opt *opts; + krb5_error_code code; + krb5_principal principal; + krb5_ccache ccache; + krb5_creds creds; + gchar *name; + + name = g_strdup_printf ("%s@%s", login, realm); + code = krb5_parse_name (k5, name, &principal); + + if (code != 0) { + g_debug ("Couldn't parse principal name: %s: %s", + name, krb5_get_error_message (k5, code)); + g_free (name); + return code; + } + + g_debug ("Using principal name to kinit: %s", name); + g_free (name); + + if (filename == NULL) + code = krb5_cc_default (k5, &ccache); + else + code = krb5_cc_resolve (k5, filename, &ccache); + + if (code != 0) { + krb5_free_principal (k5, principal); + g_debug ("Couldn't open credential cache: %s: %s", + filename ? filename : "<default>", + krb5_get_error_message (k5, code)); + return code; + } + + code = krb5_get_init_creds_opt_alloc (k5, &opts); + g_return_val_if_fail (code == 0, code); + + code = krb5_get_init_creds_opt_set_out_ccache (k5, opts, ccache); + g_return_val_if_fail (code == 0, code); + + code = krb5_get_init_creds_password (k5, &creds, principal, + (char *)password, + NULL, 0, 0, NULL, opts); + + krb5_get_init_creds_opt_free (k5, opts); + krb5_cc_close (k5, ccache); + krb5_free_principal (k5, principal); + + if (code == 0) { + g_debug ("kinit succeeded"); + krb5_free_cred_contents (k5, &creds); + } else { + g_debug ("kinit failed: %s", krb5_get_error_message (k5, code)); + } + + return code; +} + +static void +kinit_thread_func (GTask *task, + gpointer object, + gpointer task_data, + GCancellable *cancellable) +{ + LoginClosure *login = task_data; + krb5_context k5 = NULL; + krb5_error_code code; + GError *error = NULL; + gchar *filename = NULL; + gchar *contents; + gsize length; + gint temp_fd; + + filename = g_build_filename (g_get_user_runtime_dir (), + "um-krb5-creds.XXXXXX", NULL); + temp_fd = g_mkstemp_full (filename, O_RDWR, S_IRUSR | S_IWUSR); + if (temp_fd == -1) { + g_warning ("Couldn't create credential cache file: %s: %s", + filename, g_strerror (errno)); + g_free (filename); + filename = NULL; + } else { + close (temp_fd); + } + + code = krb5_init_context (&k5); + if (code == 0) { + code = login_perform_kinit (k5, login->realm, login->user, + login->password, filename); + } + + switch (code) { + case 0: + if (filename != NULL) { + g_file_get_contents (filename, &contents, &length, &error); + if (error == NULL) { + g_debug ("Read in credential cache: %s", filename); + } else { + g_warning ("Couldn't read credential cache: %s: %s", + filename, error->message); + g_error_free (error); + } + + g_task_return_pointer (task, g_bytes_new_take (contents, length), (GDestroyNotify) g_bytes_unref); + } + break; + + case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: + case KRB5KDC_ERR_POLICY: + g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_BAD_LOGIN, + _("Cannot log in as %s at the %s domain"), + login->user, login->domain); + break; + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_BAD_PASSWORD, + _("Invalid password, please try again")); + break; + case KRB5_PREAUTH_FAILED: + case KRB5KDC_ERR_KEY_EXP: + case KRB5KDC_ERR_CLIENT_REVOKED: + case KRB5KDC_ERR_ETYPE_NOSUPP: + case KRB5_PROG_ETYPE_NOSUPP: + g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_CANNOT_AUTH, + _("Cannot log in as %s at the %s domain"), + login->user, login->domain); + break; + default: + g_task_return_new_error (task, CC_REALM_ERROR, CC_REALM_ERROR_GENERIC, + _("Couldn’t connect to the %s domain: %s"), + login->domain, krb5_get_error_message (k5, code)); + break; + } + + if (filename) { + g_unlink (filename); + g_debug ("Deleted credential cache: %s", filename); + g_free (filename); + } + + if (k5) + krb5_free_context (k5); + + g_object_unref (task); +} + +void +cc_realm_login (CcRealmObject *realm, + const gchar *user, + const gchar *password, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LoginClosure *login; + CcRealmKerberos *kerberos; + + g_return_if_fail (CC_REALM_IS_OBJECT (realm)); + g_return_if_fail (user != NULL); + g_return_if_fail (password != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + kerberos = cc_realm_object_get_kerberos (realm); + g_return_if_fail (kerberos != NULL); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, cc_realm_login); + + login = g_slice_new0 (LoginClosure); + login->domain = g_strdup (cc_realm_kerberos_get_domain_name (kerberos)); + login->realm = g_strdup (cc_realm_kerberos_get_realm_name (kerberos)); + login->user = g_strdup (user); + login->password = g_strdup (password); + g_task_set_task_data (task, login, login_closure_free); + + g_task_set_return_on_cancel (task, TRUE); + g_task_run_in_thread (task, kinit_thread_func); + + g_object_unref (kerberos); +} + +GBytes * +cc_realm_login_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE); + g_return_val_if_fail (g_async_result_is_tagged (result, cc_realm_login), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return g_task_propagate_pointer (G_TASK (result), error); +} diff --git a/panels/user-accounts/cc-realm-manager.h b/panels/user-accounts/cc-realm-manager.h new file mode 100644 index 0000000..7e68e8e --- /dev/null +++ b/panels/user-accounts/cc-realm-manager.h @@ -0,0 +1,97 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Stef Walter <stefw@gnome.org> + */ + +#pragma once + +#include "cc-realm-generated.h" + +G_BEGIN_DECLS + +typedef enum { + CC_REALM_ERROR_BAD_LOGIN, + CC_REALM_ERROR_BAD_PASSWORD, + CC_REALM_ERROR_CANNOT_AUTH, + CC_REALM_ERROR_GENERIC, +} CcRealmErrors; + +#define CC_REALM_ERROR (cc_realm_error_get_quark ()) + +GQuark cc_realm_error_get_quark (void) G_GNUC_CONST; + +#define CC_TYPE_REALM_MANAGER (cc_realm_manager_get_type ()) +G_DECLARE_FINAL_TYPE (CcRealmManager, cc_realm_manager, CC, REALM_MANAGER, CcRealmObjectManagerClient) + +void cc_realm_manager_new (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +CcRealmManager * cc_realm_manager_new_finish (GAsyncResult *result, + GError **error); + +void cc_realm_manager_discover (CcRealmManager *self, + const gchar *input, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GList * cc_realm_manager_discover_finish (CcRealmManager *self, + GAsyncResult *result, + GError **error); + +GList * cc_realm_manager_get_realms (CcRealmManager *self); + +void cc_realm_login (CcRealmObject *realm, + const gchar *login, + const gchar *password, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GBytes * cc_realm_login_finish (GAsyncResult *result, + GError **error); + +gboolean cc_realm_join_as_user (CcRealmObject *realm, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) + G_GNUC_WARN_UNUSED_RESULT; + +gboolean cc_realm_join_as_admin (CcRealmObject *realm, + const gchar *login, + const gchar *password, + GBytes *credentials, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) + G_GNUC_WARN_UNUSED_RESULT; + +gboolean cc_realm_join_finish (CcRealmObject *realm, + GAsyncResult *result, + GError **error); + +gboolean cc_realm_is_configured (CcRealmObject *realm); + +gchar * cc_realm_calculate_login (CcRealmCommon *realm, + const gchar *username); + +G_END_DECLS diff --git a/panels/user-accounts/cc-user-image.c b/panels/user-accounts/cc-user-image.c new file mode 100644 index 0000000..8dc6389 --- /dev/null +++ b/panels/user-accounts/cc-user-image.c @@ -0,0 +1,137 @@ +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright 2015 Red Hat, Inc. + */ + +#include "cc-user-image.h" + +#include <gtk/gtk.h> +#include <act/act.h> +#include <sys/stat.h> + +#include "user-utils.h" + +struct _CcUserImage { + GtkImage parent_instance; + + ActUser *user; +}; + +G_DEFINE_TYPE (CcUserImage, cc_user_image, GTK_TYPE_IMAGE) + +static cairo_surface_t * +render_user_icon (ActUser *user, + gint icon_size, + gint scale) +{ + g_autoptr(GdkPixbuf) source_pixbuf = NULL; + GdkPixbuf *pixbuf = NULL; + const gchar *icon_file; + cairo_surface_t *surface = NULL; + + g_return_val_if_fail (ACT_IS_USER (user), NULL); + g_return_val_if_fail (icon_size > 12, NULL); + + icon_file = act_user_get_icon_file (user); + pixbuf = NULL; + if (icon_file) { + source_pixbuf = gdk_pixbuf_new_from_file_at_size (icon_file, + icon_size * scale, + icon_size * scale, + NULL); + if (source_pixbuf) + pixbuf = round_image (source_pixbuf); + } + + if (pixbuf != NULL) { + goto out; + } + + if (source_pixbuf != NULL) { + g_object_unref (source_pixbuf); + } + + source_pixbuf = generate_default_avatar (user, icon_size * scale); + if (source_pixbuf) + pixbuf = round_image (source_pixbuf); + out: + + if (pixbuf != NULL) { + surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, NULL); + g_object_unref (pixbuf); + } + + return surface; +} + +static void +render_image (CcUserImage *image) +{ + cairo_surface_t *surface; + gint scale, pixel_size; + + if (image->user == NULL) + return; + + pixel_size = gtk_image_get_pixel_size (GTK_IMAGE (image)); + scale = gtk_widget_get_scale_factor (GTK_WIDGET (image)); + surface = render_user_icon (image->user, + pixel_size > 0 ? pixel_size : 48, + scale); + gtk_image_set_from_surface (GTK_IMAGE (image), surface); + cairo_surface_destroy (surface); +} + +void +cc_user_image_set_user (CcUserImage *image, + ActUser *user) +{ + g_clear_object (&image->user); + image->user = g_object_ref (user); + + render_image (image); +} + +static void +cc_user_image_finalize (GObject *object) +{ + CcUserImage *image = CC_USER_IMAGE (object); + + g_clear_object (&image->user); + + G_OBJECT_CLASS (cc_user_image_parent_class)->finalize (object); +} + +static void +cc_user_image_class_init (CcUserImageClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = cc_user_image_finalize; +} + +static void +cc_user_image_init (CcUserImage *image) +{ + g_signal_connect_swapped (image, "notify::scale-factor", G_CALLBACK (render_image), image); + g_signal_connect_swapped (image, "notify::pixel-size", G_CALLBACK (render_image), image); +} + +GtkWidget * +cc_user_image_new (void) +{ + return g_object_new (CC_TYPE_USER_IMAGE, NULL); +} diff --git a/panels/user-accounts/cc-user-image.h b/panels/user-accounts/cc-user-image.h new file mode 100644 index 0000000..a7f69a8 --- /dev/null +++ b/panels/user-accounts/cc-user-image.h @@ -0,0 +1,32 @@ +/* + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright 2015 Red Hat, Inc. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <act/act.h> + +G_BEGIN_DECLS + +#define CC_TYPE_USER_IMAGE (cc_user_image_get_type ()) +G_DECLARE_FINAL_TYPE (CcUserImage, cc_user_image, CC, USER_IMAGE, GtkImage) + +GtkWidget *cc_user_image_new (void); +void cc_user_image_set_user (CcUserImage *image, ActUser *user); + +G_END_DECLS diff --git a/panels/user-accounts/cc-user-panel.c b/panels/user-accounts/cc-user-panel.c new file mode 100644 index 0000000..5a9b5c2 --- /dev/null +++ b/panels/user-accounts/cc-user-panel.c @@ -0,0 +1,1678 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include "cc-user-panel.h" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <locale.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <polkit/polkit.h> +#include <act/act.h> +#include <cairo-gobject.h> + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-languages.h> + +#ifdef HAVE_MALCONTENT +#include <libmalcontent/malcontent.h> +#endif + +#include "cc-add-user-dialog.h" +#include "cc-avatar-chooser.h" +#include "cc-carousel.h" +#include "cc-language-chooser.h" +#include "cc-login-history-dialog.h" +#include "cc-password-dialog.h" +#include "cc-realm-manager.h" +#include "cc-user-accounts-resources.h" +#include "cc-user-image.h" +#include "cc-fingerprint-manager.h" +#include "cc-fingerprint-dialog.h" +#include "user-utils.h" + +#include "cc-common-language.h" +#include "cc-permission-infobar.h" +#include "cc-util.h" +#include "list-box-helper.h" + +#define USER_ACCOUNTS_PERMISSION "org.gnome.controlcenter.user-accounts.administration" + +struct _CcUserPanel { + CcPanel parent_instance; + + ActUserManager *um; + GSettings *login_screen_settings; + + GtkBox *accounts_box; + GtkBox *account_settings_box; + GtkListBox *account_settings_listbox; + GtkListBox *authentication_and_login_listbox; + GtkListBoxRow *account_type_row; + GtkSwitch *account_type_switch; + GtkButton *add_user_button; + GtkListBoxRow *autologin_row; + GtkSwitch *autologin_switch; + CcCarousel *carousel; + GtkLabel *fingerprint_state_label; + GtkListBoxRow *fingerprint_row; + GtkStack *full_name_stack; + GtkLabel *full_name_label; + GtkToggleButton *full_name_edit_button; + GtkEntry *full_name_entry; + GtkLabel *language_button_label; + GtkListBoxRow *language_row; + GtkLabel *last_login_button_label; + GtkListBoxRow *last_login_row; + GtkBox *no_users_box; + GtkRevealer *notification_revealer; + GtkLabel *password_button_label; +#ifdef HAVE_MALCONTENT + GtkLabel *parental_controls_button_label; + GtkImage *parental_control_go_next; + GtkListBoxRow *parental_controls_row; +#endif + GtkListBoxRow *password_row; + CcPermissionInfobar *permission_infobar; + GtkButton *remove_user_button; + GtkStack *stack; + GtkToggleButton *user_icon_button; + CcUserImage *user_icon_image; + CcUserImage *user_icon_image2; + GtkStack *user_icon_stack; + GtkOverlay *users_overlay; + + ActUser *selected_user; + GPermission *permission; + CcLanguageChooser *language_chooser; + + CcAvatarChooser *avatar_chooser; + + CcFingerprintManager *fingerprint_manager; + + gint other_accounts; +}; + +CC_PANEL_REGISTER (CcUserPanel, cc_user_panel) + +static void show_restart_notification (CcUserPanel *self, const gchar *locale); +static gint user_compare (gconstpointer i, gconstpointer u); + +typedef struct { + CcUserPanel *self; + GCancellable *cancellable; + gchar *login; +} AsyncDeleteData; + +static void +async_delete_data_free (AsyncDeleteData *data) +{ + g_object_unref (data->self); + g_object_unref (data->cancellable); + g_free (data->login); + g_slice_free (AsyncDeleteData, data); +} + +static void +show_error_dialog (CcUserPanel *self, + const gchar *message, + GError *error) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", message); + + if (error != NULL) { + g_dbus_error_strip_remote_error (error); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", error->message); + } + + g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); + gtk_window_present (GTK_WINDOW (dialog)); +} + +static ActUser * +get_selected_user (CcUserPanel *self) +{ + return self->selected_user; +} + +static const gchar * +get_real_or_user_name (ActUser *user) +{ + const gchar *name; + + name = act_user_get_real_name (user); + if (name == NULL) + name = act_user_get_user_name (user); + + return name; +} + +static void show_user (ActUser *user, CcUserPanel *self); + +static void +set_selected_user (CcUserPanel *self, CcCarouselItem *item) +{ + uid_t uid; + + uid = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "uid")); + g_set_object (&self->selected_user, + act_user_manager_get_user_by_id (self->um, uid)); + + if (self->selected_user != NULL) { + show_user (self->selected_user, self); + } +} + +static GtkWidget * +create_carousel_entry (CcUserPanel *self, ActUser *user) +{ + GtkWidget *box, *widget; + gchar *label; + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + + widget = cc_user_image_new (); + cc_user_image_set_user (CC_USER_IMAGE (widget), user); + gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); + + label = g_markup_printf_escaped ("<b>%s</b>", + get_real_or_user_name (user)); + widget = gtk_label_new (label); + gtk_label_set_use_markup (GTK_LABEL (widget), TRUE); + gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END); + gtk_widget_set_margin_top (widget, 5); + gtk_box_pack_start (GTK_BOX (box), widget, FALSE, TRUE, 0); + g_free (label); + + if (act_user_get_uid (user) == getuid ()) + label = g_strdup_printf ("<small>%s</small>", _("Your account")); + else + label = g_strdup (" "); + + widget = gtk_label_new (label); + gtk_label_set_use_markup (GTK_LABEL (widget), TRUE); + g_free (label); + + gtk_box_pack_start (GTK_BOX (box), widget, FALSE, TRUE, 0); + gtk_style_context_add_class (gtk_widget_get_style_context (widget), + "dim-label"); + + return box; +} + +static void +user_added (CcUserPanel *self, ActUser *user) +{ + GtkWidget *item, *widget; + gboolean show_carousel; + + if (act_user_is_system_account (user)) { + return; + } + + g_debug ("user added: %d %s\n", act_user_get_uid (user), get_real_or_user_name (user)); + + widget = create_carousel_entry (self, user); + item = cc_carousel_item_new (); + gtk_container_add (GTK_CONTAINER (item), widget); + + g_object_set_data (G_OBJECT (item), "uid", GINT_TO_POINTER (act_user_get_uid (user))); + gtk_container_add (GTK_CONTAINER (self->carousel), item); + + if (act_user_get_uid (user) != getuid ()) { + self->other_accounts++; + } + + /* Show heading for other accounts if new one have been added. */ + show_carousel = (self->other_accounts > 0); + gtk_revealer_set_reveal_child (GTK_REVEALER (self->carousel), show_carousel); + + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->users_overlay)); +} + +static gint +sort_users (gconstpointer a, gconstpointer b) +{ + ActUser *ua, *ub; + gchar *name1, *name2; + gint result; + + ua = ACT_USER (a); + ub = ACT_USER (b); + + /* Make sure the current user is shown first */ + if (act_user_get_uid (ua) == getuid ()) { + result = -G_MAXINT32; + } + else if (act_user_get_uid (ub) == getuid ()) { + result = G_MAXINT32; + } + else { + name1 = g_utf8_collate_key (get_real_or_user_name (ua), -1); + name2 = g_utf8_collate_key (get_real_or_user_name (ub), -1); + + result = strcmp (name1, name2); + + g_free (name1); + g_free (name2); + } + + return result; +} + +static void +reload_users (CcUserPanel *self, ActUser *selected_user) +{ + ActUser *user; + GSList *list, *l; + CcCarouselItem *item = NULL; + GtkSettings *settings; + gboolean animations; + guint users_count; + + settings = gtk_widget_get_settings (GTK_WIDGET (self->carousel)); + + g_object_get (settings, "gtk-enable-animations", &animations, NULL); + g_object_set (settings, "gtk-enable-animations", FALSE, NULL); + + cc_carousel_purge_items (self->carousel); + self->other_accounts = 0; + + list = act_user_manager_list_users (self->um); + users_count = g_slist_length (list); + g_debug ("Got %d users\n", users_count); + + list = g_slist_sort (list, (GCompareFunc) sort_users); + for (l = list; l; l = l->next) { + user = l->data; + g_debug ("adding user %s\n", get_real_or_user_name (user)); + user_added (self, user); + } + g_slist_free (list); + + if (cc_carousel_get_item_count (self->carousel) == 0) + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->no_users_box)); + if (self->other_accounts == 0) + gtk_revealer_set_reveal_child (GTK_REVEALER (self->carousel), FALSE); + + if (selected_user) + item = cc_carousel_find_item (self->carousel, selected_user, user_compare); + cc_carousel_select_item (self->carousel, item); + + g_object_set (settings, "gtk-enable-animations", animations, NULL); +#ifdef HAVE_MALCONTENT + /* Parental Controls row not to be shown for single user setups. */ + gtk_widget_set_visible (GTK_WIDGET (self->parental_controls_row), users_count > 1); +#endif +} + +static gint +user_compare (gconstpointer i, + gconstpointer u) +{ + CcCarouselItem *item; + ActUser *user; + gint uid_a, uid_b; + gint result; + + item = (CcCarouselItem *) i; + user = ACT_USER (u); + + uid_a = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "uid")); + uid_b = act_user_get_uid (user); + + result = uid_a - uid_b; + + return result; +} + +static void +user_changed (CcUserPanel *self, ActUser *user) +{ + reload_users (self, self->selected_user); +} + +static void +add_user (CcUserPanel *self) +{ + CcAddUserDialog *dialog; + g_autoptr(GdkPixbuf) pixbuf = NULL; + GtkWindow *toplevel; + ActUser *user; + + dialog = cc_add_user_dialog_new (self->permission); + toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); + gtk_window_set_transient_for (GTK_WINDOW (dialog), toplevel); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + user = cc_add_user_dialog_get_user (dialog); + if (user != NULL) { + set_default_avatar (user); + reload_users (self, user); + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +delete_user_done (ActUserManager *manager, + GAsyncResult *res, + CcUserPanel *self) +{ + 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)) + show_error_dialog (self, _("Failed to delete user"), error); + + g_error_free (error); + } +} + +static void +delete_user_response (CcUserPanel *self, + gint response_id, + GtkWidget *dialog) +{ + 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 (self); + + /* remove autologin */ + if (act_user_get_automatic_login (user)) { + act_user_set_automatic_login (user, FALSE); + } + + act_user_manager_delete_user_async (self->um, + user, + remove_files, + NULL, + (GAsyncReadyCallback)delete_user_done, + self); +} + +static void +enterprise_user_revoked (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + AsyncDeleteData *data = user_data; + CcUserPanel *self = data->self; + CcRealmCommon *common = CC_REALM_COMMON (source); + GError *error = NULL; + + if (g_cancellable_is_cancelled (data->cancellable)) { + async_delete_data_free (data); + return; + } + + cc_realm_common_call_change_login_policy_finish (common, result, &error); + if (error != NULL) { + show_error_dialog (self, _("Failed to revoke remotely managed user"), error); + g_error_free (error); + } + + async_delete_data_free (data); +} + +static CcRealmCommon * +find_matching_realm (CcRealmManager *realm_manager, const gchar *login) +{ + CcRealmCommon *common = NULL; + GList *realms, *l; + + realms = cc_realm_manager_get_realms (realm_manager); + for (l = realms; l != NULL; l = g_list_next (l)) { + const gchar * const *permitted_logins; + gint i; + + common = cc_realm_object_get_common (l->data); + if (common == NULL) + continue; + + permitted_logins = cc_realm_common_get_permitted_logins (common); + for (i = 0; permitted_logins[i] != NULL; i++) { + if (g_strcmp0 (permitted_logins[i], login) == 0) + break; + } + + if (permitted_logins[i] != NULL) + break; + + g_clear_object (&common); + } + g_list_free_full (realms, g_object_unref); + + return common; +} + +static void +realm_manager_found (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + AsyncDeleteData *data = user_data; + CcUserPanel *self = data->self; + CcRealmCommon *common; + CcRealmManager *realm_manager; + const gchar *add[1]; + const gchar *remove[2]; + GVariant *options; + GError *error = NULL; + + if (g_cancellable_is_cancelled (data->cancellable)) { + async_delete_data_free (data); + return; + } + + realm_manager = cc_realm_manager_new_finish (result, &error); + if (error != NULL) { + show_error_dialog (self, _("Failed to revoke remotely managed user"), error); + g_error_free (error); + async_delete_data_free (data); + return; + } + + /* Find matching realm */ + common = find_matching_realm (realm_manager, data->login); + if (common == NULL) { + /* The realm was probably left */ + async_delete_data_free (data); + return; + } + + /* Remove the user from permitted logins */ + g_debug ("Denying future login for: %s", data->login); + + add[0] = NULL; + remove[0] = data->login; + remove[1] = NULL; + + options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0); + cc_realm_common_call_change_login_policy (common, "", + add, remove, options, + data->cancellable, + enterprise_user_revoked, + data); + + g_object_unref (common); +} + +static void +enterprise_user_uncached (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + AsyncDeleteData *data = user_data; + CcUserPanel *self = data->self; + ActUserManager *manager = ACT_USER_MANAGER (source); + GError *error = NULL; + + if (g_cancellable_is_cancelled (data->cancellable)) { + async_delete_data_free (data); + return; + } + + act_user_manager_uncache_user_finish (manager, res, &error); + if (error == NULL) { + /* Find realm manager */ + cc_realm_manager_new (cc_panel_get_cancellable (CC_PANEL (self)), realm_manager_found, data); + } + else { + show_error_dialog (self, _("Failed to revoke remotely managed user"), error); + g_error_free (error); + async_delete_data_free (data); + } +} + +static void +delete_enterprise_user_response (CcUserPanel *self, + gint response_id, + GtkWidget *dialog) +{ + AsyncDeleteData *data; + ActUser *user; + + gtk_widget_destroy (dialog); + + if (response_id != GTK_RESPONSE_ACCEPT) { + return; + } + + user = get_selected_user (self); + + data = g_slice_new (AsyncDeleteData); + data->self = g_object_ref (self); + data->cancellable = g_object_ref (cc_panel_get_cancellable (CC_PANEL (self))); + data->login = g_strdup (act_user_get_user_name (user)); + + /* Uncache the user account from the accountsservice */ + g_debug ("Uncaching remote user: %s", data->login); + + act_user_manager_uncache_user_async (self->um, data->login, + data->cancellable, + enterprise_user_uncached, + data); +} + +static void +delete_user (CcUserPanel *self) +{ + ActUser *user; + GtkWidget *dialog; + + user = get_selected_user (self); + if (user == NULL) { + return; + } + else if (act_user_get_uid (user) == getuid ()) { + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), + 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 (GTK_WIDGET (self))), + 0, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, + _("%s is still logged in"), + get_real_or_user_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 if (act_user_is_local_account (user)) { + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), + 0, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("Do you want to keep %s’s files?"), + get_real_or_user_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_object (dialog, "response", + G_CALLBACK (delete_user_response), self, G_CONNECT_SWAPPED); + } + else { + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), + 0, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("Are you sure you want to revoke remotely managed %s’s account?"), + get_real_or_user_name (user)); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("_Delete"), GTK_RESPONSE_ACCEPT, + _("_Cancel"), GTK_RESPONSE_CANCEL, + NULL); + + gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users"); + + g_signal_connect_object (dialog, "response", + G_CALLBACK (delete_enterprise_user_response), self, G_CONNECT_SWAPPED); + } + + 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)); +} + +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 (CcUserPanel *self) +{ + gboolean active; + ActUser *user; + + active = gtk_switch_get_active (self->autologin_switch); + user = get_selected_user (self); + + 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 (self->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); + } + } +} + +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 = cc_util_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 (CcUserPanel *self); +static void full_name_edit_button_toggled (CcUserPanel *self); + +#ifdef HAVE_MALCONTENT +static gboolean +is_parental_controls_enabled_for_user (ActUser *user) +{ + g_autoptr(MctManager) manager = NULL; + g_autoptr(MctAppFilter) app_filter = NULL; + g_autoptr(GDBusConnection) system_bus = NULL; + g_autoptr(GError) error = NULL; + + /* FIXME: should become asynchronous */ + system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (system_bus == NULL) { + g_warning ("Error getting system bus while trying to show user details: %s", error->message); + return FALSE; + } + + manager = mct_manager_new (system_bus); + app_filter = mct_manager_get_app_filter (manager, + act_user_get_uid (user), + MCT_GET_APP_FILTER_FLAGS_NONE, + NULL, + &error); + if (error) { + if (!g_error_matches (error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_DISABLED)) + g_warning ("Error retrieving app filter for user %s: %s", + act_user_get_user_name (user), + error->message); + + return FALSE; + } + + return mct_app_filter_is_enabled (app_filter); +} +#endif + +static void +update_fingerprint_row_state (CcUserPanel *self, GParamSpec *spec, CcFingerprintManager *fingerprint_manager) +{ + CcFingerprintState state = cc_fingerprint_manager_get_state (fingerprint_manager); + + if (state != CC_FINGERPRINT_STATE_UPDATING) { + gtk_widget_set_visible (GTK_WIDGET (self->fingerprint_row), + state != CC_FINGERPRINT_STATE_NONE); + } + + gtk_widget_set_sensitive (GTK_WIDGET (self->fingerprint_row), + state != CC_FINGERPRINT_STATE_UPDATING); + + if (state == CC_FINGERPRINT_STATE_ENABLED) + gtk_label_set_text (self->fingerprint_state_label, _("Enabled")); + else if (state == CC_FINGERPRINT_STATE_DISABLED) + gtk_label_set_text (self->fingerprint_state_label, _("Disabled")); +} + +static void +show_user (ActUser *user, CcUserPanel *self) +{ + gchar *lang, *text, *name; + gboolean show, enable; + ActUser *current; + + self->selected_user = user; + + cc_user_image_set_user (self->user_icon_image, user); + cc_user_image_set_user (self->user_icon_image2, user); + + cc_avatar_chooser_set_user (self->avatar_chooser, user); + + gtk_label_set_label (self->full_name_label, act_user_get_real_name (user)); + gtk_entry_set_text (self->full_name_entry, act_user_get_real_name (user)); + gtk_widget_set_tooltip_text (GTK_WIDGET (self->full_name_label), act_user_get_user_name (user)); + + g_signal_handlers_block_by_func (self->full_name_edit_button, full_name_edit_button_toggled, self); + gtk_stack_set_visible_child (self->full_name_stack, GTK_WIDGET (self->full_name_label)); + gtk_toggle_button_set_active (self->full_name_edit_button, FALSE); + g_signal_handlers_unblock_by_func (self->full_name_edit_button, full_name_edit_button_toggled, self); + + enable = (act_user_get_account_type (user) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR); + gtk_switch_set_active (self->account_type_switch, enable); + + /* Do not show the "Account Type" option when there's a single user account. */ + show = (self->other_accounts != 0); + gtk_widget_set_visible (GTK_WIDGET (self->account_settings_box), show); + + gtk_label_set_label (self->password_button_label, get_password_mode_text (user)); + enable = act_user_is_local_account (user); + gtk_widget_set_sensitive (GTK_WIDGET (self->password_button_label), enable); + + g_signal_handlers_block_by_func (self->autologin_switch, autologin_changed, self); + gtk_switch_set_active (self->autologin_switch, act_user_get_automatic_login (user)); + g_signal_handlers_unblock_by_func (self->autologin_switch, autologin_changed, self); + gtk_widget_set_sensitive (GTK_WIDGET (self->autologin_switch), get_autologin_possible (user)); + + name = NULL; + lang = g_strdup (act_user_get_language (user)); + + if (lang && *lang != '\0') { + name = gnome_get_language_from_locale (lang, NULL); + } else { + name = g_strdup ("—"); + } + + gtk_label_set_label (self->language_button_label, name); + g_free (lang); + g_free (name); + + /* Fingerprint: show when self, local, enabled, and possible */ + show = (act_user_get_uid (user) == getuid() && + act_user_is_local_account (user) && + (self->login_screen_settings && + g_settings_get_boolean (self->login_screen_settings, + "enable-fingerprint-authentication"))); + + if (show) { + if (!self->fingerprint_manager) { + self->fingerprint_manager = cc_fingerprint_manager_new (user); + g_signal_connect_object (self->fingerprint_manager, + "notify::state", + G_CALLBACK (update_fingerprint_row_state), + self, G_CONNECT_SWAPPED); + } + + update_fingerprint_row_state (self, NULL, self->fingerprint_manager); + } else { + gtk_widget_set_visible (GTK_WIDGET (self->fingerprint_row), FALSE); + } + + /* Autologin: show when local account */ + show = act_user_is_local_account (user); + gtk_widget_set_visible (GTK_WIDGET (self->autologin_row), show); + +#ifdef HAVE_MALCONTENT + /* Parental Controls: Unavailable if user is admin */ + if (act_user_get_account_type (user) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR) { + gtk_widget_hide (GTK_WIDGET (self->parental_control_go_next)); + /* TRANSLATORS: Status of Parental Controls setup */ + gtk_label_set_text (self->parental_controls_button_label, _("Unavailable")); + } else { + if (is_parental_controls_enabled_for_user (user)) + /* TRANSLATORS: Status of Parental Controls setup */ + gtk_label_set_text (self->parental_controls_button_label, _("Enabled")); + else + /* TRANSLATORS: Status of Parental Controls setup */ + gtk_label_set_text (self->parental_controls_button_label, _("Disabled")); + + gtk_widget_show (GTK_WIDGET (self->parental_control_go_next)); + } +#endif + + /* Language: do not show for current user */ + show = act_user_get_uid (user) != getuid(); + gtk_widget_set_visible (GTK_WIDGET (self->language_row), show); + + /* Last login: show when administrator or current user */ + current = act_user_manager_get_user_by_id (self->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_label (self->last_login_button_label, text); + g_free (text); + } + gtk_widget_set_visible (GTK_WIDGET (self->last_login_row), show); + + enable = act_user_get_login_history (user) != NULL; + gtk_widget_set_sensitive (GTK_WIDGET (self->last_login_row), enable); + + if (self->permission != NULL) + on_permission_changed (self); +} + +static void +full_name_entry_activate (CcUserPanel *self) +{ + const gchar *text; + ActUser *user; + + user = get_selected_user (self); + text = gtk_entry_get_text (self->full_name_entry); + if (g_strcmp0 (text, act_user_get_real_name (user)) != 0 && + is_valid_name (text)) { + act_user_set_real_name (user, text); + } + + gtk_toggle_button_set_active (self->full_name_edit_button, FALSE); +} + +static void +full_name_edit_button_toggled (CcUserPanel *self) +{ + if (gtk_stack_get_visible_child (self->full_name_stack) == GTK_WIDGET (self->full_name_label)) { + gtk_stack_set_visible_child (self->full_name_stack, GTK_WIDGET (self->full_name_entry)); + + gtk_widget_grab_focus (GTK_WIDGET (self->full_name_entry)); + } else { + gtk_stack_set_visible_child (self->full_name_stack, GTK_WIDGET (self->full_name_label)); + + full_name_entry_activate (self); + } +} + +static gboolean +full_name_entry_key_press_cb (CcUserPanel *self, + GdkEvent *event) +{ + GdkEventKey *key = (GdkEventKey *)event; + + if (key->keyval == GDK_KEY_Escape) { + gtk_entry_set_text (self->full_name_entry, act_user_get_real_name (self->selected_user)); + + full_name_entry_activate (self); + + return TRUE; + } + + return FALSE; +} + +static void +account_type_changed (CcUserPanel *self) +{ + ActUser *user; + gboolean self_selected; + gboolean is_admin; + ActUserAccountType account_type; + + user = get_selected_user (self); + self_selected = act_user_get_uid (user) == geteuid (); + is_admin = gtk_switch_get_active (self->account_type_switch); + + account_type = is_admin ? ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR : ACT_USER_ACCOUNT_TYPE_STANDARD; + if (account_type != act_user_get_account_type (user)) { + act_user_set_account_type (user, account_type); + + if (self_selected) + show_restart_notification (self, NULL); + } +} + +static void +dismiss_notification (CcUserPanel *self) +{ + gtk_revealer_set_reveal_child (self->notification_revealer, FALSE); +} + +static void +restart_now (CcUserPanel *self) +{ + GDBusConnection *bus; + + gtk_revealer_set_reveal_child (self->notification_revealer, FALSE); + + bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + g_dbus_connection_call (bus, + "org.gnome.SessionManager", + "/org/gnome/SessionManager", + "org.gnome.SessionManager", + "Logout", + g_variant_new ("(u)", 0), + NULL, 0, G_MAXINT, + NULL, NULL, NULL); + g_object_unref (bus); +} + +static void +show_restart_notification (CcUserPanel *self, const gchar *locale) +{ + locale_t current_locale; + locale_t new_locale; + + if (locale) { + new_locale = newlocale (LC_MESSAGES_MASK, locale, (locale_t) 0); + if (new_locale == (locale_t) 0) + g_warning ("Failed to create locale %s: %s", locale, g_strerror (errno)); + else + current_locale = uselocale (new_locale); + } + + gtk_revealer_set_reveal_child (self->notification_revealer, TRUE); + + if (locale && new_locale != (locale_t) 0) { + uselocale (current_locale); + freelocale (new_locale); + } +} + +static void +language_response (CcUserPanel *self, + gint response_id, + GtkDialog *dialog) +{ + ActUser *user; + const gchar *lang, *account_language; + + if (response_id != GTK_RESPONSE_OK) { + gtk_widget_hide (GTK_WIDGET (dialog)); + return; + } + + user = get_selected_user (self); + account_language = act_user_get_language (user); + + lang = cc_language_chooser_get_language (CC_LANGUAGE_CHOOSER (dialog)); + if (lang) { + g_autofree gchar *name = NULL; + if (g_strcmp0 (lang, account_language) != 0) { + act_user_set_language (user, lang); + } + + name = gnome_get_language_from_locale (lang, NULL); + gtk_label_set_label (self->language_button_label, name); + } + + gtk_widget_hide (GTK_WIDGET (dialog)); +} + +static void +change_language (CcUserPanel *self) +{ + const gchar *current_language; + ActUser *user; + + user = get_selected_user (self); + current_language = act_user_get_language (user); + + if (self->language_chooser) { + cc_language_chooser_clear_filter (self->language_chooser); + cc_language_chooser_set_language (self->language_chooser, NULL); + } + else { + self->language_chooser = cc_language_chooser_new (); + gtk_window_set_transient_for (GTK_WINDOW (self->language_chooser), + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)))); + + g_signal_connect_object (self->language_chooser, "response", + G_CALLBACK (language_response), self, G_CONNECT_SWAPPED); + g_signal_connect (self->language_chooser, "delete-event", + G_CALLBACK (gtk_widget_hide_on_delete), NULL); + + gdk_window_set_cursor (gtk_widget_get_window (gtk_widget_get_toplevel (GTK_WIDGET (self))), NULL); + } + + if (current_language && *current_language != '\0') + cc_language_chooser_set_language (self->language_chooser, current_language); + gtk_window_present (GTK_WINDOW (self->language_chooser)); +} + +static void +change_password (CcUserPanel *self) +{ + ActUser *user; + CcPasswordDialog *dialog; + GtkWindow *parent; + + user = get_selected_user (self); + dialog = cc_password_dialog_new (user); + + parent = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); + gtk_window_set_transient_for (GTK_WINDOW (dialog), parent); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +change_fingerprint (CcUserPanel *self) +{ + ActUser *user; + GtkWindow *top_level; + CcFingerprintDialog *dialog; + + user = get_selected_user (self); + top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); + + g_assert (g_strcmp0 (g_get_user_name (), act_user_get_user_name (user)) == 0); + + dialog = cc_fingerprint_dialog_new (self->fingerprint_manager); + gtk_window_set_transient_for (GTK_WINDOW (dialog), top_level); + gtk_widget_show (GTK_WIDGET (dialog)); +} + +static void +show_history (CcUserPanel *self) +{ + CcLoginHistoryDialog *dialog; + ActUser *user; + GtkWindow *parent; + gint parent_width; + + user = get_selected_user (self); + dialog = cc_login_history_dialog_new (user); + + parent = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); + gtk_window_get_size (parent, &parent_width, NULL); + gtk_window_set_default_size (GTK_WINDOW (dialog), parent_width * 0.6, -1); + gtk_window_set_transient_for (GTK_WINDOW (dialog), parent); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +#ifdef HAVE_MALCONTENT +static void +spawn_malcontent_control (CcUserPanel *self) +{ + ActUser *user; + + user = get_selected_user (self); + + /* no-op if the user is administrator */ + if (act_user_get_account_type (user) != ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR) { + const gchar *argv[] = { "malcontent-control", NULL }; + g_spawn_async (NULL, (char **)argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); + } +} +#endif + +static void +activate_row (GtkListBox *box, GtkListBoxRow *row, CcUserPanel *self) +{ + if (!gtk_widget_get_sensitive (GTK_WIDGET (row))) + return; + + if (row == self->language_row) { + change_language (self); + } else if (row == self->password_row) { + change_password (self); + } else if (row == self->fingerprint_row) { + change_fingerprint (self); + } else if (row == self->last_login_row) { + show_history (self); + } + +#ifdef HAVE_MALCONTENT + if (row == self->parental_controls_row) { + spawn_malcontent_control (self); + } +#endif +} + +static void +users_loaded (CcUserPanel *self) +{ + GtkWidget *dialog; + + if (act_user_manager_no_service (self->um)) { + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), + 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 (dialog, "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + gtk_widget_show (dialog); + + gtk_widget_set_sensitive (GTK_WIDGET (self->accounts_box), FALSE); + } + + g_signal_connect_object (self->um, "user-changed", G_CALLBACK (user_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->um, "user-is-logged-in-changed", G_CALLBACK (user_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->um, "user-added", G_CALLBACK (user_added), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->um, "user-removed", G_CALLBACK (user_changed), self, G_CONNECT_SWAPPED); + + reload_users (self, NULL); +} + +static void +add_unlock_tooltip (GtkWidget *widget) +{ + 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, + /* 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 */ + _("To make changes,\nclick the * icon first"), + "*", + icon); + g_object_unref (icon); + g_signal_connect (widget, "button-release-event", + G_CALLBACK (show_tooltip_now), NULL); +} + +static void +remove_unlock_tooltip (GtkWidget *widget) +{ + setup_tooltip_with_embedded_icon (widget, NULL, NULL, NULL); + g_signal_handlers_disconnect_by_func (widget, + G_CALLBACK (show_tooltip_now), NULL); +} + +static guint +get_num_active_admin (ActUserManager *um) +{ + GSList *list; + GSList *l; + guint num_admin = 0; + + list = act_user_manager_list_users (um); + for (l = list; l != NULL; l = l->next) { + ActUser *u = l->data; + if (act_user_get_account_type (u) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR && !act_user_get_locked (u)) { + num_admin++; + } + } + g_slist_free (list); + + return num_admin; +} + +static gboolean +would_demote_only_admin (ActUser *user) +{ + ActUserManager *um = act_user_manager_get_default (); + + /* Prevent the user from demoting the only admin account. + * Returns TRUE when user is an administrator and there is only + * one enabled administrator. */ + + if (act_user_get_account_type (user) == ACT_USER_ACCOUNT_TYPE_STANDARD || + act_user_get_locked (user)) + return FALSE; + + if (get_num_active_admin (um) > 1) + return FALSE; + + return TRUE; +} + +static void +on_permission_changed (CcUserPanel *self) +{ + gboolean is_authorized; + gboolean self_selected; + ActUser *user; + + is_authorized = g_permission_get_allowed (G_PERMISSION (self->permission)); + + gtk_widget_set_visible (GTK_WIDGET (self->add_user_button), is_authorized); + + user = get_selected_user (self); + if (!user) { + return; + } + + self_selected = act_user_get_uid (user) == geteuid (); + gtk_widget_set_sensitive (GTK_WIDGET (self->remove_user_button), is_authorized && !self_selected + && !would_demote_only_admin (user)); + if (is_authorized) { + setup_tooltip_with_embedded_icon (GTK_WIDGET (self->remove_user_button), _("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 (GTK_WIDGET (self->remove_user_button), + _("To delete the selected user account,\nclick the * icon first"), + "*", + icon); + g_object_unref (icon); + } + + if (!act_user_is_local_account (user)) { + gtk_widget_set_sensitive (GTK_WIDGET (self->account_type_row), FALSE); + remove_unlock_tooltip (GTK_WIDGET (self->account_type_row)); + gtk_widget_set_sensitive (GTK_WIDGET (self->autologin_row), FALSE); + remove_unlock_tooltip (GTK_WIDGET (self->autologin_row)); + + } else if (is_authorized && act_user_is_local_account (user)) { + if (would_demote_only_admin (user)) { + gtk_widget_set_sensitive (GTK_WIDGET (self->account_type_row), FALSE); + } else { + gtk_widget_set_sensitive (GTK_WIDGET (self->account_type_row), TRUE); + } + remove_unlock_tooltip (GTK_WIDGET (self->account_type_row)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->autologin_row), get_autologin_possible (user)); + remove_unlock_tooltip (GTK_WIDGET (self->autologin_row)); + } + else { + gtk_widget_set_sensitive (GTK_WIDGET (self->account_type_row), FALSE); + if (would_demote_only_admin (user)) { + remove_unlock_tooltip (GTK_WIDGET (self->account_type_row)); + } else { + add_unlock_tooltip (GTK_WIDGET (self->account_type_row)); + } + gtk_widget_set_sensitive (GTK_WIDGET (self->autologin_row), FALSE); + add_unlock_tooltip (GTK_WIDGET (self->autologin_row)); + } + + /* The full name entry: insensitive if remote or not authorized and not self */ + if (!act_user_is_local_account (user)) { + gtk_widget_set_sensitive (GTK_WIDGET (self->full_name_edit_button), FALSE); + remove_unlock_tooltip (GTK_WIDGET (self->full_name_stack)); + + } else if (is_authorized || self_selected) { + gtk_widget_set_sensitive (GTK_WIDGET (self->full_name_edit_button), TRUE); + remove_unlock_tooltip (GTK_WIDGET (self->full_name_stack)); + + } else { + gtk_widget_set_sensitive (GTK_WIDGET (self->full_name_edit_button), FALSE); + add_unlock_tooltip (GTK_WIDGET (self->full_name_stack)); + } + + if (is_authorized || self_selected) { + CcFingerprintState fingerprint_state = CC_FINGERPRINT_STATE_NONE; + + if (self->fingerprint_manager) + fingerprint_state = cc_fingerprint_manager_get_state (self->fingerprint_manager); + + gtk_stack_set_visible_child (self->user_icon_stack, GTK_WIDGET (self->user_icon_button)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->language_row), TRUE); + remove_unlock_tooltip (GTK_WIDGET (self->language_row)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->password_row), TRUE); + remove_unlock_tooltip (GTK_WIDGET (self->password_row)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->fingerprint_row), + fingerprint_state != CC_FINGERPRINT_STATE_UPDATING); + remove_unlock_tooltip (GTK_WIDGET (self->fingerprint_row)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->last_login_row), TRUE); + remove_unlock_tooltip (GTK_WIDGET (self->last_login_row)); +#ifdef HAVE_MALCONTENT + gtk_widget_set_sensitive (GTK_WIDGET (self->parental_controls_row), TRUE); + remove_unlock_tooltip (GTK_WIDGET (self->parental_controls_row)); +#endif + } + else { + gtk_stack_set_visible_child (self->user_icon_stack, GTK_WIDGET (self->user_icon_image)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->language_row), FALSE); + add_unlock_tooltip (GTK_WIDGET (self->language_row)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->password_row), FALSE); + add_unlock_tooltip (GTK_WIDGET (self->password_row)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->fingerprint_row), FALSE); + add_unlock_tooltip (GTK_WIDGET (self->fingerprint_row)); + + gtk_widget_set_sensitive (GTK_WIDGET (self->last_login_row), FALSE); + add_unlock_tooltip (GTK_WIDGET (self->last_login_row)); +#ifdef HAVE_MALCONTENT + gtk_widget_set_sensitive (GTK_WIDGET (self->parental_controls_row), FALSE); + add_unlock_tooltip (GTK_WIDGET (self->parental_controls_row)); +#endif + } +} + +static void +setup_main_window (CcUserPanel *self) +{ + GIcon *icon; + GError *error = NULL; + gchar *names[3]; + gboolean loaded; + + self->other_accounts = 0; + + add_unlock_tooltip (GTK_WIDGET (self->user_icon_image)); + + self->permission = (GPermission *)polkit_permission_new_sync (USER_ACCOUNTS_PERMISSION, NULL, NULL, &error); + if (self->permission != NULL) { + g_signal_connect_object (self->permission, "notify", + G_CALLBACK (on_permission_changed), self, G_CONNECT_SWAPPED); + on_permission_changed (self); + } else { + g_warning ("Cannot create '%s' permission: %s", USER_ACCOUNTS_PERMISSION, error->message); + g_error_free (error); + } + + 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 (GTK_WIDGET (self->remove_user_button), + _("To delete the selected user account,\nclick the * icon first"), + "*", + icon); + g_object_unref (icon); + + g_object_get (self->um, "is-loaded", &loaded, NULL); + if (loaded) + users_loaded (self); + else + g_signal_connect_object (self->um, "notify::is-loaded", G_CALLBACK (users_loaded), self, G_CONNECT_SWAPPED); + + gtk_list_box_set_header_func (self->account_settings_listbox, + cc_list_box_update_header_func, + NULL, NULL); + gtk_list_box_set_header_func (self->authentication_and_login_listbox, + cc_list_box_update_header_func, + NULL, NULL); +} + +static GSettings * +settings_or_null (const gchar *schema) +{ + GSettingsSchemaSource *source = NULL; + gchar **non_relocatable = NULL; + gchar **relocatable = NULL; + GSettings *settings = NULL; + + source = g_settings_schema_source_get_default (); + if (!source) + return NULL; + + g_settings_schema_source_list_schemas (source, TRUE, &non_relocatable, &relocatable); + + if (g_strv_contains ((const gchar * const *)non_relocatable, schema) || + g_strv_contains ((const gchar * const *)relocatable, schema)) + settings = g_settings_new (schema); + + g_strfreev (non_relocatable); + g_strfreev (relocatable); + return settings; +} + +static void +cc_user_panel_constructed (GObject *object) +{ + CcUserPanel *self = CC_USER_PANEL (object); + CcShell *shell; + + G_OBJECT_CLASS (cc_user_panel_parent_class)->constructed (object); + + shell = cc_panel_get_shell (CC_PANEL (self)); + cc_shell_embed_widget_in_header (shell, GTK_WIDGET (self->add_user_button), GTK_POS_RIGHT); + + cc_permission_infobar_set_permission (self->permission_infobar, self->permission); +} + +static void +cc_user_panel_init (CcUserPanel *self) +{ + volatile GType type G_GNUC_UNUSED; + GtkCssProvider *provider; + + g_resources_register (cc_user_accounts_get_resource ()); + + /* register types that the builder might need */ + type = cc_user_image_get_type (); + type = cc_carousel_get_type (); + type = cc_permission_infobar_get_type (); + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->um = act_user_manager_get_default (); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/user-accounts/user-accounts-dialog.css"); + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); + + self->login_screen_settings = settings_or_null ("org.gnome.login-screen"); + + self->avatar_chooser = cc_avatar_chooser_new (GTK_WIDGET (self->user_icon_button)); + setup_main_window (self); +} + +static void +cc_user_panel_dispose (GObject *object) +{ + CcUserPanel *self = CC_USER_PANEL (object); + + g_clear_object (&self->selected_user); + + g_clear_object (&self->login_screen_settings); + + g_clear_pointer ((GtkWidget **)&self->language_chooser, gtk_widget_destroy); + g_clear_object (&self->permission); + G_OBJECT_CLASS (cc_user_panel_parent_class)->dispose (object); +} + +static const char * +cc_user_panel_get_help_uri (CcPanel *panel) +{ + return "help:gnome-help/user-accounts"; +} + +static void +cc_user_panel_class_init (CcUserPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + + object_class->dispose = cc_user_panel_dispose; + object_class->constructed = cc_user_panel_constructed; + + panel_class->get_help_uri = cc_user_panel_get_help_uri; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/user-accounts/cc-user-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, accounts_box); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, account_settings_box); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, account_settings_listbox); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, authentication_and_login_listbox); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, account_type_row); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, account_type_switch); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, add_user_button); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, autologin_row); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, autologin_switch); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, carousel); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, fingerprint_state_label); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, fingerprint_row); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, full_name_stack); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, full_name_label); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, full_name_edit_button); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, full_name_entry); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, language_button_label); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, language_row); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, last_login_button_label); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, last_login_row); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, no_users_box); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, notification_revealer); +#ifdef HAVE_MALCONTENT + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, parental_controls_button_label); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, parental_control_go_next); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, parental_controls_row); +#endif + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, password_button_label); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, password_row); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, permission_infobar); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, remove_user_button); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, stack); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, user_icon_button); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, user_icon_image); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, user_icon_image2); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, user_icon_stack); + gtk_widget_class_bind_template_child (widget_class, CcUserPanel, users_overlay); + + gtk_widget_class_bind_template_callback (widget_class, account_type_changed); + gtk_widget_class_bind_template_callback (widget_class, activate_row); + gtk_widget_class_bind_template_callback (widget_class, add_user); + gtk_widget_class_bind_template_callback (widget_class, autologin_changed); + gtk_widget_class_bind_template_callback (widget_class, change_fingerprint); + gtk_widget_class_bind_template_callback (widget_class, change_language); + gtk_widget_class_bind_template_callback (widget_class, full_name_edit_button_toggled); + gtk_widget_class_bind_template_callback (widget_class, full_name_entry_activate); + gtk_widget_class_bind_template_callback (widget_class, full_name_entry_key_press_cb); + gtk_widget_class_bind_template_callback (widget_class, change_password); + gtk_widget_class_bind_template_callback (widget_class, delete_user); + gtk_widget_class_bind_template_callback (widget_class, dismiss_notification); + gtk_widget_class_bind_template_callback (widget_class, restart_now); + gtk_widget_class_bind_template_callback (widget_class, set_selected_user); +} diff --git a/panels/user-accounts/cc-user-panel.h b/panels/user-accounts/cc-user-panel.h new file mode 100644 index 0000000..6f0aa5a --- /dev/null +++ b/panels/user-accounts/cc-user-panel.h @@ -0,0 +1,29 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE (CcUserPanel, cc_user_panel, CC, USER_PANEL, CcPanel) + +G_END_DECLS diff --git a/panels/user-accounts/cc-user-panel.ui b/panels/user-accounts/cc-user-panel.ui new file mode 100644 index 0000000..b965280 --- /dev/null +++ b/panels/user-accounts/cc-user-panel.ui @@ -0,0 +1,668 @@ +<interface> + <object class="GtkButton" id="add_user_button"> + <property name="visible">False</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Add User…</property> + <property name="use_underline">True</property> + <property name="has_tooltip">True</property> + <property name="tooltip_text" translatable="yes">Create a user account</property> + <signal name="clicked" handler="add_user" object="CcUserPanel" swapped="yes"/> + <style> + <class name="suggested-action"/> + </style> + </object> + <object class="GtkListStore" id="shortname-model"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkListStore" id="language-model"> + <columns> + <!-- column-name gchararray --> + <column type="gchararray"/> + <!-- column-name gchararray1 --> + <column type="gchararray"/> + </columns> + </object> + <template class="CcUserPanel" parent="CcPanel"> + <property name="visible">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="CcPermissionInfobar" id="permission_infobar"> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="visible-child">no_users_box</property> + <child> + <object class="GtkOverlay" id="users_overlay"> + <property name="visible">True</property> + <child type="overlay"> + <object class="GtkRevealer" id="notification_revealer"> + <property name="visible">True</property> + <property name="halign">GTK_ALIGN_CENTER</property> + <property name="valign">GTK_ALIGN_START</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="spacing">6</property> + <style> + <class name="app-notification"/> + </style> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="wrap">True</property> + <property name="max_width_chars">30</property> + <property name="label" translatable="yes">Your session needs to be restarted for changes to take effect</property> + </object> + </child> + <child> + <object class="GtkButton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="valign">GTK_ALIGN_CENTER</property> + <property name="label" translatable="yes">Restart Now</property> + <signal name="clicked" handler="restart_now" object="CcUserPanel" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkButton" id="dismiss_button"> + <property name="visible">True</property> + <property name="valign">GTK_ALIGN_CENTER</property> + <signal name="clicked" handler="dismiss_notification" object="CcUserPanel" swapped="yes"/> + <style> + <class name="flat"/> + </style> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="icon_name">window-close-symbolic</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="accounts_box"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="border_width">0</property> + + <child> + <object class="CcCarousel" id="carousel"> + <property name="visible">True</property> + <signal name="item-activated" handler="set_selected_user" object="CcUserPanel" swapped="yes"/> + </object> + <packing> + <property name="fill">False</property> + <property name="expand">False</property> + </packing> + </child> + + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="expand">True</property> + <property name="hscrollbar-policy">GTK_POLICY_NEVER</property> + + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="halign">GTK_ALIGN_CENTER</property> + <property name="orientation">GTK_ORIENTATION_VERTICAL</property> + <property name="spacing">20</property> + <property name="margin-top">30</property> + <property name="margin-bottom">30</property> + + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="valign">GTK_ALIGN_CENTER</property> + <property name="spacing">10</property> + <property name="margin-top">10</property> + <property name="margin-bottom">10</property> + <child> + <object class="GtkStack" id="user_icon_stack"> + <property name="visible">True</property> + <property name="halign">GTK_ALIGN_END</property> + <style> + <class name="user-icon-button"/> + </style> + <child> + <object class="CcUserImage" id="user_icon_image"> + <property name="visible">True</property> + <property name="icon_name">avatar-default</property> + <property name="pixel_size">96</property> + <property name="halign">GTK_ALIGN_END</property> + <style> + <class name="user-icon-button"/> + </style> + <child internal-child="accessible"> + <object class="AtkObject"> + <property name="accessible-name" translatable="yes">User Icon</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkToggleButton" id="user_icon_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <style> + <class name="user-icon-button"/> + </style> + <child internal-child="accessible"> + <object class="AtkObject"> + <property name="accessible-name" translatable="yes">User Icon</property> + </object> + </child> + <child> + <object class="CcUserImage" id="user_icon_image2"> + <property name="visible">True</property> + <property name="icon_name">avatar-default</property> + <property name="pixel_size">96</property> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkStack" id="full_name_stack"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="full_name_label"> + <property name="visible">True</property> + <property name="halign">GTK_ALIGN_START</property> + <property name="ellipsize">PANGO_ELLIPSIZE_END</property> + <property name="width-chars">18</property> + <property name="max-width-chars">30</property> + <property name="xalign">0</property> + <attributes> + <attribute name="scale" value="1.2"/> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkEntry" id="full_name_entry"> + <property name="visible">True</property> + <property name="max-length">255</property> + <property name="width-chars">18</property> + <property name="max-width-chars">30</property> + <property name="valign">GTK_ALIGN_CENTER</property> + <signal name="activate" handler="full_name_entry_activate" object="CcUserPanel" swapped="yes"/> + <signal name="key-press-event" handler="full_name_entry_key_press_cb" object="CcUserPanel" swapped="yes"/> + </object> + </child> + </object> + </child> + <child> + <object class="GtkToggleButton" id="full_name_edit_button"> + <property name="visible">True</property> + <signal name="toggled" handler="full_name_edit_button_toggled" object="CcUserPanel" swapped="yes"/> + <property name="valign">GTK_ALIGN_CENTER</property> + <style> + <class name="circular"/> + </style> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="margin">5</property> + <property name="icon-name">document-edit-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + + <child> + <object class="GtkBox" id="account_settings_box"> + <property name="visible">True</property> + <property name="orientation">GTK_ORIENTATION_VERTICAL</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Account Settings</property> + <property name="halign">GTK_ALIGN_START</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkListBox" id="account_settings_listbox"> + <property name="visible">True</property> + <property name="selection-mode">GTK_SELECTION_NONE</property> + <signal name="row-activated" handler="activate_row"/> + <style> + <class name="frame"/> + </style> + <child> + <object class="GtkListBoxRow" id="account_type_row"> + <property name="visible">True</property> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border-width">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="halign">GTK_ALIGN_START</property> + <property name="label" translatable="yes">_Administrator</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">account_type_switch</property> + </object> + </child> + <child> + <object class="GtkSwitch" id="account_type_switch"> + <property name="visible">True</property> + <signal name="notify::active" handler="account_type_changed" object="CcUserPanel" swapped="yes"/> + </object> + <packing> + <property name="left_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="wrap">True</property> + <property name="xalign">0</property> + <property name="max-width-chars">48</property> + <property name="label" translatable="yes">Administrators can add and remove other users, and can change settings for all users.</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.9"/> + </attributes> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkListBoxRow" id="parental_controls_row"> + <property name="visible">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="border-width">10</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Parental Controls</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">parental_controls_button_label</property> + </object> + </child> + <child> + <object class="GtkImage" id="parental_control_go_next"> + <property name="visible">True</property> + <property name="icon-name">go-next-symbolic</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="parental_controls_button_label"> + <property name="visible">True</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkListBoxRow" id="language_row"> + <property name="visible">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="border-width">10</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Language</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">language_button_label</property> + </object> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="icon-name">go-next-symbolic</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="language_button_label"> + <property name="visible">True</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkBox" id="authentication_and_login_box"> + <property name="visible">True</property> + <property name="orientation">GTK_ORIENTATION_VERTICAL</property> + <property name="spacing">10</property> + <property name="margin-top">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Authentication & Login</property> + <property name="halign">GTK_ALIGN_START</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkListBox" id="authentication_and_login_listbox"> + <property name="visible">True</property> + <property name="selection-mode">GTK_SELECTION_NONE</property> + <signal name="row-activated" handler="activate_row"/> + <style> + <class name="frame"/> + </style> + <child> + <object class="GtkListBoxRow" id="password_row"> + <property name="visible">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="border-width">10</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">password_button_label</property> + </object> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="icon-name">go-next-symbolic</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="password_button_label"> + <property name="visible">True</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBoxRow" id="fingerprint_row"> + <property name="visible">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="border-width">10</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Fingerprint Login</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">fingerprint_state_label</property> + </object> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="icon-name">go-next-symbolic</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="fingerprint_state_label"> + <property name="visible">True</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBoxRow" id="autologin_row"> + <property name="visible">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="border-width">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">A_utomatic Login</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">autologin_switch</property> + </object> + </child> + <child> + <object class="GtkSwitch" id="autologin_switch"> + <property name="visible">True</property> + <signal name="notify::active" handler="autologin_changed" object="CcUserPanel" swapped="yes"/> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBoxRow" id="last_login_row"> + <property name="visible">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="border-width">10</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Account Activity</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">last_login_button_label</property> + </object> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="icon-name">go-next-symbolic</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="last_login_button_label"> + <property name="visible">True</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkButton" id="remove_user_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="halign">GTK_ALIGN_END</property> + <property name="label" translatable="yes">Remove User…</property> + <property name="margin-top">10</property> + <property name="margin-bottom">10</property> + <signal name="clicked" handler="delete_user" object="CcUserPanel" swapped="yes"/> + <style> + <class name="destructive-action"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="no_users_box"> + <property name="visible">True</property> + <property name="orientation">GTK_ORIENTATION_VERTICAL</property> + <property name="valign">GTK_ALIGN_CENTER</property> + <property name="spacing">12</property> + <style> + <class name="dim-label"/> + </style> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="icon_name">avatar-default-symbolic</property> + <property name="pixel_size">192</property> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes" comments="Translators: This is the empty state page label which states that there are no users to show in the panel.">No Users Found</property> + <attributes> + <attribute name="weight" value="bold"/> + <attribute name="scale" value="1.6"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Unlock to add a user account.</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + <object class="GtkSizeGroup"> + <property name="mode">both</property> + <widgets> + <widget name="user_icon_button"/> + <widget name="user_icon_image"/> + </widgets> + </object> + <object class="GtkSizeGroup"> + <property name="mode">both</property> + <widgets> + <widget name="language_row"/> + <widget name="password_row"/> + <widget name="fingerprint_row"/> + <widget name="autologin_row"/> + <widget name="last_login_row"/> + </widgets> + </object> +</interface> diff --git a/panels/user-accounts/data/carousel.css b/panels/user-accounts/data/carousel.css new file mode 100644 index 0000000..738562c --- /dev/null +++ b/panels/user-accounts/data/carousel.css @@ -0,0 +1,30 @@ +.carousel-arrow-container { + border-bottom: 1px solid @borders; +} + +.carousel-arrow, +.carousel-inner-arrow { + border-width: 20px; /* ARROW_SIZE */ + border-style: solid; + border-color: transparent; +} + +.carousel-arrow { + border-bottom-color: @borders; + margin-bottom: -1px; + animation-duration: 200ms; + animation-timing-function: ease-in-out; + animation-fill-mode: forwards; +} + +.carousel-inner-arrow { + border-bottom-color: @theme_bg_color; + margin-bottom: -2px; +} + +.carousel-item { + background: transparent; + box-shadow: none; + border: none; + color: @theme_fg_color; +} diff --git a/panels/user-accounts/data/cc-fingerprint-dialog.css b/panels/user-accounts/data/cc-fingerprint-dialog.css new file mode 100644 index 0000000..800d658 --- /dev/null +++ b/panels/user-accounts/data/cc-fingerprint-dialog.css @@ -0,0 +1,83 @@ +.fingerprint-icon { + padding: 3px; +} + +.fingerprint-icon > button, +.fingerprint-icon > image { + padding: 15px; + min-width: 32px; + min-height: 32px; + border-radius: 64px; + border: 1px solid @borders; + background-color: @theme_base_color; + color: @insensitive_fg_color; +} + +.fingerprint-print-add image:not(:disabled):not(:backdrop), +.fingerprint-print-add button:not(:disabled):not(:backdrop) { + color: @theme_fg_color; +} + +.fingerprint-icon.enroll-status image { + outline-color: @theme_selected_bg_color; + outline-offset: 0px; + outline-width: 4px; +} + +.fingerprint-icon.enroll-status image:backdrop { + outline-color: @theme_unfocused_selected_bg_color; +} + +.fingerprint-icon.enroll-status { + font-weight: bold; +} + +.fingerprint-icon.enroll-status.completed image { + outline-color: @success_color; +} + +.fingerprint-icon.enroll-status.warning image { + outline-color: @warning_color; +} + +.fingerprint-icon.enroll-status.error image { + outline-color: @error_color; + /* Given we don't have an error image, we can just recolorize the warning one */ + -gtk-icon-palette: warning @error_color; +} + +.fingerprint-icon.enroll-status.success image:not(:backdrop) { + color: @theme_selected_bg_color; +} + +.fingerprint-icon.enroll-status.warning image:not(:backdrop), +.fingerprint-icon.enroll-status.warning label:not(:backdrop) { + color: @warning_color; +} + +.fingerprint-icon.enroll-status.error image:not(:backdrop), +.fingerprint-icon.enroll-status.error label:not(:backdrop) { + color: @error_color; +} + +@keyframes wiggle { + /* Unfortunately we can't use translation or xalign, so here's the workaround */ + 0% { padding-left: 0; padding-right: 0; } + 10% { padding-left: 0; padding-right: 2px; } + 20% { padding-left: 4px; padding-right: 0; } + 30% { padding-left: 0; padding-right: 8px; } + 40% { padding-left: 8px; padding-right: 0; } + 50% { padding-left: 0; padding-right: 8px; } + 60% { padding-left: 8px; padding-right: 0; } + 70% { padding-left: 0; padding-right: 8px; } + 80% { padding-left: 4px; padding-right: 0; } + 90% { padding-left: 0; padding-right: 2px; } + 100% { padding-left: 0; padding-right: 0; } +} + +.fingerprint-icon.enroll-status.retry label { + animation-name: wiggle; + animation-duration: 850ms; + animation-timing-function: ease-in; + animation-iteration-count: 1; +} diff --git a/panels/user-accounts/data/faces/bicycle.jpg b/panels/user-accounts/data/faces/bicycle.jpg Binary files differnew file mode 100644 index 0000000..c598251 --- /dev/null +++ b/panels/user-accounts/data/faces/bicycle.jpg diff --git a/panels/user-accounts/data/faces/book.jpg b/panels/user-accounts/data/faces/book.jpg Binary files differnew file mode 100644 index 0000000..abda4b7 --- /dev/null +++ b/panels/user-accounts/data/faces/book.jpg diff --git a/panels/user-accounts/data/faces/calculator.jpg b/panels/user-accounts/data/faces/calculator.jpg Binary files differnew file mode 100644 index 0000000..43ece1e --- /dev/null +++ b/panels/user-accounts/data/faces/calculator.jpg diff --git a/panels/user-accounts/data/faces/cat.jpg b/panels/user-accounts/data/faces/cat.jpg Binary files differnew file mode 100644 index 0000000..99275b2 --- /dev/null +++ b/panels/user-accounts/data/faces/cat.jpg diff --git a/panels/user-accounts/data/faces/coffee2.jpg b/panels/user-accounts/data/faces/coffee2.jpg Binary files differnew file mode 100644 index 0000000..4be830d --- /dev/null +++ b/panels/user-accounts/data/faces/coffee2.jpg diff --git a/panels/user-accounts/data/faces/flower2.jpg b/panels/user-accounts/data/faces/flower2.jpg Binary files differnew file mode 100644 index 0000000..b77e717 --- /dev/null +++ b/panels/user-accounts/data/faces/flower2.jpg diff --git a/panels/user-accounts/data/faces/gamepad.jpg b/panels/user-accounts/data/faces/gamepad.jpg Binary files differnew file mode 100644 index 0000000..9843758 --- /dev/null +++ b/panels/user-accounts/data/faces/gamepad.jpg diff --git a/panels/user-accounts/data/faces/guitar2.jpg b/panels/user-accounts/data/faces/guitar2.jpg Binary files differnew file mode 100644 index 0000000..f1ad374 --- /dev/null +++ b/panels/user-accounts/data/faces/guitar2.jpg diff --git a/panels/user-accounts/data/faces/headphones.jpg b/panels/user-accounts/data/faces/headphones.jpg Binary files differnew file mode 100644 index 0000000..2ad9175 --- /dev/null +++ b/panels/user-accounts/data/faces/headphones.jpg diff --git a/panels/user-accounts/data/faces/hummingbird.jpg b/panels/user-accounts/data/faces/hummingbird.jpg Binary files differnew file mode 100644 index 0000000..d06a3cf --- /dev/null +++ b/panels/user-accounts/data/faces/hummingbird.jpg diff --git a/panels/user-accounts/data/faces/legacy/astronaut.jpg b/panels/user-accounts/data/faces/legacy/astronaut.jpg Binary files differnew file mode 100644 index 0000000..4b79f0e --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/astronaut.jpg diff --git a/panels/user-accounts/data/faces/legacy/baseball.png b/panels/user-accounts/data/faces/legacy/baseball.png Binary files differnew file mode 100644 index 0000000..0d6dfdb --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/baseball.png diff --git a/panels/user-accounts/data/faces/legacy/butterfly.png b/panels/user-accounts/data/faces/legacy/butterfly.png Binary files differnew file mode 100644 index 0000000..66b813c --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/butterfly.png diff --git a/panels/user-accounts/data/faces/legacy/cat-eye.jpg b/panels/user-accounts/data/faces/legacy/cat-eye.jpg Binary files differnew file mode 100644 index 0000000..c818bd5 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/cat-eye.jpg diff --git a/panels/user-accounts/data/faces/legacy/chess.jpg b/panels/user-accounts/data/faces/legacy/chess.jpg Binary files differnew file mode 100644 index 0000000..7abb8a4 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/chess.jpg diff --git a/panels/user-accounts/data/faces/legacy/coffee.jpg b/panels/user-accounts/data/faces/legacy/coffee.jpg Binary files differnew file mode 100644 index 0000000..46e8fc5 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/coffee.jpg diff --git a/panels/user-accounts/data/faces/legacy/dice.jpg b/panels/user-accounts/data/faces/legacy/dice.jpg Binary files differnew file mode 100644 index 0000000..641b124 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/dice.jpg diff --git a/panels/user-accounts/data/faces/legacy/energy-arc.jpg b/panels/user-accounts/data/faces/legacy/energy-arc.jpg Binary files differnew file mode 100644 index 0000000..9f4c892 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/energy-arc.jpg diff --git a/panels/user-accounts/data/faces/legacy/fish.jpg b/panels/user-accounts/data/faces/legacy/fish.jpg Binary files differnew file mode 100644 index 0000000..fc363d6 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/fish.jpg diff --git a/panels/user-accounts/data/faces/legacy/flake.jpg b/panels/user-accounts/data/faces/legacy/flake.jpg Binary files differnew file mode 100644 index 0000000..5546d7e --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/flake.jpg diff --git a/panels/user-accounts/data/faces/legacy/flower.jpg b/panels/user-accounts/data/faces/legacy/flower.jpg Binary files differnew file mode 100644 index 0000000..3e41ba4 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/flower.jpg diff --git a/panels/user-accounts/data/faces/legacy/grapes.jpg b/panels/user-accounts/data/faces/legacy/grapes.jpg Binary files differnew file mode 100644 index 0000000..3d31daf --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/grapes.jpg diff --git a/panels/user-accounts/data/faces/legacy/guitar.jpg b/panels/user-accounts/data/faces/legacy/guitar.jpg Binary files differnew file mode 100644 index 0000000..9e8834f --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/guitar.jpg diff --git a/panels/user-accounts/data/faces/legacy/launch.jpg b/panels/user-accounts/data/faces/legacy/launch.jpg Binary files differnew file mode 100644 index 0000000..7c7bf43 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/launch.jpg diff --git a/panels/user-accounts/data/faces/legacy/leaf.jpg b/panels/user-accounts/data/faces/legacy/leaf.jpg Binary files differnew file mode 100644 index 0000000..5354103 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/leaf.jpg diff --git a/panels/user-accounts/data/faces/legacy/lightning.jpg b/panels/user-accounts/data/faces/legacy/lightning.jpg Binary files differnew file mode 100644 index 0000000..736ccd5 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/lightning.jpg diff --git a/panels/user-accounts/data/faces/legacy/penguin.jpg b/panels/user-accounts/data/faces/legacy/penguin.jpg Binary files differnew file mode 100644 index 0000000..2a8dfd6 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/penguin.jpg diff --git a/panels/user-accounts/data/faces/legacy/puppy.jpg b/panels/user-accounts/data/faces/legacy/puppy.jpg Binary files differnew file mode 100644 index 0000000..ab55a8b --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/puppy.jpg diff --git a/panels/user-accounts/data/faces/legacy/sky.jpg b/panels/user-accounts/data/faces/legacy/sky.jpg Binary files differnew file mode 100644 index 0000000..841f90e --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/sky.jpg diff --git a/panels/user-accounts/data/faces/legacy/soccerball.png b/panels/user-accounts/data/faces/legacy/soccerball.png Binary files differnew file mode 100644 index 0000000..56588a9 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/soccerball.png diff --git a/panels/user-accounts/data/faces/legacy/sunflower.jpg b/panels/user-accounts/data/faces/legacy/sunflower.jpg Binary files differnew file mode 100644 index 0000000..6102b8b --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/sunflower.jpg diff --git a/panels/user-accounts/data/faces/legacy/sunset.jpg b/panels/user-accounts/data/faces/legacy/sunset.jpg Binary files differnew file mode 100644 index 0000000..48b6223 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/sunset.jpg diff --git a/panels/user-accounts/data/faces/legacy/tennis-ball.png b/panels/user-accounts/data/faces/legacy/tennis-ball.png Binary files differnew file mode 100644 index 0000000..a1beb50 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/tennis-ball.png diff --git a/panels/user-accounts/data/faces/legacy/yellow-rose.jpg b/panels/user-accounts/data/faces/legacy/yellow-rose.jpg Binary files differnew file mode 100644 index 0000000..7f1de96 --- /dev/null +++ b/panels/user-accounts/data/faces/legacy/yellow-rose.jpg diff --git a/panels/user-accounts/data/faces/mountain.jpg b/panels/user-accounts/data/faces/mountain.jpg Binary files differnew file mode 100644 index 0000000..8425e0d --- /dev/null +++ b/panels/user-accounts/data/faces/mountain.jpg diff --git a/panels/user-accounts/data/faces/plane.jpg b/panels/user-accounts/data/faces/plane.jpg Binary files differnew file mode 100644 index 0000000..a0c4506 --- /dev/null +++ b/panels/user-accounts/data/faces/plane.jpg diff --git a/panels/user-accounts/data/faces/surfer.jpg b/panels/user-accounts/data/faces/surfer.jpg Binary files differnew file mode 100644 index 0000000..47f19b0 --- /dev/null +++ b/panels/user-accounts/data/faces/surfer.jpg diff --git a/panels/user-accounts/data/faces/tomatoes.jpg b/panels/user-accounts/data/faces/tomatoes.jpg Binary files differnew file mode 100644 index 0000000..ebdb6ba --- /dev/null +++ b/panels/user-accounts/data/faces/tomatoes.jpg diff --git a/panels/user-accounts/data/faces/tree.jpg b/panels/user-accounts/data/faces/tree.jpg Binary files differnew file mode 100644 index 0000000..8a619d3 --- /dev/null +++ b/panels/user-accounts/data/faces/tree.jpg diff --git a/panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in b/panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in new file mode 100644 index 0000000..c2e361d --- /dev/null +++ b/panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in @@ -0,0 +1,19 @@ +[Desktop Entry] +Name=Users +Comment=Add or remove users and change your password +Exec=gnome-control-center user-accounts +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=system-users +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-DetailsSettings; +OnlyShowIn=GNOME;Unity; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-control-center +X-GNOME-Bugzilla-Component=user-accounts +X-GNOME-Bugzilla-Version=@VERSION@ +X-GNOME-Settings-Panel=user-accounts +# Translators: Search terms to find the Users panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=Login;Name;Fingerprint;Avatar;Logo;Face;Password; diff --git a/panels/user-accounts/data/icons/fingerprint-detection-complete-symbolic.svg b/panels/user-accounts/data/icons/fingerprint-detection-complete-symbolic.svg new file mode 100644 index 0000000..a225384 --- /dev/null +++ b/panels/user-accounts/data/icons/fingerprint-detection-complete-symbolic.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 8.467 8.467"> + <path style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1" d="M 16.132812,0.99023438 C 13.902514,0.96755721 11.66847,1.5168081 9.65625,2.6425781 8.4831917,3.288913 9.4616217,5.0473362 10.628906,4.390625 14.036766,2.4839667 18.198267,2.527856 21.566406,4.5039062 24.934547,6.4799188 27,10.08815 27,13.994141 v 2 c -5.97e-4,1.33435 1.999403,1.33435 2,0 v -2 c 3.54e-4,-0.03325 -9.56e-4,-0.06649 -0.0039,-0.09961 C 28.961052,9.320245 26.52689,5.092052 22.578125,2.7753906 20.589365,1.6086127 18.363092,1.0130627 16.132812,0.99023438 Z M 5.7617188,6.640625 C 5.4310747,6.6594699 5.1312075,6.840853 4.9609375,7.125 3.6959444,9.158537 3.0222407,11.500805 3.0039062,13.894531 3.00095,13.927649 2.9996462,13.960893 3,13.994141 v 10.128906 c 0,1.333754 2,1.333754 2,0 V 13.994141 C 5,11.939136 5.5747962,9.9244686 6.6601562,8.1796875 7.1014645,7.4921558 6.5771855,6.5940433 5.7617188,6.640625 Z M 16,6.9921875 c -3.813165,0 -6.9283277,3.0816025 -6.9941406,6.8808595 -0.0044,0.04021 -0.00636,0.08065 -0.00586,0.121094 v 2 c 5.96e-4,1.333157 2.000596,1.333157 2,0 v -2 c 0,-2.774325 2.22666,-5.0019535 5,-5.0019535 2.77334,0 5,2.2276285 5,5.0019535 V 22.125 c 0.426667,0.161975 0.81076,0.41915 1.123047,0.751953 L 23,23.753906 v -9.759765 c 2.65e-4,-0.03849 -0.0017,-0.07697 -0.0059,-0.115235 C 22.931551,10.076928 19.815145,6.9921875 16,6.9921875 Z m -0.01563,5.9863285 C 15.43218,12.98705 14.991449,13.441767 15,13.994141 v 10.003906 c 0,0 -5.87e-4,1.09432 0.269531,2.445312 0.2701,1.350992 0.787778,3.027578 2.023438,4.263672 0.942205,0.981983 2.395438,-0.47125 1.414062,-1.414062 -0.690333,-0.690533 -1.138586,-1.835412 -1.390625,-2.898438 l -0.002,-0.002 C 16.951575,25.742041 16.843618,24.979658 17.011719,24.253906 17.009401,24.177001 17,23.998047 17,23.998047 V 13.994141 c 0.0087,-0.564623 -0.451183,-1.024549 -1.015625,-1.015625 z m -6,8.001953 C 9.4321797,20.989003 8.9914495,21.44372 9,21.996094 v 6.001953 c -5.966e-4,1.33435 1.999403,1.33435 2,0 v -6.001953 c 0.0087,-0.564623 -0.451183,-1.024549 -1.015625,-1.015625 z m 20.998047,0.0059 a 1.0001,1.0001 0 0 0 -0.6875,0.302734 l -6.296875,6.289063 -3.291016,-3.292969 a 1.0001,1.0001 0 1 0 -1.410156,1.417969 l 4.701172,4.703125 7.707031,-7.707031 a 1.0001,1.0001 0 0 0 -0.722656,-1.712891 z" transform="scale(.26458)" class="success" color="#000" font-weight="400" font-family="sans-serif" overflow="visible" fill="#33d17a"/> +</svg> diff --git a/panels/user-accounts/data/icons/fingerprint-detection-symbolic.svg b/panels/user-accounts/data/icons/fingerprint-detection-symbolic.svg new file mode 100644 index 0000000..00e31cc --- /dev/null +++ b/panels/user-accounts/data/icons/fingerprint-detection-symbolic.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 8.467 8.467"> + <path d="m 4.2684727,288.79533 c -0.5901,-0.006 -1.1811905,0.13932 -1.7135904,0.43718 a 0.26457931,0.26466281 0 1 0 0.2573486,0.46251 c 0.9016629,-0.50447 2.0027268,-0.49283 2.8938802,0.03 0.8911538,0.52282 1.4376385,1.4775 1.4376385,2.51096 v 0.52916 a 0.26458335,0.26466684 0 1 0 0.5291667,0 v -0.52916 a 0.26456806,0.26465154 0 0 0 -0.00103,-0.0264 c -0.00927,-1.21028 -0.6533118,-2.32899 -1.6980893,-2.94194 -0.5261927,-0.30871 -1.1152275,-0.46628 -1.7053224,-0.47232 z m -2.7440185,1.495 a 0.26456806,0.26465154 0 0 0 -0.2118734,0.12816 c -0.3346961,0.53804 -0.51294685,1.15776 -0.51779785,1.7911 a 0.26456806,0.26465154 0 0 0 -0.001034,0.0264 v 2.67994 a 0.26458334,0.26466683 0 0 0 0.52916665,0 v -2.67994 c 0,-0.54372 0.1520815,-1.07677 0.4392497,-1.53841 a 0.26456806,0.26465154 0 0 0 -0.2377116,-0.40721 z m 2.7088787,0.093 c -1.0089,0 -1.8331201,0.81534 -1.8505331,1.82056 a 0.26456806,0.26465154 0 0 0 -0.00155,0.032 v 0.52916 a 0.26458335,0.26466684 0 1 0 0.5291667,0 v -0.52916 c 0,-0.73404 0.5891371,-1.32344 1.3229167,-1.32344 0.7337795,0 1.3229167,0.5894 1.3229167,1.32344 v 2.64686 c 0,0 0.00212,0.18248 0.079582,0.41445 0.077301,0.23197 0.2313432,0.5351 0.5270998,0.83096 a 0.26456806,0.26465154 0 1 0 0.3741372,-0.37414 c -0.2334101,-0.23348 -0.3439509,-0.45923 -0.3989419,-0.62425 -0.054991,-0.16503 -0.05271,-0.24702 -0.05271,-0.24702 v -2.64686 a 0.26456806,0.26465154 0 0 0 -0.00156,-0.0305 c -0.016571,-1.00594 -0.8410985,-1.82211 -1.8505223,-1.82211 z m -0.00414,1.58388 a 0.26456806,0.26465154 0 0 0 -0.2604479,0.26872 v 2.64686 c 0,0 -1.558e-4,0.28954 0.071313,0.64699 0.071464,0.35745 0.208433,0.80105 0.535368,1.1281 a 0.26456806,0.26465154 0 1 0 0.3741373,-0.37414 c -0.2022317,-0.20229 -0.329846,-0.55309 -0.3906737,-0.85731 -0.060823,-0.30422 -0.060978,-0.54364 -0.060978,-0.54364 v -2.64686 a 0.26456806,0.26465154 0 0 0 -0.2687175,-0.26872 z m -1.5875,2.11718 a 0.26456806,0.26465154 0 0 0 -0.2604479,0.26872 v 1.58802 a 0.26458335,0.26466684 0 1 0 0.5291667,0 v -1.58802 a 0.26456806,0.26465154 0 0 0 -0.2687175,-0.26872 z m 4.7625001,0 a 0.26456806,0.26465154 0 0 0 -0.2604479,0.26769 v 0.52968 a 0.26458335,0.26466684 0 1 0 0.5291667,0 v -0.52968 a 0.26456806,0.26465154 0 0 0 -0.2687175,-0.26769 z" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1" color="#000" font-weight="400" font-family="sans-serif" overflow="visible" fill="#3d3846" transform="translate(0 -288.533)"/> +</svg> diff --git a/panels/user-accounts/data/icons/fingerprint-detection-warning-symbolic.svg b/panels/user-accounts/data/icons/fingerprint-detection-warning-symbolic.svg new file mode 100644 index 0000000..5b3fa81 --- /dev/null +++ b/panels/user-accounts/data/icons/fingerprint-detection-warning-symbolic.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 8.467 8.467"> + <path overflow="visible" font-weight="400" d="m 4.2684194,0.26199621 c -0.5900922,-0.006 -1.1811756,0.13932088 -1.7135688,0.4371771 A 0.26457597,0.26465948 0 1 0 2.8121959,1.1616716 C 3.7138475,0.65720791 4.8148975,0.66882014 5.7060397,1.1916435 6.5971824,1.7144569 7.14366,2.6691227 7.14366,3.7025698 v 0.52916 a 0.26458003,0.2646635 0 1 0 0.52916,0 v -0.52916 A 0.26456472,0.26464821 0 0 0 7.6717881,3.676215 C 7.6625167,2.4659504 7.0184846,1.3472551 5.9737203,0.73431284 5.4475342,0.42560675 4.8585069,0.26803613 4.2684194,0.26199621 Z M 1.5244356,1.7569766 A 0.26456472,0.26464821 0 0 0 1.3125648,1.8851325 C 0.97787297,2.4231657 0.79962444,3.042883 0.7947735,3.676215 A 0.26456472,0.26464821 0 0 0 0.79374,3.7025698 v 2.679906 a 0.26458,0.2646635 0 0 0 0.52916,0 v -2.679906 c 0,-0.5437132 0.1520796,-1.0767539 0.4392441,-1.5383881 A 0.26456472,0.26464821 0 0 0 1.5244356,1.7569766 Z M 4.23328,1.849993 c -1.0088872,0 -1.8330969,0.8153304 -1.8505097,1.8205378 a 0.26456472,0.26464821 0 0 0 -0.00155,0.032039 v 0.52916 a 0.26458003,0.2646635 0 0 0 0.52916,0 v -0.52916 c 0,-0.7340309 0.5891297,-1.3234168 1.3229,-1.3234168 0.7337703,0 1.3229,0.5893859 1.3229,1.3234168 V 4.4110447 C 5.6854821,4.2368203 5.8715938,4.0894366 6.08534,4.0260602 V 3.7025698 A 0.26456472,0.26464821 0 0 0 6.083779,3.6720809 C 6.0672189,2.6661536 5.2426911,1.849993 4.23328,1.849993 Z M 4.22914,3.4338558 A 0.26456472,0.26464821 0 0 0 3.9687,3.7025698 v 2.6468335 c 0,0 -1.553e-4,0.2895352 0.071312,0.6469806 0.00877,0.043848 0.022106,0.091907 0.033073,0.1379745 L 4.49786,6.3468194 V 3.7025698 A 0.26456472,0.26464821 0 0 0 4.2291459,3.4338558 Z M 6.3251097,4.5226644 C 6.1860729,4.5146644 6.0504148,4.5975684 5.944776,4.7789762 L 4.2911556,7.8464506 C 4.1500114,8.1013375 4.3039147,8.46656 4.5805412,8.46656 h 3.4808807 c 0.2597435,0 0.5035248,-0.3067533 0.3224568,-0.6201094 L 6.7137175,4.7955125 C 6.6082242,4.6245446 6.4641524,4.5302343 6.3251156,4.5226644 Z M 2.6416659,5.5510125 A 0.26456472,0.26464821 0 0 0 2.38122,5.8197266 v 1.5879967 a 0.26458003,0.2646635 0 1 0 0.52916,0 V 5.8197266 A 0.26456472,0.26464821 0 0 0 2.6416659,5.5510125 Z m 3.699986,0.013436 C 6.4857104,5.5594481 6.6190614,5.693238 6.6145,5.8372962 V 6.87908 C 6.61635,7.0188682 6.4897161,7.14366 6.34992,7.14366 6.2101265,7.14366 6.0833636,7.0188682 6.08534,6.87908 V 5.8372962 c -0.00212,-0.1234583 0.093688,-0.2414303 0.2149712,-0.26458 0.01352,-0.004 0.027371,-0.00675 0.041341,-0.00775 z M 6.34992,7.40824 c 0.1461236,0 0.26458,0.1184617 0.26458,0.26458 0,0.1461281 -0.1184564,0.26458 -0.26458,0.26458 -0.1461236,0 -0.26458,-0.1184519 -0.26458,-0.26458 0,-0.1461183 0.1184564,-0.26458 0.26458,-0.26458 z" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1" class="warning" color="#000" font-family="sans-serif" fill="#ff7800"/> +</svg> diff --git a/panels/user-accounts/data/icons/left-index-finger.svg b/panels/user-accounts/data/icons/left-index-finger.svg new file mode 100644 index 0000000..3c36aea --- /dev/null +++ b/panels/user-accounts/data/icons/left-index-finger.svg @@ -0,0 +1,177 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="left-index-finger.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="933" + inkscape:window-width="1054" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="22.316511" + inkscape:cy="30.299841" + inkscape:window-x="36" + inkscape:window-y="91" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(1.1605241,0,0,1.3370602,-4.3871473,-0.7984997)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/left-little-finger.svg b/panels/user-accounts/data/icons/left-little-finger.svg new file mode 100644 index 0000000..0835854 --- /dev/null +++ b/panels/user-accounts/data/icons/left-little-finger.svg @@ -0,0 +1,180 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="left-pinky-finger.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-ring-finger.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="933" + inkscape:window-width="1054" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="22.316511" + inkscape:cy="30.299841" + inkscape:window-x="122" + inkscape:window-y="443" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(1.1074589,0,0,1.2726911,-25.531655,5.5330271)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/left-middle-finger.svg b/panels/user-accounts/data/icons/left-middle-finger.svg new file mode 100644 index 0000000..1082da2 --- /dev/null +++ b/panels/user-accounts/data/icons/left-middle-finger.svg @@ -0,0 +1,180 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="left-middle-finger.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-index-finger.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="933" + inkscape:window-width="1054" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="22.316511" + inkscape:cy="30.299841" + inkscape:window-x="122" + inkscape:window-y="443" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(1.1824583,0,0,1.3363867,-12.845608,-2.0066594)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/left-ring-finger.svg b/panels/user-accounts/data/icons/left-ring-finger.svg new file mode 100644 index 0000000..50ace80 --- /dev/null +++ b/panels/user-accounts/data/icons/left-ring-finger.svg @@ -0,0 +1,180 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="left-ring-finger.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-middle-finger.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="933" + inkscape:window-width="1054" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="22.316511" + inkscape:cy="30.299841" + inkscape:window-x="122" + inkscape:window-y="443" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(1.1824583,0,0,1.3363867,-20.636466,-0.7947482)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/left-thumb.svg b/panels/user-accounts/data/icons/left-thumb.svg new file mode 100644 index 0000000..fd0f582 --- /dev/null +++ b/panels/user-accounts/data/icons/left-thumb.svg @@ -0,0 +1,180 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="left-thumb.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-pinky-finger.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="933" + inkscape:window-width="1054" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="22.316511" + inkscape:cy="30.299841" + inkscape:window-x="116" + inkscape:window-y="498" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(1.1916623,0,0,1.4021101,4.5265732,14.334323)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/print_error.svg b/panels/user-accounts/data/icons/print_error.svg new file mode 100644 index 0000000..4ad6bee --- /dev/null +++ b/panels/user-accounts/data/icons/print_error.svg @@ -0,0 +1,525 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 36.184 43.865" + enable-background="new 0 0 36.184 43.865" + xml:space="preserve" + id="svg2419" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="print_error.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/print_error.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata2435"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs2433"><linearGradient + inkscape:collect="always" + id="linearGradient84104"><stop + style="stop-color:#fffffc;stop-opacity:1;" + offset="0" + id="stop84106" /><stop + style="stop-color:#fffffc;stop-opacity:0;" + offset="1" + id="stop84108" /></linearGradient><linearGradient + id="linearGradient84076"><stop + style="stop-color:#73d216;stop-opacity:1;" + offset="0" + id="stop84078" /><stop + id="stop84090" + offset="0.31459025" + style="stop-color:#73d216;stop-opacity:1;" /><stop + style="stop-color:#4e9a06;stop-opacity:1;" + offset="1" + id="stop84080" /></linearGradient><linearGradient + id="linearGradient3531"><stop + id="stop3533" + offset="0" + style="stop-color:#9b9b9b;stop-opacity:1;" /><stop + id="stop3535" + offset="1" + style="stop-color:#414141;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient3483"><stop + id="stop3485" + offset="0" + style="stop-color:#000000;stop-opacity:1;" /><stop + id="stop3487" + offset="1" + style="stop-color:#787878;stop-opacity:1" /></linearGradient><linearGradient + id="linearGradient3263"><stop + id="stop3265" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop3267" + offset="1" + style="stop-color:#ffffff;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient3247"><stop + style="stop-color:#52a714;stop-opacity:1;" + offset="0" + id="stop3249" /><stop + style="stop-color:#398800;stop-opacity:1;" + offset="1" + id="stop3251" /></linearGradient><linearGradient + id="linearGradient3233"><stop + id="stop3235" + offset="0" + style="stop-color:#398800;stop-opacity:1;" /><stop + id="stop3237" + offset="1" + style="stop-color:#84c706;stop-opacity:1;" /></linearGradient><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 21.932501 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="36.183998 : 21.932501 : 1" + inkscape:persp3d-origin="18.091999 : 14.621667 : 1" + id="perspective2437" /> + + + + + + <filter + inkscape:collect="always" + id="filter3471"><feGaussianBlur + inkscape:collect="always" + stdDeviation="0.057808254" + id="feGaussianBlur3473" /></filter><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3233" + id="linearGradient3517" + gradientUnits="userSpaceOnUse" + x1="25.144751" + y1="43.865002" + x2="25.144751" + y2="23.838018" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3247" + id="linearGradient3519" + gradientUnits="userSpaceOnUse" + x1="30.691881" + y1="23.365002" + x2="30.691881" + y2="44.365963" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3263" + id="linearGradient3521" + gradientUnits="userSpaceOnUse" + x1="26.455547" + y1="24.322035" + x2="26.455547" + y2="30.466549" /><linearGradient + gradientUnits="userSpaceOnUse" + y2="55.692348" + x2="18.072493" + y1="29.205048" + x1="21.55229" + id="linearGradient5138" + xlink:href="#linearGradient5132" + inkscape:collect="always" /><linearGradient + id="linearGradient5132" + inkscape:collect="always"><stop + id="stop5134" + offset="0" + style="stop-color:white;stop-opacity:1;" /><stop + id="stop5136" + offset="1" + style="stop-color:white;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient1913"><stop + id="stop1915" + offset="0" + style="stop-color:#73d216;stop-opacity:1" /><stop + id="stop1917" + offset="1" + style="stop-color:#8ae234;stop-opacity:1" /></linearGradient><inkscape:perspective + id="perspective84036" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient84076" + id="radialGradient84088" + cx="26.183998" + cy="40.111427" + fx="26.183998" + fy="40.111427" + r="10.5" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9228377,7.3282173e-8,-7.9001009e-8,0.8924238,2.0204218,4.4167191)" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3263" + id="linearGradient84092" + gradientUnits="userSpaceOnUse" + x1="26.455547" + y1="24.322035" + x2="26.455547" + y2="30.466549" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient84104" + id="linearGradient84110" + x1="28.185518" + y1="22.649143" + x2="27.596079" + y2="42.648415" + gradientUnits="userSpaceOnUse" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient84076" + id="radialGradient84134" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0862379,-2.5925308e-8,1.5464794e-8,0.8186283,-2.2580516,7.302016)" + cx="26.183998" + cy="39.098457" + fx="26.183998" + fy="39.098457" + r="10.5" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3263" + id="linearGradient84136" + gradientUnits="userSpaceOnUse" + x1="26.455547" + y1="24.322035" + x2="26.455547" + y2="30.466549" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient84104" + id="linearGradient84138" + gradientUnits="userSpaceOnUse" + x1="21.515692" + y1="23.09075" + x2="34.488232" + y2="40.661182" /><filter + inkscape:collect="always" + id="filter84266" + x="-0.07103052" + width="1.142061" + y="-0.5276553" + height="2.0553105"><feGaussianBlur + inkscape:collect="always" + stdDeviation="0.45756194" + id="feGaussianBlur84268" /></filter><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3263" + id="linearGradient84277" + gradientUnits="userSpaceOnUse" + x1="26.455547" + y1="24.322035" + x2="26.455547" + y2="30.466549" /><linearGradient + id="linearGradient5171"><stop + style="stop-color:#fe3a00;stop-opacity:1" + offset="0" + id="stop5173" /><stop + style="stop-color:#c00;stop-opacity:1;" + offset="1" + id="stop5175" /></linearGradient><inkscape:perspective + id="perspective7871" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><inkscape:perspective + id="perspective7973" + inkscape:persp3d-origin="14 : 9.3333333 : 1" + inkscape:vp_z="28 : 14 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 14 : 1" + sodipodi:type="inkscape:persp3d" /> + +<radialGradient + inkscape:collect="always" + xlink:href="#linearGradient5171" + id="radialGradient8812" + cx="26.184002" + cy="39.797016" + fx="26.184002" + fy="39.797016" + r="10.65866" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9444304,0,0,0.7220468,1.4550326,11.504981)" /><linearGradient + y2="40.661182" + x2="34.488232" + y1="23.09075" + x1="21.515692" + gradientUnits="userSpaceOnUse" + id="linearGradient84279" + xlink:href="#linearGradient84104" + inkscape:collect="always" /><linearGradient + y2="30.466549" + x2="26.455547" + y1="24.322035" + x1="26.455547" + gradientUnits="userSpaceOnUse" + id="linearGradient9095" + xlink:href="#linearGradient3263" + inkscape:collect="always" /><linearGradient + y2="40.661182" + x2="34.488232" + y1="23.09075" + x1="21.515692" + gradientUnits="userSpaceOnUse" + id="linearGradient9089" + xlink:href="#linearGradient84104" + inkscape:collect="always" /><linearGradient + y2="30.466549" + x2="26.455547" + y1="24.322035" + x1="26.455547" + gradientUnits="userSpaceOnUse" + id="linearGradient9087" + xlink:href="#linearGradient3263" + inkscape:collect="always" /><radialGradient + r="10.5" + fy="39.098457" + fx="26.183998" + cy="39.098457" + cx="26.183998" + gradientTransform="matrix(1.0862379,-2.5925308e-8,1.5464794e-8,0.8186283,-2.2580516,7.302016)" + gradientUnits="userSpaceOnUse" + id="radialGradient9085" + xlink:href="#linearGradient84076" + inkscape:collect="always" /><linearGradient + gradientUnits="userSpaceOnUse" + y2="42.648415" + x2="27.596079" + y1="22.649143" + x1="28.185518" + id="linearGradient9083" + xlink:href="#linearGradient84104" + inkscape:collect="always" /><linearGradient + y2="30.466549" + x2="26.455547" + y1="24.322035" + x1="26.455547" + gradientUnits="userSpaceOnUse" + id="linearGradient9081" + xlink:href="#linearGradient3263" + inkscape:collect="always" /><radialGradient + gradientTransform="matrix(0.9228377,7.3282173e-8,-7.9001009e-8,0.8924238,2.0204218,4.4167191)" + gradientUnits="userSpaceOnUse" + r="10.5" + fy="40.111427" + fx="26.183998" + cy="40.111427" + cx="26.183998" + id="radialGradient9079" + xlink:href="#linearGradient84076" + inkscape:collect="always" /><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="48 : 24 : 1" + inkscape:persp3d-origin="24 : 16 : 1" + id="perspective9077" /><linearGradient + id="linearGradient9071"><stop + style="stop-color:#73d216;stop-opacity:1" + offset="0" + id="stop9073" /><stop + style="stop-color:#8ae234;stop-opacity:1" + offset="1" + id="stop9075" /></linearGradient><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5132" + id="linearGradient9063" + x1="21.55229" + y1="29.205048" + x2="18.072493" + y2="55.692348" + gradientUnits="userSpaceOnUse" /><linearGradient + y2="30.466549" + x2="26.455547" + y1="24.322035" + x1="26.455547" + gradientUnits="userSpaceOnUse" + id="linearGradient9061" + xlink:href="#linearGradient3263" + inkscape:collect="always" /><linearGradient + y2="44.365963" + x2="30.691881" + y1="23.365002" + x1="30.691881" + gradientUnits="userSpaceOnUse" + id="linearGradient9059" + xlink:href="#linearGradient3247" + inkscape:collect="always" /><linearGradient + y2="23.838018" + x2="25.144751" + y1="43.865002" + x1="25.144751" + gradientUnits="userSpaceOnUse" + id="linearGradient9057" + xlink:href="#linearGradient3233" + inkscape:collect="always" /> + + + + + + <inkscape:perspective + id="perspective9051" + inkscape:persp3d-origin="18.091999 : 14.621667 : 1" + inkscape:vp_z="36.183998 : 21.932501 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 21.932501 : 1" + sodipodi:type="inkscape:persp3d" /><linearGradient + id="linearGradient9045"><stop + style="stop-color:#398800;stop-opacity:1;" + offset="0" + id="stop9047" /><stop + style="stop-color:#84c706;stop-opacity:1;" + offset="1" + id="stop9049" /></linearGradient><linearGradient + id="linearGradient9039"><stop + id="stop9041" + offset="0" + style="stop-color:#52a714;stop-opacity:1;" /><stop + id="stop9043" + offset="1" + style="stop-color:#398800;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient9033"><stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop9035" /><stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop9037" /></linearGradient><linearGradient + id="linearGradient9027"><stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop9029" /><stop + style="stop-color:#787878;stop-opacity:1" + offset="1" + id="stop9031" /></linearGradient><linearGradient + id="linearGradient9021"><stop + style="stop-color:#9b9b9b;stop-opacity:1;" + offset="0" + id="stop9023" /><stop + style="stop-color:#414141;stop-opacity:1;" + offset="1" + id="stop9025" /></linearGradient><linearGradient + id="linearGradient9013"><stop + id="stop9015" + offset="0" + style="stop-color:#73d216;stop-opacity:1;" /><stop + style="stop-color:#73d216;stop-opacity:1;" + offset="0.31459025" + id="stop9017" /><stop + id="stop9019" + offset="1" + style="stop-color:#4e9a06;stop-opacity:1;" /></linearGradient><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3263" + id="linearGradient9115" + gradientUnits="userSpaceOnUse" + x1="26.455547" + y1="24.322035" + x2="26.455547" + y2="30.466549" /></defs><sodipodi:namedview + inkscape:window-height="733" + inkscape:window-width="1263" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="10.958333" + inkscape:cx="16.307224" + inkscape:cy="24" + inkscape:window-x="6" + inkscape:window-y="140" + inkscape:current-layer="svg2419" /> +<path + id="path2424" + d="M 11.485207,8.6743869 C 11.872117,8.5219533 18.066562,7.5772471 17.547442,14.819684 C 16.836811,24.751336 10.199071,21.863582 8.4570051,28.091683 C 9.1530536,27.855507 9.3319265,27.184615 9.77036,26.633222 C 11.09052,24.971517 11.912946,24.144427 13.199081,23.591154 C 17.777838,21.620819 20.261644,13.237019 16.405184,9.3659791 C 14.958646,7.9131637 12.270692,8.0514823 11.485207,8.6743869 z M -0.41567362,30.622819 C -0.62079412,30.073309 -0.81036052,29.508743 -0.98534482,28.932886 C 0.28134733,30.510848 2.408639,29.511665 3.7800623,29.851546 C 7.7792565,30.842672 10.201988,29.700696 12.4972,27.002072 C 14.538683,24.600785 15.730521,26.026314 17.692291,22.415916 C 18.187107,21.506024 19.527682,20.515211 20.049718,16.478567 C 20.335526,14.26641 21.73087,14.007651 21.560747,12.407106 C 21.300215,9.95501 21.209476,9.6012157 20.182901,7.9460967 C 18.706228,5.5636294 16.443158,4.9249489 14.396731,4.9187631 C 12.674334,4.9140584 11.84411,5.6088349 11.260829,5.8365431 C 15.390232,4.8356399 18.977454,6.2618089 19.977781,8.6743869 C 20.486207,9.9004346 20.744795,10.273048 20.835204,11.578136 C 20.978108,13.644446 20.307334,14.416019 20.263588,13.790293 C 20.079855,11.11331 19.152438,7.8049552 16.050354,6.738867 C 14.094417,6.0670338 11.302445,6.5779657 9.6303729,7.8454158 C 7.5908326,9.7988133 6.4952361,12.616937 6.4952361,14.474358 C 6.4952361,18.037708 6.4689884,19.168722 5.854599,22.494014 C 5.596011,23.89696 4.6627611,25.357303 5.2022963,27.278709 C 5.9975029,26.985134 6.7110502,25.878585 6.9949136,25.111717 C 8.9868188,19.727642 10.292396,20.99227 12.343602,18.829042 C 12.831614,18.314346 13.669595,16.922691 13.771669,16.409877 C 14.018591,15.167833 14.629092,12.200099 12.557472,12.269729 C 10.832904,13.36875 11.557145,15.649595 10.237957,17.872102 C 12.271664,16.963152 12.138481,13.127869 12.885082,13.037538 C 13.533495,12.95944 13.493638,14.504468 13.351707,15.328734 C 13.046456,17.100529 12.422345,18.101693 11.019554,19.120734 C 9.9006261,19.933709 8.8604412,20.065441 7.8484484,21.773252 C 7.6579099,22.073413 6.3027534,25.346012 6.134574,25.654641 C 5.2858998,27.216607 5.3879741,25.564311 6.3212239,22.946607 C 6.3212239,22.946607 7.2311425,20.110606 7.1115699,17.014904 C 7.0571303,15.581848 7.1368454,9.9201951 10.843597,7.9150456 C 12.438871,7.052201 14.9149,6.5102179 16.620025,7.776727 C 21.95802,11.740919 19.47227,21.463682 15.405828,24.214058 C 13.770697,25.320606 12.413596,25.389296 11.019554,27.516768 C 9.932706,29.17565 6.8500655,30.289726 4.7074794,29.321496 C 5.5649027,28.845379 6.8957558,28.803036 7.2787772,27.73883 C 10.085332,19.931827 15.530262,23.306048 16.183536,14.966471 C 16.414904,12.009088 14.877959,9.6953099 12.69746,9.6953099 C 10.466409,9.6953099 8.9664042,12.675276 8.6281009,15.440706 C 8.4657544,16.774023 8.3782621,18.528881 8.2713273,19.450064 C 10.056167,18.413145 8.4851971,11.162239 12.484562,10.747283 C 15.963834,10.386902 15.66636,14.964589 15.26973,16.96127 C 14.967396,18.483716 14.270374,19.795389 11.270365,21.516375 C 9.690645,22.422502 8.643655,23.573275 7.8426157,25.457043 C 7.3730743,26.56171 6.4126046,28.452065 4.547077,28.632726 C 4.3332073,28.653426 3.9268547,26.573943 3.8004772,25.47304 C 3.6138271,23.848031 4.453752,22.765006 4.7327549,21.015793 C 5.0438382,19.072746 5.2615964,17.525836 5.2781228,14.197721 C 4.4936096,9.0893427 5.2917327,7.5791289 7.4226532,5.1562011 C 9.9142358,2.3220811 12.045219,2.0705493 13.508882,2.0570557 C 15.652565,2.0393053 16.899028,3.5509526 16.263252,3.4276891 C 14.62326,3.1105913 11.630055,2.6909308 9.0577845,5.2945196 C 7.6229131,6.7454536 6.0402768,8.6122845 6.2084562,10.886543 C 6.8802017,7.6195895 10.334508,3.7516736 13.747676,3.7516736 C 17.577206,3.7516736 19.976808,5.1712562 21.120039,7.0155042 C 22.280769,8.8889209 22.761976,10.297513 22.761976,12.822062 C 22.761976,14.686071 21.730541,15.451057 21.262943,16.962211 C 21.083098,17.542773 20.463848,19.045459 20.191651,20.211286 C 19.097998,24.896241 18.309596,27.152622 14.271346,30.220096 C 12.511782,31.556235 11.111907,31.646565 10.179629,31.285243 C 10.187406,31.13281 12.366934,30.934272 13.724035,28.937591 C 15.002392,27.055705 16.650162,27.007717 17.396761,24.840726 C 16.622941,25.778846 15.419438,26.673683 14.41425,27.115925 C 14.130387,27.241071 13.618072,27.886557 13.421701,28.116148 C 11.368551,30.531548 9.713004,31.103642 7.3458545,31.373692 C 7.7220709,31.550589 8.110925,31.687026 8.5231103,31.739719 C 11.53187,32.124565 13.387675,32.163143 15.774267,30.055431 C 19.142717,27.08111 19.696834,25.201106 20.047775,22.967308 C 20.307334,21.313131 21.669648,17.436727 22.261326,15.868175 C 23.094574,13.65922 22.570465,18.105457 22.332292,18.688842 C 20.93825,22.115755 21.500144,26.735784 17.955739,30.380997 C 15.523456,32.882964 12.32416,33.811675 7.1358733,32.637379 C 5.7525248,32.324044 6.253216,31.857336 4.671552,31.373692 C 4.1971499,31.159157 1.6540024,31.013311 1.3623619,31.011429 C 0.72269676,31.007665 0.11997297,30.875933 -0.41567362,30.622819 z M 4.2680743,5.1477326 C 2.8681995,6.7162844 3.1442858,7.9310416 2.2158966,9.2445983 C 0.39119867,11.826545 1.4517984,15.393659 1.0658607,17.99913 C 0.56618308,21.378997 0.52283784,20.110089 0.0065640818,17.990301 C -0.75463862,14.864849 -0.20180392,13.731954 -0.076398418,12.407106 C 0.6186783,9.1298026 1.2262629,7.7560263 2.8691715,5.6887748 C 4.2253003,3.9828454 6.149156,3.0371977 4.2680743,5.1477326 z M 24.036445,36.450079 C 23.488161,37.083333 22.896131,37.673305 22.262298,38.211523 L 21.475841,38.506979 C 18.241547,39.711387 16.58114,39.821477 11.857534,39.082837 C 9.932706,38.781735 7.3050248,38.411944 5.4978253,39.277612 C 6.5839482,39.510065 10.395401,38.361053 11.576587,39.986142 C 11.459931,40.156453 9.2113823,39.88358 7.0960157,40.347465 C 6.5837004,40.058595 6.0888836,39.731146 5.6115651,39.367002 C 5.1527173,39.009443 4.8173306,38.720573 4.4926375,38.413826 C 6.9570004,37.030641 10.904842,37.507699 13.34879,37.820091 C 17.581468,38.362075 19.260345,38.181414 21.964825,37.188718 C 22.696843,36.919609 23.388031,36.670259 24.036445,36.450079 z M 26.094456,7.8981086 C 27.151167,9.9813566 28.257457,11.575313 28.257457,14.734999 C 28.257457,18.212845 25.759069,19.908303 25.360494,22.223022 C 24.987194,24.390014 24.927894,26.193802 24.599312,26.193802 C 24.132687,26.193802 23.98395,24.125609 24.054915,22.674675 C 24.126854,21.205864 24.314124,20.404861 24.655344,19.310545 C 25.039337,18.081673 25.62266,15.331577 25.504059,13.251152 C 25.348517,10.505481 25.590559,11.089486 26.303134,12.745546 C 26.769759,13.828571 26.261622,15.950138 26.303134,17.484815 C 27.329709,15.648996 27.135261,12.882863 26.349775,10.877714 C 26.087298,10.20494 25.726989,8.8446969 26.094456,7.8981086 z M 24.622001,9.2587722 C 24.485902,8.654687 23.723397,6.4935811 22.726959,5.2026076 C 21.232786,3.2689699 22.578201,3.6726143 23.649494,4.846911 C 24.696483,5.9948613 25.472289,7.3598893 25.501453,8.680032 C 25.560753,11.39277 25.122651,11.470929 24.622001,9.2587722 z M 28.324534,16.874703 C 28.602565,16.515264 28.874762,18.765998 28.371196,19.556391 C 27.026733,21.665043 27.509885,25.128655 26.639823,27.894085 C 25.254531,32.293934 20.848813,35.618286 15.998831,35.528896 C 19.543236,34.354599 22.714341,33.813557 24.183237,26.840229 C 24.312532,26.225793 25.174816,26.557005 24.397107,29.459814 C 25.254531,28.223415 25.999186,25.124891 26.111954,22.623864 C 26.186809,20.930167 27.729588,18.502534 28.324534,16.874703 z M 0.35036898,7.0776065 C 3.1160939,2.0115701 8.6912897,0.54275818 13.214636,0.54275818 C 16.041605,0.54275818 18.507912,1.0273437 20.818678,2.3550142 C 21.174479,2.6147144 21.713042,2.9355759 22.002738,3.2253863 C 22.938905,4.1653883 21.369626,3.7445623 19.405193,2.8753556 C 15.80217,1.2811182 13.224356,1.5241817 11.057467,1.8713897 C 9.4544161,2.1282671 8.4327017,3.4107521 7.629718,4.1889118 C 4.6705382,7.05126 4.2097461,9.6106249 4.5655476,12.822062 C 5.0068971,16.806955 4.2680743,21.015793 3.3367686,23.309812 C 2.8604223,24.48505 2.9236112,26.011258 3.1365088,26.978548 C 3.5214743,28.723056 5.0173803,29.448829 3.1481744,29.208582 C -0.12595652,28.787761 -0.12403302,24.289333 0.99392257,21.109887 C 2.1361815,17.860811 1.50895,15.966631 1.7492717,13.57764 C 2.8163869,2.8893489 3.1267874,15.841548 3.0655428,16.408937 C 2.7077971,19.726701 2.2340211,20.608302 1.7385783,22.084704 C 0.6727661,25.260787 1.9314335,27.1104 2.2661583,27.366598 C 0.91096007,23.04949 3.2794126,21.655634 3.4660626,16.648877 C 3.5117529,15.433179 3.5311956,14.688893 3.3455178,13.046948 C 2.7748744,7.9799706 4.0814242,5.0574021 6.9725545,2.7097496 C 5.2188225,2.7266866 3.8724152,3.9950776 2.6825216,5.1477326 C 1.376944,6.4114188 0.16469114,9.2935264 -0.21249732,10.402898 C -0.77633582,12.06178 -1.2711526,10.046281 0.35036898,7.0776065 z M -1.1097781,28.513226 C -2.0128918,25.368594 -2.4649347,21.861701 -2.3900803,18.216487 C -2.3609163,16.765553 -2.2306501,15.171597 -1.7834679,13.305707 C -0.91243472,9.6699043 -0.68197602,11.586122 -0.74034592,12.032491 C -1.892347,20.8421 1.6144968,19.138912 -0.52455272,25.744091 C -1.2711526,25.202108 -0.48304042,21.510409 -1.656139,19.312946 C -1.3508469,21.734954 -2.0167804,25.74309 0.62937185,28.271403 C 1.3856931,28.994988 2.8681995,29.389244 0.72269676,29.208582 C -0.014181718,29.14648 -0.61787772,28.901835 -1.1097781,28.513226 z M 0.48744008,32.752173 C 0.31731635,32.398379 0.15496979,32.036116 -0.001544018,31.666325 C 1.0075324,31.547766 3.0567937,31.442381 4.827052,32.187607 C 5.8555711,32.620441 3.7965886,32.40967 2.0000826,32.51976 C 1.3565291,32.558339 0.82574314,32.712653 0.48744008,32.752173 z M 1.3409749,34.366831 C 1.1446035,34.029973 0.95600926,33.683706 0.77422,33.328971 C 5.2207668,32.537638 7.1728144,33.319562 10.628756,33.606549 C 14.190659,33.902946 18.050036,32.097277 19.263261,30.020616 C 19.886399,28.953587 20.061384,28.694827 20.186789,28.414427 C 20.295669,28.171663 20.367606,27.912904 20.662164,27.098048 C 21.315439,25.292378 21.284331,27.753885 21.120039,28.360793 C 20.008889,32.4586 19.076611,31.735955 17.211084,33.542565 C 21.502088,32.007888 22.154392,28.937591 21.595413,24.661006 C 21.461259,23.63726 22.257437,20.869947 22.622961,19.880074 C 22.622961,19.880074 23.390948,17.397867 23.395808,12.997078 C 23.398724,10.126261 22.460614,5.7019479 18.621652,4.0505932 C 17.335517,3.4973188 15.549704,1.9767551 15.549704,1.9767551 C 16.345283,2.4005401 18.263906,2.6674072 20.120685,3.773956 C 20.191651,3.8162985 24.211429,6.0576243 24.612921,11.923462 C 24.802488,14.69548 24.823213,14.824989 24.636563,16.630658 C 24.636563,16.630658 24.522513,18.48748 23.87021,20.022157 C 23.555238,20.876533 23.217907,23.396378 23.404558,24.661006 C 23.836185,27.588279 22.063983,34.896582 14.845879,34.896582 C 11.657275,34.896582 9.7168927,34.487272 8.1906401,34.251096 C 6.8082637,34.036561 4.0143469,33.91612 1.3409749,34.366831 z M 3.9297711,37.852084 C 3.5603597,37.465356 3.2055303,37.055105 2.865283,36.622271 C 3.9113006,36.029477 4.8299684,36.020068 6.6935517,36.070879 C 8.8283608,36.070879 12.045156,36.916786 11.057467,37.195306 C 11.009832,37.207538 6.4806541,36.578988 3.9297711,37.852084 z M 14.733111,41.653493 C 14.310232,41.716536 13.88152,41.763583 13.44406,41.791811 C 11.361746,41.927308 9.4310844,41.515174 7.6851297,40.656094 C 8.5367202,40.538475 10.183517,40.482019 11.744767,40.7022 C 12.669268,40.832991 13.723062,41.288407 14.733111,41.653493 z M 20.761322,39.340655 C 19.332282,40.284421 17.738953,40.992951 16.005636,41.406965 C 14.734083,41.029648 11.984449,40.585503 12.005589,39.75495 C 12.021421,39.132891 15.493321,40.331468 17.489114,40.076472 C 18.855937,39.902398 19.892232,39.635171 20.761322,39.340655 z M 26.76134,32.003183 C 27.309624,31.472491 26.651489,34.623708 23.573708,35.937265 C 21.48945,36.675905 18.394173,38.324437 13.07076,36.917727 C 12.417485,36.744594 10.347808,36.196965 9.8870164,36.103812 C 6.2998369,35.382109 4.4372258,35.597584 2.4064351,36.01254 C 2.1702062,35.685092 1.9417545,35.345412 1.7210798,34.995381 C 2.3753268,34.730976 4.0192075,34.628413 5.200352,34.626531 C 8.9615437,34.619945 14.003037,35.980548 15.996887,36.070879 C 20.670913,36.282591 23.680644,33.000582 24.053944,33.000582 C 24.986221,33.000582 23.179994,34.632178 22.678372,35.082889 C 22.251605,35.467734 24.361138,34.516441 26.76134,32.003183 z M 28.531599,25.021387 C 28.408138,27.148859 28.247735,30.101537 26.467755,31.577877 C 23.628148,33.933056 24.799572,32.277938 26.039044,30.634111 C 28.194268,27.774586 27.297959,21.332891 28.708527,20.0852 C 28.725053,21.437335 28.621035,23.498941 28.531599,25.021387 z" + style="fill:#a1a1a1;fill-opacity:1" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + sodipodi:nodetypes="cscssscccssssssscsssscsscsssccssscscssssscsssscssssssscsssscsssssscscsscssssssscsccssscsccccscccscssccssssssscsccsssccsscscsccscsssssssssssscsscssccsssccsscccsscccssssscscsscssccsssccccsccscscccsscccssccssssccsscc" /><path + sodipodi:type="arc" + style="opacity:0.24299999;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter84266)" + id="path84140" + sodipodi:cx="27.093658" + sodipodi:cy="38.810692" + sodipodi:rx="7.7301183" + sodipodi:ry="1.0405928" + d="M 34.823777,38.810692 A 7.7301183,1.0405928 0 1 1 19.36354,38.810692 A 7.7301183,1.0405928 0 1 1 34.823777,38.810692 z" + transform="matrix(1.1911672,0,0,2.1266149,-5.0625748,-41.775272)" /><g + id="Background"> +</g> +<g + id="Guides"> +</g> + +<circle + clip-rule="evenodd" + cx="26.184" + cy="33.865002" + r="10" + id="circle2428" + sodipodi:cx="26.184" + sodipodi:cy="33.865002" + sodipodi:rx="10" + sodipodi:ry="10" + style="fill:url(#radialGradient8812);fill-opacity:1;fill-rule:evenodd;stroke:#a40000;stroke-width:1.31732059000000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(1.1263785,0,0,1.1511056,-2.2713556,-9.1997707)" /><path + sodipodi:type="arc" + style="opacity:0.5;fill:url(#linearGradient84277);fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path3253" + sodipodi:cx="26.455547" + sodipodi:cy="27.394291" + sodipodi:rx="6.1445141" + sodipodi:ry="3.072257" + d="M 32.600061,27.394291 A 6.1445141,3.072257 0 1 1 20.311033,27.394291 A 6.1445141,3.072257 0 1 1 32.600061,27.394291 z" + transform="matrix(1.1246822,0,0,1.4387643,-2.5144268,-14.969086)" /><path + sodipodi:type="arc" + style="opacity:0.48;fill:none;fill-opacity:1;stroke:#e64837;stroke-width:1.21842730000000010;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path84102" + sodipodi:cx="28.185518" + sodipodi:cy="31.917336" + sodipodi:rx="10.027505" + sodipodi:ry="10.240856" + d="M 38.213023,31.917336 A 10.027505,10.240856 0 1 1 18.158013,31.917336 A 10.027505,10.240856 0 1 1 38.213023,31.917336 z" + transform="matrix(0.9962424,0,0,0.9957004,-0.8393988,-2.0505383)" /><g + id="g7978" + transform="translate(103.26268,8.6771365)"> +</g><g + style="display:none" + id="g7980" + display="none" + transform="translate(103.26268,8.6771365)"> +</g><path + id="path7993" + style="opacity:0.55;fill:#a40000;fill-opacity:1;fill-rule:evenodd;stroke:#a40000;stroke-width:1.14231765000000007;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 27.143847,37.652725 C 26.708227,37.652725 26.326837,37.518032 25.998788,37.249507 C 25.677852,36.974118 25.516939,36.592349 25.516939,36.10334 C 25.516939,35.676959 25.66985,35.315778 25.977452,35.018942 C 26.291276,34.7161 26.672666,34.565107 27.122511,34.56425 C 27.571466,34.56425 27.953745,34.7161 28.26757,35.018942 C 28.588505,35.31492 28.749418,35.676959 28.749418,36.10334 C 28.749418,36.585484 28.588505,36.963823 28.26757,37.239212 C 27.946633,37.515458 27.571466,37.652725 27.143847,37.652725 z M 25.956115,30.858079 L 25.613842,25.912229 C 25.549833,24.94794 25.517828,24.256464 25.517828,23.836088 C 25.517828,23.26472 25.670739,22.82118 25.978341,22.504613 C 26.292165,22.18118 26.702004,22.019893 27.208746,22.019035 C 27.82217,22.019035 28.232008,22.225791 28.439151,22.638445 C 28.645403,23.045095 28.749418,23.633622 28.749418,24.404025 C 28.749418,24.858717 28.724526,25.320272 28.674741,25.787834 L 28.214228,30.878669 C 28.164443,31.484354 28.056871,31.94934 27.893292,32.272773 C 27.728822,32.596205 27.457671,32.758349 27.079838,32.758349 C 26.694892,32.758349 26.427296,32.603067 26.277052,32.293362 C 26.126807,31.976794 26.020125,31.498939 25.956115,30.858079 z" /><path + id="path8814" + style="fill:#ffffff;fill-rule:evenodd" + d="M 27.143847,36.992527 C 26.708227,36.992527 26.326837,36.857834 25.998788,36.589309 C 25.677852,36.31392 25.516939,35.932151 25.516939,35.443142 C 25.516939,35.016761 25.66985,34.65558 25.977452,34.358744 C 26.291276,34.055902 26.672666,33.904909 27.122511,33.904052 C 27.571466,33.904052 27.953745,34.055902 28.26757,34.358744 C 28.588505,34.654722 28.749418,35.016761 28.749418,35.443142 C 28.749418,35.925286 28.588505,36.303625 28.26757,36.579014 C 27.946633,36.85526 27.571466,36.992527 27.143847,36.992527 z M 25.956115,30.197881 L 25.613842,25.252031 C 25.549833,24.287742 25.517828,23.596266 25.517828,23.17589 C 25.517828,22.604522 25.670739,22.160982 25.978341,21.844415 C 26.292165,21.520982 26.702004,21.359695 27.208746,21.358837 C 27.82217,21.358837 28.232008,21.565593 28.439151,21.978247 C 28.645403,22.384897 28.749418,22.973424 28.749418,23.743827 C 28.749418,24.198519 28.724526,24.660074 28.674741,25.127636 L 28.214228,30.218471 C 28.164443,30.824156 28.056871,31.289142 27.893292,31.612575 C 27.728822,31.936007 27.457671,32.098151 27.079838,32.098151 C 26.694892,32.098151 26.427296,31.942869 26.277052,31.633164 C 26.126807,31.316596 26.020125,30.838741 25.956115,30.197881 z" /><rect + style="fill:#666666;fill-opacity:0.75;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="rect9126" + width="1.8060035" + height="42.650608" + x="-26.038683" + y="-24.284592" + transform="matrix(-0.6420845,-0.7666339,0.7632254,-0.6461324,0,0)" + ry="0" + rx="0.018515259" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/print_ok.svg b/panels/user-accounts/data/icons/print_ok.svg new file mode 100644 index 0000000..ba821ef --- /dev/null +++ b/panels/user-accounts/data/icons/print_ok.svg @@ -0,0 +1,310 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 36.184 43.865" + enable-background="new 0 0 36.184 43.865" + xml:space="preserve" + id="svg2419" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="print_ok.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/print_ok.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata2435"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs2433"><linearGradient + inkscape:collect="always" + id="linearGradient84104"><stop + style="stop-color:#fffffc;stop-opacity:1;" + offset="0" + id="stop84106" /><stop + style="stop-color:#fffffc;stop-opacity:0;" + offset="1" + id="stop84108" /></linearGradient><linearGradient + id="linearGradient84076"><stop + style="stop-color:#73d216;stop-opacity:1;" + offset="0" + id="stop84078" /><stop + id="stop84090" + offset="0.31459025" + style="stop-color:#73d216;stop-opacity:1;" /><stop + style="stop-color:#4e9a06;stop-opacity:1;" + offset="1" + id="stop84080" /></linearGradient><linearGradient + id="linearGradient3531"><stop + id="stop3533" + offset="0" + style="stop-color:#9b9b9b;stop-opacity:1;" /><stop + id="stop3535" + offset="1" + style="stop-color:#414141;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient3483"><stop + id="stop3485" + offset="0" + style="stop-color:#000000;stop-opacity:1;" /><stop + id="stop3487" + offset="1" + style="stop-color:#787878;stop-opacity:1" /></linearGradient><linearGradient + id="linearGradient3263"><stop + id="stop3265" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop3267" + offset="1" + style="stop-color:#ffffff;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient3247"><stop + style="stop-color:#52a714;stop-opacity:1;" + offset="0" + id="stop3249" /><stop + style="stop-color:#398800;stop-opacity:1;" + offset="1" + id="stop3251" /></linearGradient><linearGradient + id="linearGradient3233"><stop + id="stop3235" + offset="0" + style="stop-color:#398800;stop-opacity:1;" /><stop + id="stop3237" + offset="1" + style="stop-color:#84c706;stop-opacity:1;" /></linearGradient><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 21.932501 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="36.183998 : 21.932501 : 1" + inkscape:persp3d-origin="18.091999 : 14.621667 : 1" + id="perspective2437" /> + + + + + + <filter + inkscape:collect="always" + id="filter3471"><feGaussianBlur + inkscape:collect="always" + stdDeviation="0.057808254" + id="feGaussianBlur3473" /></filter><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3233" + id="linearGradient3517" + gradientUnits="userSpaceOnUse" + x1="25.144751" + y1="43.865002" + x2="25.144751" + y2="23.838018" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3247" + id="linearGradient3519" + gradientUnits="userSpaceOnUse" + x1="30.691881" + y1="23.365002" + x2="30.691881" + y2="44.365963" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3263" + id="linearGradient3521" + gradientUnits="userSpaceOnUse" + x1="26.455547" + y1="24.322035" + x2="26.455547" + y2="30.466549" /><linearGradient + gradientUnits="userSpaceOnUse" + y2="55.692348" + x2="18.072493" + y1="29.205048" + x1="21.55229" + id="linearGradient5138" + xlink:href="#linearGradient5132" + inkscape:collect="always" /><linearGradient + id="linearGradient5132" + inkscape:collect="always"><stop + id="stop5134" + offset="0" + style="stop-color:white;stop-opacity:1;" /><stop + id="stop5136" + offset="1" + style="stop-color:white;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient1913"><stop + id="stop1915" + offset="0" + style="stop-color:#73d216;stop-opacity:1" /><stop + id="stop1917" + offset="1" + style="stop-color:#8ae234;stop-opacity:1" /></linearGradient><inkscape:perspective + id="perspective84036" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient84076" + id="radialGradient84088" + cx="26.183998" + cy="40.111427" + fx="26.183998" + fy="40.111427" + r="10.5" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.9228377,7.3282173e-8,-7.9001009e-8,0.8924238,2.0204218,4.4167191)" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3263" + id="linearGradient84092" + gradientUnits="userSpaceOnUse" + x1="26.455547" + y1="24.322035" + x2="26.455547" + y2="30.466549" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient84104" + id="linearGradient84110" + x1="28.185518" + y1="22.649143" + x2="27.596079" + y2="42.648415" + gradientUnits="userSpaceOnUse" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient84076" + id="radialGradient84134" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0862379,-2.5925308e-8,1.5464794e-8,0.8186283,-2.2580516,7.302016)" + cx="26.183998" + cy="39.098457" + fx="26.183998" + fy="39.098457" + r="10.5" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3263" + id="linearGradient84136" + gradientUnits="userSpaceOnUse" + x1="26.455547" + y1="24.322035" + x2="26.455547" + y2="30.466549" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient84104" + id="linearGradient84138" + gradientUnits="userSpaceOnUse" + x1="21.515692" + y1="23.09075" + x2="34.488232" + y2="40.661182" /><filter + inkscape:collect="always" + id="filter84266" + x="-0.07103052" + width="1.142061" + y="-0.5276553" + height="2.0553105"><feGaussianBlur + inkscape:collect="always" + stdDeviation="0.45756194" + id="feGaussianBlur84268" /></filter><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3263" + id="linearGradient84277" + gradientUnits="userSpaceOnUse" + x1="26.455547" + y1="24.322035" + x2="26.455547" + y2="30.466549" /><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient84104" + id="linearGradient84279" + gradientUnits="userSpaceOnUse" + x1="21.515692" + y1="23.09075" + x2="34.488232" + y2="40.661182" /></defs><sodipodi:namedview + inkscape:window-height="713" + inkscape:window-width="1222" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="4.399364" + inkscape:cx="39.372624" + inkscape:cy="7.0437262" + inkscape:window-x="15" + inkscape:window-y="165" + inkscape:current-layer="svg2419" /> +<path + sodipodi:type="arc" + style="opacity:0.24299999;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter84266)" + id="path84140" + sodipodi:cx="27.093658" + sodipodi:cy="38.810692" + sodipodi:rx="7.7301183" + sodipodi:ry="1.0405928" + d="M 34.823777,38.810692 A 7.7301183,1.0405928 0 1 1 19.36354,38.810692 A 7.7301183,1.0405928 0 1 1 34.823777,38.810692 z" + transform="matrix(1.1911672,0,0,2.1266149,-5.0625748,-41.775272)" /><g + id="Background"> +</g> +<g + id="Guides"> +</g> +<path + id="path2424" + d="M 11.4715,8.6587828 C 11.85841,8.5063492 18.052855,7.561643 17.533735,14.80408 C 16.823104,24.735732 10.185364,21.847978 8.4432981,28.076079 C 9.1393466,27.839903 9.3182195,27.169011 9.756653,26.617618 C 11.076813,24.955913 11.899239,24.128823 13.185374,23.57555 C 17.764131,21.605215 20.247937,13.221415 16.391477,9.350375 C 14.944939,7.8975596 12.256985,8.0358782 11.4715,8.6587828 z M -0.42938057,30.607215 C -0.63450107,30.057705 -0.82406747,29.493139 -0.99905177,28.917282 C 0.26764038,30.495244 2.394932,29.496061 3.7663553,29.835942 C 7.7655495,30.827068 10.188281,29.685092 12.483493,26.986468 C 14.524976,24.585181 15.716814,26.01071 17.678584,22.400312 C 18.1734,21.49042 19.513975,20.499607 20.036011,16.462963 C 20.321819,14.250806 21.717163,13.992047 21.54704,12.391502 C 21.286508,9.9394059 21.195769,9.5856116 20.169194,7.9304926 C 18.692521,5.5480253 16.429451,4.9093448 14.383024,4.903159 C 12.660627,4.8984543 11.830403,5.5932308 11.247122,5.820939 C 15.376525,4.8200358 18.963747,6.2462048 19.964074,8.6587828 C 20.4725,9.8848305 20.731088,10.257444 20.821497,11.562532 C 20.964401,13.628842 20.293627,14.400415 20.249881,13.774689 C 20.066148,11.097706 19.138731,7.7893511 16.036647,6.7232629 C 14.08071,6.0514297 11.288738,6.5623616 9.6166659,7.8298117 C 7.5771256,9.7832092 6.4815291,12.601333 6.4815291,14.458754 C 6.4815291,18.022104 6.4552814,19.153118 5.840892,22.47841 C 5.582304,23.881356 4.6490541,25.341699 5.1885893,27.263105 C 5.9837959,26.96953 6.6973432,25.862981 6.9812066,25.096113 C 8.9731118,19.712038 10.278689,20.976666 12.329895,18.813438 C 12.817907,18.298742 13.655888,16.907087 13.757962,16.394273 C 14.004884,15.152229 14.615385,12.184495 12.543765,12.254125 C 10.819197,13.353146 11.543438,15.633991 10.22425,17.856498 C 12.257957,16.947548 12.124774,13.112265 12.871375,13.021934 C 13.519788,12.943836 13.479931,14.488864 13.338,15.31313 C 13.032749,17.084925 12.408638,18.086089 11.005847,19.10513 C 9.8869191,19.918105 8.8467342,20.049837 7.8347414,21.757648 C 7.6442029,22.057809 6.2890464,25.330408 6.120867,25.639037 C 5.2721928,27.201003 5.3742671,25.548707 6.3075169,22.931003 C 6.3075169,22.931003 7.2174355,20.095002 7.0978629,16.9993 C 7.0434233,15.566244 7.1231384,9.904591 10.82989,7.8994415 C 12.425164,7.0365969 14.901193,6.4946138 16.606318,7.7611229 C 21.944313,11.725315 19.458563,21.448078 15.392121,24.198454 C 13.75699,25.305002 12.399889,25.373692 11.005847,27.501164 C 9.918999,29.160046 6.8363585,30.274122 4.6937724,29.305892 C 5.5511957,28.829775 6.8820488,28.787432 7.2650702,27.723226 C 10.071625,19.916223 15.516555,23.290444 16.169829,14.950867 C 16.401197,11.993484 14.864252,9.6797058 12.683753,9.6797058 C 10.452702,9.6797058 8.9526972,12.659672 8.6143939,15.425102 C 8.4520474,16.758419 8.3645551,18.513277 8.2576203,19.43446 C 10.04246,18.397541 8.4714901,11.146635 12.470855,10.731679 C 15.950127,10.371298 15.652653,14.948985 15.256023,16.945666 C 14.953689,18.468112 14.256667,19.779785 11.256658,21.500771 C 9.676938,22.406898 8.629948,23.557671 7.8289087,25.441439 C 7.3593673,26.546106 6.3988976,28.436461 4.53337,28.617122 C 4.3195003,28.637822 3.9131477,26.558339 3.7867702,25.457436 C 3.6001201,23.832427 4.440045,22.749402 4.7190479,21.000189 C 5.0301312,19.057142 5.2478894,17.510232 5.2644158,14.182117 C 4.4799026,9.0737386 5.2780257,7.5635248 7.4089462,5.140597 C 9.9005288,2.306477 12.031512,2.0549452 13.495175,2.0414516 C 15.638858,2.0237012 16.885321,3.5353485 16.249545,3.412085 C 14.609553,3.0949872 11.616348,2.6753267 9.0440775,5.2789155 C 7.6092061,6.7298495 6.0265698,8.5966804 6.1947492,10.870939 C 6.8664947,7.6039854 10.320801,3.7360695 13.733969,3.7360695 C 17.563499,3.7360695 19.963101,5.1556521 21.106332,6.9999001 C 22.267062,8.8733168 22.748269,10.281909 22.748269,12.806458 C 22.748269,14.670467 21.716834,15.435453 21.249236,16.946607 C 21.069391,17.527169 20.450141,19.029855 20.177944,20.195682 C 19.084291,24.880637 18.295889,27.137018 14.257639,30.204492 C 12.498075,31.540631 11.0982,31.630961 10.165922,31.269639 C 10.173699,31.117206 12.353227,30.918668 13.710328,28.921987 C 14.988685,27.040101 16.636455,26.992113 17.383054,24.825122 C 16.609234,25.763242 15.405731,26.658079 14.400543,27.100321 C 14.11668,27.225467 13.604365,27.870953 13.407994,28.100544 C 11.354844,30.515944 9.699297,31.088038 7.3321475,31.358088 C 7.7083639,31.534985 8.097218,31.671422 8.5094033,31.724115 C 11.518163,32.108961 13.373968,32.147539 15.76056,30.039827 C 19.12901,27.065506 19.683127,25.185502 20.034068,22.951704 C 20.293627,21.297527 21.655941,17.421123 22.247619,15.852571 C 23.080867,13.643616 22.556758,18.089853 22.318585,18.673238 C 20.924543,22.100151 21.486437,26.72018 17.942032,30.365393 C 15.509749,32.86736 12.310453,33.796071 7.1221663,32.621775 C 5.7388178,32.30844 6.239509,31.841732 4.657845,31.358088 C 4.1834429,31.143553 1.6402954,30.997707 1.3486549,30.995825 C 0.70898981,30.992061 0.10626602,30.860329 -0.42938057,30.607215 z M 4.2543673,5.1321285 C 2.8544925,6.7006803 3.1305788,7.9154375 2.2021896,9.2289942 C 0.37749172,11.810941 1.4380914,15.378055 1.0521537,17.983526 C 0.55247613,21.363393 0.50913089,20.094485 -0.0071428722,17.974697 C -0.76834557,14.849245 -0.21551087,13.71635 -0.090105372,12.391502 C 0.60497135,9.1141985 1.2125559,7.7404222 2.8554645,5.6731707 C 4.2115933,3.9672413 6.135449,3.0215936 4.2543673,5.1321285 z M 24.022738,36.434475 C 23.474454,37.067729 22.882424,37.657701 22.248591,38.195919 L 21.462134,38.491375 C 18.22784,39.695783 16.567433,39.805873 11.843827,39.067233 C 9.918999,38.766131 7.2913178,38.39634 5.4841183,39.262008 C 6.5702412,39.494461 10.381694,38.345449 11.56288,39.970538 C 11.446224,40.140849 9.1976753,39.867976 7.0823087,40.331861 C 6.5699934,40.042991 6.0751766,39.715542 5.5978581,39.351398 C 5.1390103,38.993839 4.8036236,38.704969 4.4789305,38.398222 C 6.9432934,37.015037 10.891135,37.492095 13.335083,37.804487 C 17.567761,38.346471 19.246638,38.16581 21.951118,37.173114 C 22.683136,36.904005 23.374324,36.654655 24.022738,36.434475 z M 26.080749,7.8825045 C 27.13746,9.9657525 28.24375,11.559709 28.24375,14.719395 C 28.24375,18.197241 25.745362,19.892699 25.346787,22.207418 C 24.973487,24.37441 24.914187,26.178198 24.585605,26.178198 C 24.11898,26.178198 23.970243,24.110005 24.041208,22.659071 C 24.113147,21.19026 24.300417,20.389257 24.641637,19.294941 C 25.02563,18.066069 25.608953,15.315973 25.490352,13.235548 C 25.33481,10.489877 25.576852,11.073882 26.289427,12.729942 C 26.756052,13.812967 26.247915,15.934534 26.289427,17.469211 C 27.316002,15.633392 27.121554,12.867259 26.336068,10.86211 C 26.073591,10.189336 25.713282,8.8290928 26.080749,7.8825045 z M 24.608294,9.2431681 C 24.472195,8.6390829 23.70969,6.477977 22.713252,5.1870035 C 21.219079,3.2533658 22.564494,3.6570102 23.635787,4.8313069 C 24.682776,5.9792572 25.458582,7.3442852 25.487746,8.6644279 C 25.547046,11.377166 25.108944,11.455325 24.608294,9.2431681 z M 28.310827,16.859099 C 28.588858,16.49966 28.861055,18.750394 28.357489,19.540787 C 27.013026,21.649439 27.496178,25.113051 26.626116,27.878481 C 25.240824,32.27833 20.835106,35.602682 15.985124,35.513292 C 19.529529,34.338995 22.700634,33.797953 24.16953,26.824625 C 24.298825,26.210189 25.161109,26.541401 24.3834,29.44421 C 25.240824,28.207811 25.985479,25.109287 26.098247,22.60826 C 26.173102,20.914563 27.715881,18.48693 28.310827,16.859099 z M 0.33666203,7.0620024 C 3.1023869,1.995966 8.6775827,0.52715408 13.200929,0.52715408 C 16.027898,0.52715408 18.494205,1.0117396 20.804971,2.3394101 C 21.160772,2.5991103 21.699335,2.9199718 21.989031,3.2097822 C 22.925198,4.1497842 21.355919,3.7289582 19.391486,2.8597515 C 15.788463,1.2655141 13.210649,1.5085776 11.04376,1.8557856 C 9.4407091,2.112663 8.4189947,3.395148 7.616011,4.1733077 C 4.6568312,7.0356559 4.1960391,9.5950208 4.5518406,12.806458 C 4.9931901,16.791351 4.2543673,21.000189 3.3230616,23.294208 C 2.8467153,24.469446 2.9099042,25.995654 3.1228018,26.962944 C 3.5077673,28.707452 5.0036733,29.433225 3.1344674,29.192978 C -0.13966347,28.772157 -0.13773997,24.273729 0.98021562,21.094283 C 2.1224745,17.845207 1.495243,15.951027 1.7355647,13.562036 C 2.8026799,2.8737448 3.1130804,15.825944 3.0518358,16.393333 C 2.6940901,19.711097 2.2203141,20.592698 1.7248713,22.0691 C 0.65905915,25.245183 1.9177265,27.094796 2.2524513,27.350994 C 0.89725312,23.033886 3.2657056,21.64003 3.4523556,16.633273 C 3.4980459,15.417575 3.5174886,14.673289 3.3318108,13.031344 C 2.7611674,7.9643665 4.0677172,5.041798 6.9588475,2.6941455 C 5.2051155,2.7110825 3.8587082,3.9794735 2.6688146,5.1321285 C 1.363237,6.3958147 0.15098419,9.2779223 -0.22620427,10.387294 C -0.79004277,12.046176 -1.2848596,10.030677 0.33666203,7.0620024 z M -1.1234851,28.497622 C -2.0265988,25.35299 -2.4786417,21.846097 -2.4037873,18.200883 C -2.3746233,16.749949 -2.2443571,15.155993 -1.7971749,13.290103 C -0.92614167,9.6543002 -0.69568297,11.570518 -0.75405287,12.016887 C -1.906054,20.826496 1.6007898,19.123308 -0.53825967,25.728487 C -1.2848596,25.186504 -0.49674737,21.494805 -1.669846,19.297342 C -1.3645539,21.71935 -2.0304874,25.727486 0.6156649,28.255799 C 1.3719861,28.979384 2.8544925,29.37364 0.70898981,29.192978 C -0.027888672,29.130876 -0.63158467,28.886231 -1.1234851,28.497622 z M 0.47373313,32.736569 C 0.3036094,32.382775 0.14126284,32.020512 -0.015250972,31.650721 C 0.99382545,31.532162 3.0430867,31.426777 4.813345,32.172003 C 5.8418641,32.604837 3.7828816,32.394066 1.9863756,32.504156 C 1.3428221,32.542735 0.81203619,32.697049 0.47373313,32.736569 z M 1.3272679,34.351227 C 1.1308965,34.014369 0.94230231,33.668102 0.76051305,33.313367 C 5.2070598,32.522034 7.1591074,33.303958 10.615049,33.590945 C 14.176952,33.887342 18.036329,32.081673 19.249554,30.005012 C 19.872692,28.937983 20.047677,28.679223 20.173082,28.398823 C 20.281962,28.156059 20.353899,27.8973 20.648457,27.082444 C 21.301732,25.276774 21.270624,27.738281 21.106332,28.345189 C 19.995182,32.442996 19.062904,31.720351 17.197377,33.526961 C 21.488381,31.992284 22.140685,28.921987 21.581706,24.645402 C 21.447552,23.621656 22.24373,20.854343 22.609254,19.86447 C 22.609254,19.86447 23.377241,17.382263 23.382101,12.981474 C 23.385017,10.110657 22.446907,5.6863438 18.607945,4.0349891 C 17.32181,3.4817147 15.535997,1.961151 15.535997,1.961151 C 16.331576,2.384936 18.250199,2.6518031 20.106978,3.7583519 C 20.177944,3.8006944 24.197722,6.0420202 24.599214,11.907858 C 24.788781,14.679876 24.809506,14.809385 24.622856,16.615054 C 24.622856,16.615054 24.508806,18.471876 23.856503,20.006553 C 23.541531,20.860929 23.2042,23.380774 23.390851,24.645402 C 23.822478,27.572675 22.050276,34.880978 14.832172,34.880978 C 11.643568,34.880978 9.7031857,34.471668 8.1769331,34.235492 C 6.7945567,34.020957 4.0006399,33.900516 1.3272679,34.351227 z M 3.9160641,37.83648 C 3.5466527,37.449752 3.1918233,37.039501 2.851576,36.606667 C 3.8975936,36.013873 4.8162614,36.004464 6.6798447,36.055275 C 8.8146538,36.055275 12.031449,36.901182 11.04376,37.179702 C 10.996125,37.191934 6.4669471,36.563384 3.9160641,37.83648 z M 14.719404,41.637889 C 14.296525,41.700932 13.867813,41.747979 13.430353,41.776207 C 11.348039,41.911704 9.4173774,41.49957 7.6714227,40.64049 C 8.5230132,40.522871 10.16981,40.466415 11.73106,40.686596 C 12.655561,40.817387 13.709355,41.272803 14.719404,41.637889 z M 20.747615,39.325051 C 19.318575,40.268817 17.725246,40.977347 15.991929,41.391361 C 14.720376,41.014044 11.970742,40.569899 11.991882,39.739346 C 12.007714,39.117287 15.479614,40.315864 17.475407,40.060868 C 18.84223,39.886794 19.878525,39.619567 20.747615,39.325051 z M 26.747633,31.987579 C 27.295917,31.456887 26.637782,34.608104 23.560001,35.921661 C 21.475743,36.660301 18.380466,38.308833 13.057053,36.902123 C 12.403778,36.72899 10.334101,36.181361 9.8733094,36.088208 C 6.2861299,35.366505 4.4235188,35.58198 2.3927281,35.996936 C 2.1564992,35.669488 1.9280475,35.329808 1.7073728,34.979777 C 2.3616198,34.715372 4.0055005,34.612809 5.186645,34.610927 C 8.9478367,34.604341 13.98933,35.964944 15.98318,36.055275 C 20.657206,36.266987 23.666937,32.984978 24.040237,32.984978 C 24.972514,32.984978 23.166287,34.616574 22.664665,35.067285 C 22.237898,35.45213 24.347431,34.500837 26.747633,31.987579 z M 28.517892,25.005783 C 28.394431,27.133255 28.234028,30.085933 26.454048,31.562273 C 23.614441,33.917452 24.785865,32.262334 26.025337,30.618507 C 28.180561,27.758982 27.284252,21.317287 28.69482,20.069596 C 28.711346,21.421731 28.607328,23.483337 28.517892,25.005783 z" + style="fill:#a1a1a1;fill-opacity:1" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + sodipodi:nodetypes="cscssscccssssssscsssscsscsssccssscscssssscsssscssssssscsssscsssssscscsscssssssscsccssscsccccscccscssccssssssscsccsssccsscscsccscsssssssssssscsscssccsssccsscccsscccssssscscsscssccsssccccsccscscccsscccssccssssccsscc" /> +<circle + clip-rule="evenodd" + cx="26.184" + cy="33.865002" + r="10" + id="circle2428" + sodipodi:cx="26.184" + sodipodi:cy="33.865002" + sodipodi:rx="10" + sodipodi:ry="10" + style="fill:url(#radialGradient84134);fill-opacity:1;fill-rule:evenodd;stroke:#448c00;stroke-width:1.31732059;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(1.1263785,0,0,1.1511056,-2.2713556,-9.1997707)" /><path + sodipodi:type="arc" + style="opacity:0.50746268;fill:url(#linearGradient84277);fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path3253" + sodipodi:cx="26.455547" + sodipodi:cy="27.394291" + sodipodi:rx="6.1445141" + sodipodi:ry="3.072257" + d="M 32.600061,27.394291 A 6.1445141,3.072257 0 1 1 20.311033,27.394291 A 6.1445141,3.072257 0 1 1 32.600061,27.394291 z" + transform="matrix(1.1111101,0,0,1.2758999,-2.2387648,-10.924499)" /><path + clip-rule="evenodd" + d="M 21.777021,33.575871 C 22.428021,34.222871 23.330021,35.596871 24.012021,36.209871 C 25.264021,34.898871 27.599021,31.912871 31.077021,29.499871 C 31.754021,29.029871 33.458021,29.462871 32.557021,30.487871 C 29.846021,33.287871 27.332021,36.692871 25.385021,39.387871 C 24.468021,40.656871 23.706021,39.994871 22.908021,38.978871 C 21.912021,37.682871 20.897021,36.071871 20.509021,35.011871 C 20.282021,34.392871 20.974021,32.785871 21.777021,33.575871 z" + id="path2430" + style="opacity:0.42786069;fill:#398800;fill-opacity:1;fill-rule:evenodd;stroke:#398800;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3471)" + transform="matrix(1.1263785,0,0,1.1511056,-2.2713556,-9.1997707)" /><path + clip-rule="evenodd" + d="M 21.969435,28.185201 C 22.702707,28.929966 23.718701,30.511585 24.486892,31.217213 C 25.897117,29.708113 28.527211,26.270911 32.444755,23.493294 C 33.207314,22.952275 35.126663,23.450703 34.111795,24.630586 C 31.058183,27.853682 28.226468,31.773196 26.033409,34.875426 C 25.00052,36.33618 24.142219,35.574148 23.243369,34.404624 C 22.121497,32.912791 20.978222,31.05836 20.541188,29.838188 C 20.2855,29.125654 21.064954,27.275827 21.969435,28.185201 z" + id="path3469" + style="fill:#ffffff;fill-rule:evenodd" /><path + sodipodi:type="arc" + style="opacity:0.36815945;fill:none;fill-opacity:1;stroke:url(#linearGradient84279);stroke-width:1.2184273;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path84102" + sodipodi:cx="28.185518" + sodipodi:cy="31.917336" + sodipodi:rx="10.027505" + sodipodi:ry="10.240856" + d="M 38.213023,31.917336 A 10.027505,10.240856 0 1 1 18.158013,31.917336 A 10.027505,10.240856 0 1 1 38.213023,31.917336 z" + transform="matrix(1.0116992,0,0,1.0189783,-1.4418411,-2.7101136)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/right-index-finger.svg b/panels/user-accounts/data/icons/right-index-finger.svg new file mode 100644 index 0000000..5a621a2 --- /dev/null +++ b/panels/user-accounts/data/icons/right-index-finger.svg @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="right-index-finger.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="922" + inkscape:window-width="1302" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="15.672125" + inkscape:cy="30.299841" + inkscape:window-x="36" + inkscape:window-y="91" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(-1.1605241,0,0,1.3370602,44.823901,-0.7984997)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background" + transform="matrix(-1,0,0,1,40.436754,0)"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1" + transform="matrix(-1,0,0,1,40.436754,0)"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="matrix(-1,0,0,1,92.903401,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/right-little-finger.svg b/panels/user-accounts/data/icons/right-little-finger.svg new file mode 100644 index 0000000..9fcec2a --- /dev/null +++ b/panels/user-accounts/data/icons/right-little-finger.svg @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="right-pinky-finger.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-ring-finger.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="933" + inkscape:window-width="1054" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="22.316511" + inkscape:cy="30.299841" + inkscape:window-x="346" + inkscape:window-y="109" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(-1.1074589,0,0,1.2726911,65.968411,5.5330271)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background" + transform="matrix(-1,0,0,1,40.436756,0)"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1" + transform="matrix(-1,0,0,1,40.436756,0)"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="matrix(-1,0,0,1,92.903403,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/right-middle-finger.svg b/panels/user-accounts/data/icons/right-middle-finger.svg new file mode 100644 index 0000000..b33a654 --- /dev/null +++ b/panels/user-accounts/data/icons/right-middle-finger.svg @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="right-middle-finger.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-index-finger.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="933" + inkscape:window-width="1054" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="22.316511" + inkscape:cy="30.299841" + inkscape:window-x="362" + inkscape:window-y="121" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(-1.1824583,0,0,1.3363867,53.282364,-2.0066594)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background" + transform="matrix(-1,0,0,1,40.436756,0)"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1" + transform="matrix(-1,0,0,1,40.436756,0)"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="matrix(-1,0,0,1,92.903403,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/right-ring-finger.svg b/panels/user-accounts/data/icons/right-ring-finger.svg new file mode 100644 index 0000000..9e264fe --- /dev/null +++ b/panels/user-accounts/data/icons/right-ring-finger.svg @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="right-ring-finger.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-middle-finger.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="933" + inkscape:window-width="1054" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="22.316511" + inkscape:cy="30.299841" + inkscape:window-x="424" + inkscape:window-y="91" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(-1.1824583,0,0,1.3363867,61.073222,-0.7947482)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background" + transform="matrix(-1,0,0,1,40.436756,0)"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1" + transform="matrix(-1,0,0,1,40.436756,0)"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="matrix(-1,0,0,1,92.903403,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/icons/right-thumb.svg b/panels/user-accounts/data/icons/right-thumb.svg new file mode 100644 index 0000000..0aa0f2e --- /dev/null +++ b/panels/user-accounts/data/icons/right-thumb.svg @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + x="0px" + y="0px" + width="48" + height="48" + viewBox="0 0 40.425 46.214" + enable-background="new 0 0 40.425 46.214" + xml:space="preserve" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="right-thumb.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-pinky-finger.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"><metadata + id="metadata44"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs42"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 23.107 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="40.424999 : 23.107 : 1" + inkscape:persp3d-origin="20.2125 : 15.404667 : 1" + id="perspective46" /> + + + + + + +<radialGradient + r="8.341651" + fy="9.3411446" + fx="38.658855" + cy="9.3411446" + cx="38.658855" + gradientUnits="userSpaceOnUse" + id="radialGradient2479" + xlink:href="#linearGradient2378" + inkscape:collect="always" /><linearGradient + id="linearGradient2378"><stop + id="stop2386" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + style="stop-color:#27dc16;stop-opacity:1;" + offset="1" + id="stop2382" /></linearGradient><linearGradient + id="linearGradient3702"><stop + id="stop3704" + offset="0" + style="stop-color:black;stop-opacity:0;" /><stop + style="stop-color:black;stop-opacity:1;" + offset="0.5" + id="stop3710" /><stop + id="stop3706" + offset="1" + style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient + id="linearGradient6732"><stop + id="stop6734" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /><stop + id="stop6736" + offset="1" + style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient + id="linearGradient4585"><stop + id="stop4587" + offset="0" + style="stop-color:#9e9e9e;stop-opacity:1;" /><stop + id="stop4589" + offset="1" + style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective + id="perspective2516" + inkscape:persp3d-origin="24 : 16 : 1" + inkscape:vp_z="48 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 24 : 1" + sodipodi:type="inkscape:persp3d" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86956" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86964" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient2378" + id="radialGradient86966" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" + cx="26.49" + cy="5.5700002" + fx="26.49" + fy="5.5700002" + r="4.0552225" /></defs><sodipodi:namedview + inkscape:window-height="933" + inkscape:window-width="1054" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="11.122171" + inkscape:cx="22.316511" + inkscape:cy="30.299841" + inkscape:window-x="116" + inkscape:window-y="117" + inkscape:current-layer="svg2" /> +<g + id="g35" + style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(-1.1916623,0,0,1.4021101,35.910183,14.334323)"> + <circle + style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:ry="3.829" + sodipodi:rx="3.829" + sodipodi:cy="5.5700002" + sodipodi:cx="26.49" + id="circle37" + r="3.829" + cy="5.5700002" + cx="26.49" + stroke-miterlimit="3.8637" /> + <path + style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path39" + stroke-miterlimit="3.8637" + d="" /> + </g><g + id="Background" + transform="matrix(-1,0,0,1,40.436756,0)"> +</g> +<g + id="Guides" + display="none" + style="display:none"> +</g> +<g + id="g7" + style="fill:#2f2f2f;fill-opacity:1" + transform="matrix(-1,0,0,1,40.436756,0)"> + <path + style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd" + id="path9" + d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.132,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z" + clip-rule="evenodd" /> + </g> +<g + style="display:inline" + inkscape:label="Base" + id="layer1" + transform="matrix(-1,0,0,1,92.903403,2.6102791)" /></svg>
\ No newline at end of file diff --git a/panels/user-accounts/data/join-dialog.ui b/panels/user-accounts/data/join-dialog.ui new file mode 100644 index 0000000..0fda6f7 --- /dev/null +++ b/panels/user-accounts/data/join-dialog.ui @@ -0,0 +1,238 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 3.8 --> + <object class="GtkDialog" id="join-dialog"> + <property name="can_focus">False</property> + <property name="border_width">10</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="destroy_with_parent">True</property> + <property name="type_hint">dialog</property> + <property name="title" translatable="yes">Add User</property> + <property name="use_header_bar">1</property> + <child internal-child="headerbar"> + <object class="GtkHeaderBar" id="join-dialog-header-bar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="show_close_button">False</property> + <child> + <object class="GtkButton" id="button1"> + <property name="label" translatable="yes">_Cancel</property> + <property name="visible">True</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="valign">center</property> + <style> + <class name="text-button"/> + </style> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button2"> + <property name="label" translatable="yes" comments="Translators: This button enrolls the computer in the domain in order to use enterprise logins.">_Enroll</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="valign">center</property> + <style> + <class name="text-button"/> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child> + <object class="GtkBox" id="box2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="orientation">vertical</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel" id="label71"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Domain Administrator Login</property> + <attributes> + <attribute name="weight" value="bold"/> + <attribute name="scale" value="1.2"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label" translatable="yes">In order to use enterprise logins, this computer needs to be +enrolled in the domain. Please have your network administrator +type their domain password here.</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_start">12</property> + <property name="hexpand">True</property> + <property name="row_spacing">6</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">_Domain</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">join-domain</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="join-domain"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="margin_bottom">5</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label14"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Administrator _Name</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">join-name</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="join-name"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <property name="activates_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label15"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Administrator Password</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">join-password</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="join-password"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <property name="activates_default">True</property> + <property name="invisible_char_set">True</property> + <property name="input_purpose">password</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">button1</action-widget> + <action-widget response="-5">button2</action-widget> + </action-widgets> + </object> +</interface> diff --git a/panels/user-accounts/data/net.reactivated.Fprint.Device.xml b/panels/user-accounts/data/net.reactivated.Fprint.Device.xml new file mode 100644 index 0000000..786d89c --- /dev/null +++ b/panels/user-accounts/data/net.reactivated.Fprint.Device.xml @@ -0,0 +1,585 @@ +<!DOCTYPE node PUBLIC +"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" [ +<!ENTITY ERROR_CLAIM_DEVICE "net.reactivated.Fprint.Error.ClaimDevice"> +<!ENTITY ERROR_ALREADY_IN_USE "net.reactivated.Fprint.Error.AlreadyInUse"> +<!ENTITY ERROR_INTERNAL "net.reactivated.Fprint.Error.Internal"> +<!ENTITY ERROR_PERMISSION_DENIED "net.reactivated.Fprint.Error.PermissionDenied"> +<!ENTITY ERROR_NO_ENROLLED_PRINTS "net.reactivated.Fprint.Error.NoEnrolledPrints"> +<!ENTITY ERROR_NO_ACTION_IN_PROGRESS "net.reactivated.Fprint.Error.NoActionInProgress"> +<!ENTITY ERROR_INVALID_FINGERNAME "net.reactivated.Fprint.Error.InvalidFingername"> +]> + +<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> + <interface name="net.reactivated.Fprint.Device"> + value="fprint_device" /> + + <doc:doc> + <doc:title id="polkit-integration"> + PolicyKit integration + </doc:title> + <doc:para> + fprintd uses PolicyKit to check whether users are allowed to access fingerprint data, or the + fingerprint readers itself. + <doc:list> + <doc:item> + <doc:term>net.reactivated.fprint.device.verify</doc:term> + <doc:definition> + Whether the user is allowed to verify fingers against saved fingerprints. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>net.reactivated.fprint.device.enroll</doc:term> + <doc:definition> + Whether the user is allowed to enroll new fingerprints. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>net.reactivated.fprint.device.setusername</doc:term> + <doc:definition> + Whether the user is allowed to query, verify, or enroll fingerprints for users other than itself. + </doc:definition> + </doc:item> + </doc:list> + </doc:para> + + <doc:title id="usernames"> + Usernames + </doc:title> + <doc:para> + When a username argument is used for a method, a PolicyKit check is done on the + <doc:tt>net.reactivated.fprint.device.setusername</doc:tt> PolicyKit + action to see whether the user the client is running as is allowed to access data from other users. + </doc:para> + <doc:para> + By default, only root is allowed to access fingerprint data for users other than itself. For a normal user, + it is recommended that you use an empty string for the username, which will mean "the client the user is + running as". + </doc:para> + <doc:para> + See <doc:ref type="description" to="polkit-integration">PolicyKit integration</doc:ref>. + </doc:para> + + <doc:title id="fingerprint-names"> + Fingerprint names + </doc:title> + <doc:para> + When a finger name argument is used for a method, it refers to either a single finger, or + "any" finger. See the list of possible values below: + <doc:list> + <doc:item> + <doc:term>left-thumb</doc:term> + <doc:definition> + Left thumb + </doc:definition> + </doc:item> + <doc:item> + <doc:term>left-index-finger</doc:term> + <doc:definition> + Left index finger + </doc:definition> + </doc:item> + <doc:item> + <doc:term>left-middle-finger</doc:term> + <doc:definition> + Left middle finger + </doc:definition> + </doc:item> + <doc:item> + <doc:term>left-ring-finger</doc:term> + <doc:definition> + Left ring finger + </doc:definition> + </doc:item> + <doc:item> + <doc:term>left-little-finger</doc:term> + <doc:definition> + Left little finger + </doc:definition> + </doc:item> + <doc:item> + <doc:term>right-thumb</doc:term> + <doc:definition> + Right thumb + </doc:definition> + </doc:item> + <doc:item> + <doc:term>right-index-finger</doc:term> + <doc:definition> + Right index finger + </doc:definition> + </doc:item> + <doc:item> + <doc:term>right-middle-finger</doc:term> + <doc:definition> + Right middle finger + </doc:definition> + </doc:item> + <doc:item> + <doc:term>right-ring-finger</doc:term> + <doc:definition> + Right ring finger + </doc:definition> + </doc:item> + <doc:item> + <doc:term>right-little-finger</doc:term> + <doc:definition> + Right little finger + </doc:definition> + </doc:item> + <doc:item> + <doc:term>any</doc:term> + <doc:definition> + Any finger. This is only used for <doc:ref type="method" to="Device.VerifyStart">Device.VerifyStart</doc:ref> + (select the first finger with a fingerprint associated, or all the fingerprints available for the user when + the device supports it) and <doc:ref type="signal" to="Device::VerifyFingerSelected">Device::VerifyFingerSelected</doc:ref> + (any finger with an associated fingerprint can be used). + </doc:definition> + </doc:item> + </doc:list> + </doc:para> + + <doc:title id="verify-statuses"> + Verify Statuses + </doc:title> + <doc:para> + <doc:list> + Possible values for the result passed through <doc:ref type="signal" to="Device::VerifyResult">Device::VerifyResult</doc:ref> are: + <doc:item> + <doc:term>verify-no-match</doc:term> + <doc:definition> + The verification did not match, <doc:ref type="method" to="Device.VerifyStop">Device.VerifyStop</doc:ref> should now be called. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>verify-match</doc:term> + <doc:definition> + The verification succeeded, <doc:ref type="method" to="Device.VerifyStop">Device.VerifyStop</doc:ref> should now be called. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>verify-retry-scan</doc:term> + <doc:definition> + The user should retry scanning their finger, the verification is still ongoing. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>verify-swipe-too-short</doc:term> + <doc:definition> + The user's swipe was too short. The user should retry scanning their finger, the verification is still ongoing. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>verify-finger-not-centered</doc:term> + <doc:definition> + The user's finger was not centered on the reader. The user should retry scanning their finger, the verification is still ongoing. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>verify-remove-and-retry</doc:term> + <doc:definition> + The user should remove their finger from the reader and retry scanning their finger, the verification is still ongoing. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>verify-disconnected</doc:term> + <doc:definition> + The device was disconnected during the verification, no other actions should be taken, and you shouldn't use the device any more. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>verify-unknown-error</doc:term> + <doc:definition> + An unknown error occurred (usually a driver problem), <doc:ref type="method" to="Device.VerifyStop">Device.VerifyStop</doc:ref> should now be called. + </doc:definition> + </doc:item> + </doc:list> + </doc:para> + + <doc:title id="enroll-statuses"> + Enroll Statuses + </doc:title> + <doc:para> + <doc:list> + Possible values for the result passed through <doc:ref type="signal" to="Device::EnrollResult">Device::EnrollResult</doc:ref> are: + <doc:item> + <doc:term>enroll-completed</doc:term> + <doc:definition> + The enrollment successfully completed, <doc:ref type="method" to="Device.EnrollStop">Device.EnrollStop</doc:ref> should now be called. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>enroll-failed</doc:term> + <doc:definition> + The enrollment failed, <doc:ref type="method" to="Device.EnrollStop">Device.EnrollStop</doc:ref> should now be called. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>enroll-stage-passed</doc:term> + <doc:definition> + One stage of the enrollment passed, the enrollment is still ongoing. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>enroll-retry-scan</doc:term> + <doc:definition> + The user should retry scanning their finger, the enrollment is still ongoing. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>enroll-swipe-too-short</doc:term> + <doc:definition> + The user's swipe was too short. The user should retry scanning their finger, the enrollment is still ongoing. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>enroll-finger-not-centered</doc:term> + <doc:definition> + The user's finger was not centered on the reader. The user should retry scanning their finger, the enrollment is still ongoing. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>enroll-remove-and-retry</doc:term> + <doc:definition> + The user should remove their finger from the reader and retry scanning their finger, the enrollment is still ongoing. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>enroll-data-full</doc:term> + <doc:definition> + No further prints can be enrolled on this device, <doc:ref type="method" to="Device.EnrollStop">Device.EnrollStop</doc:ref> should now be called. + + <doc:ref type="method" to="DeleteEnrolledFingers2">Delete other prints</doc:ref> from the device first to continue + (e.g. from other users). Note that old prints or prints from other operating systems may be deleted automatically + to resolve this error without any notification. + </doc:definition> + </doc:item> + <doc:item> + <doc:term>enroll-disconnected</doc:term> + <doc:definition> + The device was disconnected during the enrollment, no other actions should be taken, and you shouldn't use the device any more. + + </doc:definition> + </doc:item> + <doc:item> + <doc:term>enroll-unknown-error</doc:term> + <doc:definition> + An unknown error occurred (usually a driver problem), <doc:ref type="method" to="Device.EnrollStop">Device.EnrollStop</doc:ref> should now be called. + + </doc:definition> + </doc:item> + </doc:list> + </doc:para> + </doc:doc> + + <!-- ************************************************************ --> + + <method name="ListEnrolledFingers"> + <arg type="s" name="username" direction="in"> + <doc:doc><doc:summary>The username for whom to list the enrolled fingerprints. See <doc:ref type="description" to="usernames">Usernames</doc:ref>.</doc:summary></doc:doc> + </arg> + <arg type="as" name="enrolled_fingers" direction="out"> + <doc:doc><doc:summary>An array of strings representing the enrolled fingerprints. See <doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>.</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + List all the enrolled fingerprints for the chosen user. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_PERMISSION_DENIED;">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="&ERROR_NO_ENROLLED_PRINTS;">if the chosen user doesn't have any fingerprints enrolled</doc:error> + </doc:errors> + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <method name="DeleteEnrolledFingers"> + <arg type="s" name="username" direction="in"> + <doc:doc><doc:summary>The username for whom to delete the enrolled fingerprints. See <doc:ref type="description" to="usernames">Usernames</doc:ref>.</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Delete all the enrolled fingerprints for the chosen user. + </doc:para> + <doc:para> + This call only exists for compatibility reasons, you should instead claim the device using + <doc:ref type="method" to="Device.Claim">Device.Claim</doc:ref> and then call + <doc:ref type="method" to="DeleteEnrolledFingers2">DeleteEnrolledFingers2</doc:ref>. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_PERMISSION_DENIED;">if the caller lacks the appropriate PolicyKit authorization</doc:error> + </doc:errors> + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <method name="DeleteEnrolledFingers2"> + <doc:doc> + <doc:description> + <doc:para> + Delete all the enrolled fingerprints for the user currently claiming the device with <doc:ref type="method" to="Device.Claim">Device.Claim</doc:ref>. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_PERMISSION_DENIED;">if the caller lacks the appropriate PolicyKit authorization</doc:error> + </doc:errors> + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <method name="Claim"> + <arg type="s" name="username" direction="in"> + <doc:doc><doc:summary>The username for whom to claim the device. See <doc:ref type="description" to="usernames">Usernames</doc:ref>.</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Claim the device for the chosen user. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_PERMISSION_DENIED;">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="&ERROR_ALREADY_IN_USE;">if the device is already claimed</doc:error> + <doc:error name="&ERROR_INTERNAL;">if the device couldn't be claimed</doc:error> + </doc:errors> + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <method name="Release"> + <doc:doc> + <doc:description> + <doc:para> + Release a device claimed with <doc:ref type="method" to="Device.Claim">Device.Claim</doc:ref>. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_PERMISSION_DENIED;">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="&ERROR_CLAIM_DEVICE;">if the device was not claimed</doc:error> + </doc:errors> + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <method name="VerifyStart"> + <arg type="s" name="finger_name" direction="in"> + <doc:doc><doc:summary>A string representing the finger to verify. See <doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>.</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Check the chosen finger against a saved fingerprint. You need to have claimed the device using + <doc:ref type="method" to="Device.Claim">Device.Claim</doc:ref>. The finger selected is sent to the front-end + using <doc:ref type="signal" to="Device::VerifyFingerSelected">Device::VerifyFingerSelected</doc:ref> and + verification status through <doc:ref type="signal" to="Device::VerifyStatus">Device::VerifyStatus</doc:ref>. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_PERMISSION_DENIED;">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="&ERROR_CLAIM_DEVICE;">if the device was not claimed</doc:error> + <doc:error name="&ERROR_ALREADY_IN_USE;">if the device was already being used</doc:error> + <doc:error name="&ERROR_NO_ENROLLED_PRINTS;">if there are no enrolled prints for the chosen user</doc:error> + <doc:error name="&ERROR_INTERNAL;">if there was an internal error</doc:error> + </doc:errors> + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <method name="VerifyStop"> + <doc:doc> + <doc:description> + <doc:para> + Stop an on-going fingerprint verification started with <doc:ref type="method" to="Device.VerifyStart">Device.VerifyStart</doc:ref>. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_PERMISSION_DENIED;">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="&ERROR_CLAIM_DEVICE;">if the device was not claimed</doc:error> + <doc:error name="&ERROR_NO_ACTION_IN_PROGRESS;">if there was no ongoing verification</doc:error> + <doc:error name="&ERROR_INTERNAL;">if there was an internal error</doc:error> + </doc:errors> + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <signal name="VerifyFingerSelected"> + <arg type="s" name="finger_name"> + <doc:doc> + <doc:summary> + <doc:para> + A string representing the finger select to be verified. + </doc:para> + </doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:seealso> + <doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>. + </doc:seealso> + </doc:doc> + </signal> + + <!-- ************************************************************ --> + + <signal name="VerifyStatus"> + <arg type="s" name="result"> + <doc:doc> + <doc:summary> + A string representing the status of the verification. + </doc:summary> + </doc:doc> + </arg> + + <arg type="b" name="done"> + <doc:doc> + <doc:summary> + Whether the verification finished and can be stopped. + </doc:summary> + </doc:doc> + </arg> + + <doc:doc> + <doc:seealso> + <doc:ref type="description" to="verify-statuses">Verify Statuses</doc:ref> and <doc:ref type="method" to="Device.VerifyStop">Device.VerifyStop</doc:ref>. + </doc:seealso> + </doc:doc> + </signal> + + <!-- ************************************************************ --> + + <method name="EnrollStart"> + <arg type="s" name="finger_name" direction="in"> + <doc:doc><doc:summary>A string representing the finger to enroll. See + <doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>. + Note that "any" is not a valid finger name for this method.</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Start enrollment for the selected finger. You need to have claimed the device using + <doc:ref type="method" to="Device.Claim">Device.Claim</doc:ref> before calling + this method. Enrollment status is sent through <doc:ref type="signal" to="Device::EnrollStatus">Device::EnrollStatus</doc:ref>. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_PERMISSION_DENIED;">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="&ERROR_CLAIM_DEVICE;">if the device was not claimed</doc:error> + <doc:error name="&ERROR_ALREADY_IN_USE;">if the device was already being used</doc:error> + <doc:error name="&ERROR_INVALID_FINGERNAME;">if the finger name passed is invalid</doc:error> + <doc:error name="&ERROR_INTERNAL;">if there was an internal error</doc:error> + </doc:errors> + + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <method name="EnrollStop"> + <doc:doc> + <doc:description> + <doc:para> + Stop an on-going fingerprint enrollment started with <doc:ref type="method" to="Device.EnrollStart">Device.EnrollStart</doc:ref>. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_PERMISSION_DENIED;">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="&ERROR_CLAIM_DEVICE;">if the device was not claimed</doc:error> + <doc:error name="&ERROR_NO_ACTION_IN_PROGRESS;">if there was no ongoing verification</doc:error> + <doc:error name="&ERROR_INTERNAL;">if there was an internal error</doc:error> + </doc:errors> + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <signal name="EnrollStatus"> + <arg type="s" name="result"> + <doc:doc> + <doc:summary> + A string representing the status of the enrollment. + </doc:summary> + </doc:doc> + </arg> + + <arg type="b" name="done"> + <doc:doc> + <doc:summary> + Whether the enrollment finished and can be stopped. + </doc:summary> + </doc:doc> + </arg> + + <doc:doc> + <doc:seealso> + <doc:ref type="description" to="enroll-statuses">Enrollment Statuses</doc:ref> and <doc:ref type="method" to="Device.EnrollStop">Device.EnrollStop</doc:ref>. + </doc:seealso> + </doc:doc> + </signal> + + <!-- ************************************************************ --> + + <property name="name" type="s" access="read"> + <doc:doc> + <doc:description> + <doc:para> + The product name of the device. + </doc:para> + </doc:description> + </doc:doc> + </property> + + <!-- ************************************************************ --> + + <property name="num-enroll-stages" type="i" access="read"> + <doc:doc> + <doc:description> + <doc:para> + The number of enrollment stages for the device. This is only available when the device has been claimed, otherwise it will be undefined (-1). + </doc:para> + <doc:seealso> + <doc:ref type="method" to="Device.Claim">Device.Claim</doc:ref> and <doc:ref type="method" to="Device.EnrollStart">Device.EnrollStart</doc:ref>. + </doc:seealso> + </doc:description> + </doc:doc> + </property> + + <!-- ************************************************************ --> + + <property name="scan-type" type="s" access="read"> + <doc:doc> + <doc:description> + <doc:para> + The scan type of the device, either "press" if you place your finger on the device, or "swipe" if you have to swipe your finger. + </doc:para> + </doc:description> + </doc:doc> + </property> + + </interface> +</node> + diff --git a/panels/user-accounts/data/net.reactivated.Fprint.Manager.xml b/panels/user-accounts/data/net.reactivated.Fprint.Manager.xml new file mode 100644 index 0000000..f4a38c7 --- /dev/null +++ b/panels/user-accounts/data/net.reactivated.Fprint.Manager.xml @@ -0,0 +1,50 @@ +<!DOCTYPE node PUBLIC +"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" [ +<!ENTITY ERROR_NO_SUCH_DEVICE "net.reactivated.Fprint.Error.NoSuchDevice"> +]> +<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> + <interface name="net.reactivated.Fprint.Manager"> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" + value="fprint_manager" /> + + <!-- ************************************************************ --> + + <method name="GetDevices"> + <arg type="ao" name="devices" direction="out"> + <doc:doc><doc:summary>An array of object paths for devices.</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Enumerate all the fingerprint readers attached to the system. If there are + no devices available, an empty array is returned. + </doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- ************************************************************ --> + + <method name="GetDefaultDevice"> + <arg type="o" name="device" direction="out"> + <doc:doc><doc:summary>The object path for the default device.</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Returns the default fingerprint reader device. + </doc:para> + </doc:description> + + <doc:errors> + <doc:error name="&ERROR_NO_SUCH_DEVICE;">if the device does not exist</doc:error> + </doc:errors> + </doc:doc> + </method> + + </interface> +</node> + diff --git a/panels/user-accounts/data/org.freedesktop.realmd.xml b/panels/user-accounts/data/org.freedesktop.realmd.xml new file mode 100644 index 0000000..316213a --- /dev/null +++ b/panels/user-accounts/data/org.freedesktop.realmd.xml @@ -0,0 +1,666 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/"> + + <!-- + org.freedesktop.realmd.Provider: + @short_description: a realm provider + + Various realm providers represent different software implementations + that provide access to realms or domains. + + This interface is implemented by individual providers, but is + aggregated globally at the system bus name + <literal>org.freedesktop.realmd</literal> + with the object path <literal>/org/freedesktop/realmd</literal> + --> + <interface name="org.freedesktop.realmd.Provider"> + + <!-- + Name: the name of the provider + + The name of the provider. This is not normally displayed + to the user, but may be useful for diagnostics or debugging. + --> + <property name="Name" type="s" access="read"/> + + <!-- + Version: the version of the provider + + The version of the provider. This is not normally used in + logic, but may be useful for diagnostics or debugging. + --> + <property name="Version" type="s" access="read"/> + + <!-- + Realms: a list of realms + + A list of known, enrolled or discovered realms. All realms + that this provider knows about are listed here. As realms + are discovered they are added to this list. + + Each realm is represented by the DBus object path of the + realm object. + --> + <property name="Realms" type="ao" access="read"/> + + <!-- + Discover: + @string: an input string to discover realms for + @options: options for the discovery operation + @relevance: the relevance of the returned results + @realm: a list of realms discovered + + Discover realms for the given string. The input @string is + usually a domain or realm name, perhaps typed by a user. If + an empty string is provided the realm provider should try to + discover a default realm if possible (eg: from DHCP). + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + The @relevance returned can be used to rank results from + different discover calls to different providers. Implementors + should return a positive number if the provider highly + recommends that the realms be handled by this provider, + or a zero if it can possibly handle the realms. Negative + should be returned if no realms are found. + + This method does not return an error when no realms are + discovered. It simply returns an @realm list. + + To see diagnostic information about the discovery process + connect to the org.freedesktop.realmd.Service::Diagnostics + signal. + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.discover-realm</literal>. + + In addition to common DBus error results, this method may + return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the discovery could not be run for some reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to perform a discovery + operation.</para></listitem> + </itemizedlist> + --> + <method name="Discover"> + <arg name="string" type="s" direction="in"/> + <arg name="options" type="a{sv}" direction="in"/> + <arg name="relevance" type="i" direction="out"/> + <arg name="realm" type="ao" direction="out"/> + </method> + + </interface> + + <!-- + org.freedesktop.realmd.Service: + @short_description: the realmd service + + Global calls for managing the realmd service. Usually you'll want + to use #org.freedesktop.realmd.Provider instead. + + This interface is implemented by the realmd service, and is always + available at the object path <literal>/org/freedesktop/realmd</literal> + + The service also implements the + <literal>org.freedesktop.DBus.ObjectManager</literal> interface which + makes it easy to retrieve all realmd objects and properties in one go. + --> + <interface name="org.freedesktop.realmd.Service"> + + <!-- + Cancel: + @operation: the operation to cancel + + Cancel a realmd operation. To be able to cancel an operation + pass a uniquely chosen <literal>operation</literal> string + identifier as an option in the methods <literal>options</literal> + argument. + + These operation string identifiers should be unique per client + calling the realmd service. + + It is not guaranteed that the service can or will cancel the + operation. For example the operation may have already completed + by the time this method is handled. The caller of the operation + method will receive a + <literal>org.freedesktop.realmd.Error.Cancelled</literal> + if the operation was cancelled. + --> + <method name="Cancel"> + <arg name="operation" type="s" direction="in"/> + </method> + + <!-- + SetLocale: + @locale: the locale for the client + + Set the language @locale for the client. This locale is used + for error messages. The locale is used until the next time + this method is called, the client disconnects, or the client + calls #org.freedesktop.realmd.Service.Release(). + --> + <method name="SetLocale"> + <arg name="locale" type="s" direction="in"/> + </method> + + <!-- + Diagnostics: + @data: diagnostic data + @operation: the operation this data resulted from + + This signal is fired when diagnostics result from an operation + in the provider or one of its realms. + + It is not guaranteed that this signal is emitted once per line. + More than one line may be contained in @data, or a partial + line. New line characters are embedded in @data. + + This signal is sent explicitly to the client which invoked + operation method. In order to tell which operation this + diagnostic data results from, pass a unique + <literal>operation</literal> string identifier in the + <literal>options</literal> argument of the operation method. + That same identifier will be passed back via the @operation + argument of this signal. + --> + <signal name="Diagnostics"> + <arg name="data" type="s"/> + <arg name="operation" type="s"/> + </signal> + + <!-- + Release: + + Normally realmd waits until all clients have disconnected + before exiting itself, sometime later. For long lived clients + they can call this method to allow the realmd service to quit. + This is an optimization. The daemon will not exit immediately. + It is safe to call this multiple times. + --> + <method name="Release"> + <!-- no arguments --> + </method> + + </interface> + + <!-- + org.freedesktop.realmd.Realm: + @short_description: a realm + + Represents one realm. + + Contains generic information about a realm, and useful properties for + introspecting what kind of realm this is and how to work with + the realm. + + Use #org.freedesktop.realmd.Provider:Realms or + #org.freedesktop.realmd.Provider.Discover() to get access to some + kerberos realm objects. + + Realms will always implement additional interfaces, such as + #org.freedesktop.realmd.Kerberos. Do not assume that all realms + implement that kerberos interface. Use the + #org.freedesktop.realmd.Realm:SupportedInterfaces property to see + which interfaces are set. + + Different realms support various ways to configure them on the + system. Use the #org.freedesktop.realmd.Realm:Configured property + to determine if a realm is configured. If it is configured the + property will be set to the interface of the mechanism that was + used to configure it. + + To configure a realm, look in the + #org.freedesktop.realmd.Realm:SupportedInterfaces property for a + recognized purpose specific interface that can be used for + configuration, such as the + #org.freedesktop.realmd.KerberosMembership interface and its + #org.freedesktop.realmd.KerberosMembership.Join() method. + + To deconfigure a realm from the current system, you can use the + #org.freedesktop.realmd.Realm.Deconfigure() method. In additon some + of the configuration specific interfaces provide methods to + deconfigure a realm in a specific way, such as + #org.freedesktop.realmd.KerberosMembership.Leave() method. + + The various properties are guaranteed to have been updated before + the operation methods return, if they change state. + --> + <interface name="org.freedesktop.realmd.Realm"> + + <!-- + Name: the realm name + + This is the name of the realm, appropriate for display to + end users where necessary. + --> + <property name="Name" type="s" access="read"/> + + <!-- + Configured: whether this domain is configured and how + + If this property is an empty string, then the realm is not + configured. Otherwise the realm is configured, and contains + a string which is the interface that represents how it was + configured, for example #org.freedesktop.realmd.KerberosMembership. + --> + <property name="Configured" type="s" access="read"/> + + <!-- + Deconfigure: deconfigure this realm + + Deconfigure this realm from the local machine with standard + default behavior. + + The behavior of this method depends on the which configuration + interface is present in the + #org.freedesktop.realmd.Realm.Configured property. It does not + always delete membership accounts in the realm, but just + reconfigures the local machine so it no longer is configured + for the given realm. In some cases the implementation may try + to update membership accounts, but this is not guaranteed. + + Various configuration interfaces may support more specific ways + to deconfigure a realm in a specific way, such as the + #org.freedesktop.realmd.KerberosMembership.Leave() method. + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.deconfigure-realm</literal>. + + In addition to common DBus error results, this method may return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the deconfigure failed for a generic reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to deconfigure a + realm.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>: + returned if this realm is not configured on the machine.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>: + returned if the service is currently performing another operation like + join or leave.</para></listitem> + </itemizedlist> + --> + <method name="Deconfigure"> + <arg name="options" type="a{sv}" direction="in"/> + </method> + + <!-- + SupportedInterfaces: + + Additional supported interfaces of this realm. This includes + interfaces that contain more information about the realm, + such as #org.freedesktop.realmd.Kerberos and interfaces + which contain methods for configuring a realm, such as + #org.freedesktop.realmd.KerberosMembership. + --> + <property name="SupportedInterfaces" type="as" access="read"/> + + <!-- + Details: informational details about the realm + + Informational details about the realm. The following values + should be present: + <itemizedlist> + <listitem><para><literal>server-software</literal>: + identifier of the software running on the server (eg: + <literal>active-directory</literal>).</para></listitem> + <listitem><para><literal>client-software</literal>: + identifier of the software running on the client (eg: + <literal>sssd</literal>).</para></listitem> + </itemizedlist> + --> + <property name="Details" type="a(ss)" access="read"/> + + <!-- + LoginFormats: supported formats for login names + + Supported formats for login to this realm. This is only + relevant once the realm has been enrolled. The formats + will contain a <literal>%U</literal> in the string, which + indicate where the user name should be placed. The formats + may contain a <literal>%D</literal> in the string which + indicate where a domain name should be placed. + + The first format in the list is the preferred format for + login names. + --> + <property name="LoginFormats" type="as" access="read"/> + + <!-- + LoginPolicy: the policy for logins using this realm + + The policy for logging into this computer using this realm. + + The policy can be changed using the + #org.freedesktop.realmd.Realm.ChangeLoginPolicy() method. + + The following policies are predefined. Not all providers + support all these policies and there may be provider specific + policies or multiple policies represented in the string: + <itemizedlist> + <listitem><para><literal>allow-any-login</literal>: allow + login by any authenticated user present in this + realm.</para></listitem> + <listitem><para><literal>allow-permitted-logins</literal>: + only allow the logins permitted in the + #org.freedesktop.realmd.Realm:PermittedLogins + property.</para></listitem> + <listitem><para><literal>deny-any-login</literal>: + don't allow any logins via authenticated users of this + realm.</para></listitem> + </itemizedlist> + --> + <property name="LoginPolicy" type="s" access="read"/> + + <!-- + PermittedLogins: the permitted login names + + The list of permitted authenticated users allowed to login + into this computer. This is only relevant if the + #org.freedesktop.realmd.Realm:LoginPolicy property + contains the <literal>allow-permitted-logins</literal> + string. + --> + <property name="PermittedLogins" type="as" access="read"/> + + <!-- + ChangeLoginPolicy: + @login_policy: the new login policy, or an empty string + @permitted_add: a list of logins to permit + @permitted_remove: a list of logins to not permit + @options: options for this operation + + Change the login policy and/or permitted logins for this realm. + + Not all realms support the all the various login policies. An + error will be returned if the new login policy is not supported. + You may specify an empty string for the @login_policy argument + which will cause no change in the policy itself. If the policy + is changed, it will be reflected in the + #org.freedesktop.realmd.Realm:LoginPolicy property. + + The @permitted_add and @permitted_remove arguments represent + lists of login names that should be added and removed from + the #org.freedesktop.realmd.Kerberos:PermittedLogins property. + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.login-policy</literal>. + + In addition to common DBus error results, this method may return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the policy change failed for a generic reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to change login policy + operation.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>: + returned if the realm is not configured.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>: + returned if the service is currently performing another operation like + join or leave.</para></listitem> + </itemizedlist> + --> + <method name="ChangeLoginPolicy"> + <arg name="login_policy" type="s" direction="in"/> + <arg name="permitted_add" type="as" direction="in"/> + <arg name="permitted_remove" type="as" direction="in"/> + <arg name="options" type="a{sv}" direction="in"/> + </method> + + </interface> + + <!-- + org.freedesktop.realmd.Kerberos: + @short_description: a kerberos realm + + An interface that describes a kerberos realm in more detail. This + is always implemented on an DBus object path that also implements + the #org.freedesktop.realmd.Realm interface. + --> + <interface name="org.freedesktop.realmd.Kerberos"> + + <!-- + RealmName: the kerberos realm name + + The kerberos name for this realm. This is usually in upper + case. + --> + <property name="RealmName" type="s" access="read"/> + + <!-- + DomainName: the DNS domain name + + The DNS domain name for this realm. + --> + <property name="DomainName" type="s" access="read"/> + + </interface> + + <!-- + org.freedesktop.realmd.KerberosMembership: + + An interface used to configure this machine by joining a realm. + + It sets up a computer/host account in the realm for this machine + and a keytab to track the credentials for that account. + + The various properties are guaranteed to have been updated before + the operation methods return, if they change state. + --> + <interface name="org.freedesktop.realmd.KerberosMembership"> + + <!-- + SuggestedAdministrator: common administrator name + + The common administrator name for this type of realm. This + can be used by clients as a hint when prompting the user for + administrative authentication. + --> + <property name="SuggestedAdministrator" type="s" access="read"/> + + <!-- + SupportedJoinCredentials: credentials supported for joining + + Various kinds of credentials that are supported when calling the + #org.freedesktop.realmd.Kerberos.Join() method. + + Each credential is represented by a type, and an owner. The type + denotes which kind of credential is passed to the method. The + owner indicates to the client how to prompt the user or obtain + the credential, and to the service how to use the credential. + + The various types are: + <itemizedlist> + <listitem><para><literal>ccache</literal>: + the credentials should contain an array of bytes as a + <literal>ay</literal> containing the data from a kerberos + credential cache file.</para></listitem> + <listitem><para><literal>password</literal>: + the credentials should contain a pair of strings as a + <literal>(ss)</literal> representing a name and + password. The name may contain a realm in the standard + kerberos format. If missing, it will default to this + realm. The name may be empty for a computer or one time + password.</para></listitem> + <listitem><para><literal>automatic</literal>: + the credentials should contain an empty string as a + <literal>s</literal>. Using <literal>automatic</literal> + indicates that default or system credentials are to be + used.</para></listitem> + </itemizedlist> + + The various owners are: + <itemizedlist> + <listitem><para><literal>administrator</literal>: + the credentials belong to a kerberos user principal. + The caller may use this as a hint to prompt the user + for administrative credentials.</para></listitem> + <listitem><para><literal>user</literal>: + the credentials belong to a kerberos user principal. + The caller may use this as a hint to prompt the user + for his (possibly non-administrative) + credentials.</para></listitem> + <listitem><para><literal>computer</literal>: + the credentials belong to the computer realmd is + being run on.</para></listitem> + <listitem><para><literal>secret</literal>: + the credentials are a one time password or other secret + used to join or leave the computer.</para></listitem> + </itemizedlist> + --> + <property name="SupportedJoinCredentials" type="a(ss)" access="read"/> + + <!-- + SupportedLeaveCredentials: credentials supported for leaving + + Various kinds of credentials that are supported when calling the + #org.freedesktop.realmd.Kerberos.Leave() method. + + See #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials for + a discussion of what the values represent. + --> + <property name="SupportedLeaveCredentials" type="a(ss)" access="read"/> + + <!-- + Join: + + Join this machine to the realm and enroll the machine. + + If this method returns successfully then the machine will be + joined to the realm. It is not necessary to restart services or the + machine afterward. Relevant properties on the realm will be updated + before the method returns. + + The @credentials should be set according to one of the + supported credentials returned by + #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials. + The first string in the tuple is the type, the second string + is the owner, and the variant contains the credential contents + See the discussion at + #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials + for more information. + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.configure-realm</literal>. + + In addition to common DBus error results, this method may return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the join failed for a generic reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to perform an join + operation.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>: + returned if the credentials passed did not authenticate against the realm + correctly. It is appropriate to prompt the user again.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.AlreadyEnrolled</literal>: + returned if already enrolled in this realm, or another realm and enrolling + in multiple realms is not supported.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>: + returned if the service is currently performing another operation like + join or leave.</para></listitem> + </itemizedlist> + --> + <method name="Join"> + <arg name="credentials" type="(ssv)" direction="in"/> + <arg name="options" type="a{sv}" direction="in"/> + </method> + + <!-- + Leave: + + Leave the realm and unenroll the machine. + + If this method returns successfully then the machine will have + left the domain and been unenrolled. It is not necessary to restart + services or the machine afterward. Relevant properties on the realm + will be updated before the method returns. + + The @credentials should be set according to one of the + supported credentials returned by + #org.freedesktop.realmd.Kerberos:SupportedUnenrollCredentials. + The first string in the tuple is the type, the second string + is the owner, and the variant contains the credential contents + See the discussion at + #org.freedesktop.realmd.Kerberos:SupportedEnrollCredentials + for more information. + + @options can contain, but is not limited to, the following values: + <itemizedlist> + <listitem><para><literal>operation</literal>: a string + identifier chosen by the client, which can then later be + passed to org.freedesktop.realmd.Service.Cancel() in order + to cancel the operation</para></listitem> + </itemizedlist> + + This method requires authorization for the PolicyKit action + called <literal>org.freedesktop.realmd.deconfigure-realm</literal>. + + In addition to common DBus error results, this method may return: + <itemizedlist> + <listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>: + may be returned if the unenroll failed for a generic reason.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>: + returned if the operation was cancelled.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>: + returned if the calling client is not permitted to perform an unenroll + operation.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>: + returned if the credentials passed did not authenticate against the realm + correctly. It is appropriate to prompt the user again.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.NotEnrolled</literal>: + returned if not enrolled in this realm.</para></listitem> + <listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>: + returned if the service is currently performing another operation like + enroll or unenroll.</para></listitem> + </itemizedlist> + --> + <method name="Leave"> + <arg name="credentials" type="(ssv)" direction="in"/> + <arg name="options" type="a{sv}" direction="in"/> + </method> + + </interface> + +</node> diff --git a/panels/user-accounts/data/user-accounts-dialog.css b/panels/user-accounts/data/user-accounts-dialog.css new file mode 100644 index 0000000..49be443 --- /dev/null +++ b/panels/user-accounts/data/user-accounts-dialog.css @@ -0,0 +1,28 @@ +levelbar .strength-weak { + background-color: #cc0000; + border-color: #cc0000; +} + +levelbar .strength-low { + background-color: #f5ce00; + border-color: #f5ce00; +} + +levelbar .strength-medium, +levelbar .strength-good, +levelbar .strength-high { + background-color: #73d216; + border-color: #73d216; +} + +.user-icon-button { + background: transparent; + box-shadow: none; + border: 1px solid @borders; + border-radius: 50%; + padding: 3px; +} + +.user-icon-button > .user-icon-button { + padding: 0; +} diff --git a/panels/user-accounts/fingerprint-strings.h b/panels/user-accounts/fingerprint-strings.h new file mode 100644 index 0000000..4336130 --- /dev/null +++ b/panels/user-accounts/fingerprint-strings.h @@ -0,0 +1,172 @@ +/* + * Helper functions to translate statuses and actions to strings + * Copyright (C) 2008 Bastien Nocera <hadess@hadess.net> + * + * Experimental code. This will be moved out of fprintd into it's own + * package once the system has matured. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +struct { + const char *dbus_name; + const char *place_str_generic; + const char *place_str_specific; + const char *swipe_str_generic; + const char *swipe_str_specific; +} fingers[] = { + { "any", + N_("Place your finger on the fingerprint reader"), + N_("Place your finger on %s"), + N_("Swipe your finger across the fingerprint reader"), + N_("Swipe your finger across %s") }, + { "left-thumb", + N_("Place your left thumb on the fingerprint reader"), + N_("Place your left thumb on %s"), + N_("Swipe your left thumb across the fingerprint reader"), + N_("Swipe your left thumb across %s") }, + { "left-index-finger", + N_("Place your left index finger on the fingerprint reader"), + N_("Place your left index finger on %s"), + N_("Swipe your left index finger across the fingerprint reader"), + N_("Swipe your left index finger across %s") }, + { "left-middle-finger", + N_("Place your left middle finger on the fingerprint reader"), + N_("Place your left middle finger on %s"), + N_("Swipe your left middle finger across the fingerprint reader"), + N_("Swipe your left middle finger across %s") }, + { "left-ring-finger", + N_("Place your left ring finger on the fingerprint reader"), + N_("Place your left ring finger on %s"), + N_("Swipe your left ring finger across the fingerprint reader"), + N_("Swipe your left ring finger across %s") }, + { "left-little-finger", + N_("Place your left little finger on the fingerprint reader"), + N_("Place your left little finger on %s"), + N_("Swipe your left little finger across the fingerprint reader"), + N_("Swipe your left little finger across %s") }, + { "right-thumb", + N_("Place your right thumb on the fingerprint reader"), + N_("Place your right thumb on %s"), + N_("Swipe your right thumb across the fingerprint reader"), + N_("Swipe your right thumb across %s") }, + { "right-index-finger", + N_("Place your right index finger on the fingerprint reader"), + N_("Place your right index finger on %s"), + N_("Swipe your right index finger across the fingerprint reader"), + N_("Swipe your right index finger across %s") }, + { "right-middle-finger", + N_("Place your right middle finger on the fingerprint reader"), + N_("Place your right middle finger on %s"), + N_("Swipe your right middle finger across the fingerprint reader"), + N_("Swipe your right middle finger across %s") }, + { "right-ring-finger", + N_("Place your right ring finger on the fingerprint reader"), + N_("Place your right ring finger on %s"), + N_("Swipe your right ring finger across the fingerprint reader"), + N_("Swipe your right ring finger across %s") }, + { "right-little-finger", + N_("Place your right little finger on the fingerprint reader"), + N_("Place your right little finger on %s"), + N_("Swipe your right little finger across the fingerprint reader"), + N_("Swipe your right little finger across %s") }, + { NULL, NULL, NULL, NULL, NULL } +}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + +G_GNUC_UNUSED static char *finger_str_to_msg(const char *finger_name, const char *driver_name, gboolean is_swipe) +{ + int i; + + if (finger_name == NULL) + return NULL; + + for (i = 0; fingers[i].dbus_name != NULL; i++) { + if (g_str_equal (fingers[i].dbus_name, finger_name)) { + if (is_swipe == FALSE) { + if (driver_name) + return g_strdup_printf (TR (fingers[i].place_str_specific), driver_name); + else + return g_strdup (TR (fingers[i].place_str_generic)); + } else { + if (driver_name) + return g_strdup_printf (TR (fingers[i].swipe_str_specific), driver_name); + else + return g_strdup (TR (fingers[i].swipe_str_generic)); + } + } + } + + return NULL; +} + +#pragma GCC diagnostic pop + +/* Cases not handled: + * verify-no-match + * verify-match + * verify-unknown-error + */ +G_GNUC_UNUSED static const char *verify_result_str_to_msg(const char *result, gboolean is_swipe) +{ + if (result == NULL) + return NULL; + + if (strcmp (result, "verify-retry-scan") == 0) { + if (is_swipe == FALSE) + return N_("Place your finger on the reader again"); + else + return N_("Swipe your finger again"); + } + if (strcmp (result, "verify-swipe-too-short") == 0) + return N_("Swipe was too short, try again"); + if (strcmp (result, "verify-finger-not-centered") == 0) + return N_("Your finger was not centered, try swiping your finger again"); + if (strcmp (result, "verify-remove-and-retry") == 0) + return N_("Remove your finger, and try swiping your finger again"); + + return NULL; +} + +/* Cases not handled: + * enroll-completed + * enroll-failed + * enroll-unknown-error + */ +G_GNUC_UNUSED static const char *enroll_result_str_to_msg(const char *result, gboolean is_swipe) +{ + if (result == NULL) + return NULL; + + if (strcmp (result, "enroll-retry-scan") == 0 || strcmp (result, "enroll-stage-passed") == 0) { + if (is_swipe == FALSE) + return N_("Place your finger on the reader again"); + else + return N_("Swipe your finger again"); + } + if (strcmp (result, "enroll-swipe-too-short") == 0) + return N_("Swipe was too short, try again"); + if (strcmp (result, "enroll-finger-not-centered") == 0) + return N_("Your finger was not centered, try swiping your finger again"); + if (strcmp (result, "enroll-remove-and-retry") == 0) + return N_("Remove your finger, and try swiping your finger again"); + + return NULL; +} + diff --git a/panels/user-accounts/meson.build b/panels/user-accounts/meson.build new file mode 100644 index 0000000..b8ee9d9 --- /dev/null +++ b/panels/user-accounts/meson.build @@ -0,0 +1,207 @@ +panels_list += cappletname +desktop = 'gnome-@0@-panel.desktop'.format(cappletname) + +desktop_in = configure_file( + input: 'data/' + desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf +) + +i18n.merge_file( + desktop, + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: control_center_desktopdir +) + +image_data = files( + 'data/faces/bicycle.jpg', + 'data/faces/book.jpg', + 'data/faces/calculator.jpg', + 'data/faces/cat.jpg', + 'data/faces/coffee2.jpg', + 'data/faces/flower2.jpg', + 'data/faces/gamepad.jpg', + 'data/faces/guitar2.jpg', + 'data/faces/headphones.jpg', + 'data/faces/hummingbird.jpg', + 'data/faces/mountain.jpg', + 'data/faces/plane.jpg', + 'data/faces/surfer.jpg', + 'data/faces/tomatoes.jpg', + 'data/faces/tree.jpg', +) + +legacy_image_data = files( + 'data/faces/legacy/astronaut.jpg', + 'data/faces/legacy/baseball.png', + 'data/faces/legacy/butterfly.png', + 'data/faces/legacy/cat-eye.jpg', + 'data/faces/legacy/chess.jpg', + 'data/faces/legacy/coffee.jpg', + 'data/faces/legacy/dice.jpg', + 'data/faces/legacy/energy-arc.jpg', + 'data/faces/legacy/fish.jpg', + 'data/faces/legacy/flake.jpg', + 'data/faces/legacy/flower.jpg', + 'data/faces/legacy/grapes.jpg', + 'data/faces/legacy/guitar.jpg', + 'data/faces/legacy/launch.jpg', + 'data/faces/legacy/leaf.jpg', + 'data/faces/legacy/lightning.jpg', + 'data/faces/legacy/penguin.jpg', + 'data/faces/legacy/puppy.jpg', + 'data/faces/legacy/sky.jpg', + 'data/faces/legacy/soccerball.png', + 'data/faces/legacy/sunflower.jpg', + 'data/faces/legacy/sunset.jpg', + 'data/faces/legacy/tennis-ball.png', + 'data/faces/legacy/yellow-rose.jpg', +) + +image_dir = join_paths(control_center_datadir, 'pixmaps', 'faces') + +install_data( + image_data, + install_dir: image_dir +) + +legacy_image_dir = join_paths(image_dir, 'legacy') + +install_data( + legacy_image_data, + install_dir: legacy_image_dir +) + +# create symlinks for legacy images to not break current images for people +meson.add_install_script('sh', '-c', + '''for f in $DESTDIR@0@/*; do + ln -sf legacy/$(basename $f) $DESTDIR@1@/$(basename $f); + done'''.format(legacy_image_dir, image_dir)) + +polkit = 'org.gnome.controlcenter.@0@.policy'.format(cappletname) + +i18n.merge_file( + polkit, + input: polkit + '.in', + output: polkit, + po_dir: po_dir, + install: true, + install_dir: join_paths(control_center_datadir, 'polkit-1', 'actions') +) + +common_sources = files( + 'cc-add-user-dialog.c', + 'cc-realm-manager.c', + 'pw-utils.c', + 'user-utils.c', +) + +resource_data = files( + 'cc-add-user-dialog.ui', + 'cc-avatar-chooser.ui', + 'cc-carousel.ui', + 'cc-login-history-dialog.ui', + 'cc-password-dialog.ui', + 'cc-user-panel.ui', + 'cc-fingerprint-dialog.ui', + 'data/icons/fingerprint-detection-complete-symbolic.svg', + 'data/icons/fingerprint-detection-symbolic.svg', + 'data/icons/fingerprint-detection-warning-symbolic.svg', + 'data/carousel.css', + 'data/join-dialog.ui', + 'data/user-accounts-dialog.css', + 'data/cc-fingerprint-dialog.css', +) + +common_sources += gnome.compile_resources( + 'cc-' + cappletname + '-resources', + cappletname + '.gresource.xml', + c_name: 'cc_' + cappletname.underscorify(), + dependencies: resource_data, + export: true +) + +realmd_namespace = 'org.freedesktop.realmd' + +common_sources += gnome.gdbus_codegen( + 'cc-realm-generated', + 'data/' + realmd_namespace + '.xml', + interface_prefix: realmd_namespace + '.', + namespace: 'CcRealm', + object_manager: true, + annotations: ['org.freedesktop.realmd.Realm', 'org.gtk.GDBus.C.Name', 'Common'] +) + +fprintd_namespace = 'net.reactivated.Fprint' +common_sources += gnome.gdbus_codegen( + 'cc-fprintd-generated', + sources: [ + 'data' / fprintd_namespace + '.Manager.xml', + 'data' / fprintd_namespace + '.Device.xml', + ], + interface_prefix: fprintd_namespace + '.', + namespace: 'CcFprintd', + autocleanup: 'all', +) + +enum_headers = [ + 'cc-fingerprint-manager.h', +] + +sources = common_sources + files( + 'cc-avatar-chooser.c', + 'cc-carousel.c', + 'cc-crop-area.c', + 'cc-fingerprint-manager.c', + 'cc-fingerprint-dialog.c', + 'cc-login-history-dialog.c', + 'cc-password-dialog.c', + 'cc-user-image.c', + 'cc-user-panel.c', + 'run-passwd.c', +) + +sources += gnome.mkenums_simple( + 'cc-user-accounts-enum-types', + sources: files(enum_headers)) + +# Kerberos support +krb_dep = dependency('krb5', required: false) +assert(krb_dep.found(), 'kerberos libraries not found in your path') + +deps = common_deps + [ + accounts_dep, + gdk_pixbuf_dep, + gnome_desktop_dep, + liblanguage_dep, + krb_dep, + m_dep, + polkit_gobject_dep, + dependency('pwquality', version: '>= 1.2.2') +] + +if enable_cheese + deps += cheese_deps +endif + +if enable_malcontent + deps += malcontent_dep +endif + +cflags += [ + '-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir), + '-DHAVE_LIBPWQUALITY', + '-DUM_PIXMAP_DIR="@0@"'.format(join_paths(control_center_pkgdatadir, 'pixmaps')) +] + +panels_libs += static_library( + cappletname, + sources: sources, + include_directories: [top_inc, shell_inc], + dependencies: deps, + c_args: cflags +) diff --git a/panels/user-accounts/org.gnome.controlcenter.user-accounts.policy.in b/panels/user-accounts/org.gnome.controlcenter.user-accounts.policy.in new file mode 100644 index 0000000..7d9e686 --- /dev/null +++ b/panels/user-accounts/org.gnome.controlcenter.user-accounts.policy.in @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE policyconfig PUBLIC + "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> + +<policyconfig> + <vendor>The GNOME Project</vendor> + <vendor_url>http://www.gnome.org/</vendor_url> + + <action id="org.gnome.controlcenter.user-accounts.administration"> + <description>Manage user accounts</description> + <message>Authentication is required to change user data</message> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.imply">org.freedesktop.accounts.user-administration org.freedesktop.realmd.configure-realm org.freedesktop.realmd.login-policy</annotate> + </action> + +</policyconfig> diff --git a/panels/user-accounts/pw-utils.c b/panels/user-accounts/pw-utils.c new file mode 100644 index 0000000..0f4dfd8 --- /dev/null +++ b/panels/user-accounts/pw-utils.c @@ -0,0 +1,177 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include "pw-utils.h" + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pwquality.h> + +static pwquality_settings_t * +get_pwq (void) +{ + static pwquality_settings_t *settings; + + if (settings == NULL) { + gchar *err = NULL; + gint rv = 0; + + settings = pwquality_default_settings (); + pwquality_set_int_value (settings, PWQ_SETTING_MAX_SEQUENCE, 4); + + rv = pwquality_read_config (settings, NULL, (gpointer)&err); + if (rv < 0) { + g_warning ("failed to read pwquality configuration: %s\n", + pwquality_strerror (NULL, 0, rv, err)); + pwquality_free_settings (settings); + + /* Load just default settings in case of failure. */ + settings = pwquality_default_settings (); + pwquality_set_int_value (settings, PWQ_SETTING_MAX_SEQUENCE, 4); + } + } + + return settings; +} + +gint +pw_min_length (void) +{ + gint value = 0; + gint rv; + + rv = pwquality_get_int_value (get_pwq (), PWQ_SETTING_MIN_LENGTH, &value); + if (rv < 0) { + g_warning ("Failed to read pwquality setting: %s\n", + pwquality_strerror (NULL, 0, rv, NULL)); + } + + return value; +} + +gchar * +pw_generate (void) +{ + gchar *res; + gint rv; + + rv = pwquality_generate (get_pwq (), 0, &res); + + if (rv < 0) { + g_warning ("Password generation failed: %s\n", + pwquality_strerror (NULL, 0, rv, NULL)); + return NULL; + } + + return res; +} + +static const gchar * +pw_error_hint (gint error) +{ + switch (error) { + case PWQ_ERROR_SAME_PASSWORD: + return C_("Password hint", "The new password needs to be different from the old one."); + case PWQ_ERROR_CASE_CHANGES_ONLY: + return C_("Password hint", "Try changing some letters and numbers."); + case PWQ_ERROR_TOO_SIMILAR: + return C_("Password hint", "Try changing the password a bit more."); + case PWQ_ERROR_USER_CHECK: + return C_("Password hint", "A password without your user name would be stronger."); + case PWQ_ERROR_GECOS_CHECK: + return C_("Password hint", "Try to avoid using your name in the password."); + case PWQ_ERROR_BAD_WORDS: + return C_("Password hint", "Try to avoid some of the words included in the password."); + case PWQ_ERROR_ROTATED: + return C_("Password hint", "Try changing the password a bit more."); + case PWQ_ERROR_CRACKLIB_CHECK: + return C_("Password hint", "Try to avoid common words."); + case PWQ_ERROR_PALINDROME: + return C_("Password hint", "Try to avoid reordering existing words."); + case PWQ_ERROR_MIN_DIGITS: + return C_("Password hint", "Try to use more numbers."); + case PWQ_ERROR_MIN_UPPERS: + return C_("Password hint", "Try to use more uppercase letters."); + case PWQ_ERROR_MIN_LOWERS: + return C_("Password hint", "Try to use more lowercase letters."); + case PWQ_ERROR_MIN_OTHERS: + return C_("Password hint", "Try to use more special characters, like punctuation."); + case PWQ_ERROR_MIN_CLASSES: + return C_("Password hint", "Try to use a mixture of letters, numbers and punctuation."); + case PWQ_ERROR_MAX_CONSECUTIVE: + return C_("Password hint", "Try to avoid repeating the same character."); + case PWQ_ERROR_MAX_CLASS_REPEAT: + return C_("Password hint", "Try to avoid repeating the same type of character: you need to mix up letters, numbers and punctuation."); + case PWQ_ERROR_MAX_SEQUENCE: + return C_("Password hint", "Try to avoid sequences like 1234 or abcd."); + case PWQ_ERROR_MIN_LENGTH: + return C_("Password hint", "Password needs to be longer. Try to add more letters, numbers and punctuation."); + case PWQ_ERROR_EMPTY_PASSWORD: + return C_("Password hint", "Mix uppercase and lowercase and try to use a number or two."); + default: + return C_("Password hint", "Adding more letters, numbers and punctuation will make the password stronger."); + } +} + +gdouble +pw_strength (const gchar *password, + const gchar *old_password, + const gchar *username, + const gchar **hint, + gint *strength_level) +{ + gint rv, level, length = 0; + gdouble strength = 0.0; + void *auxerror; + + rv = pwquality_check (get_pwq (), + password, old_password, username, + &auxerror); + + if (password != NULL) + length = strlen (password); + + strength = CLAMP (0.01 * rv, 0.0, 1.0); + if (rv < 0) { + level = (length > 0) ? 1 : 0; + } + else if (strength < 0.50) { + level = 2; + } else if (strength < 0.75) { + level = 3; + } else if (strength < 0.90) { + level = 4; + } else { + level = 5; + } + + if (length && length < pw_min_length()) + *hint = pw_error_hint (PWQ_ERROR_MIN_LENGTH); + else + *hint = pw_error_hint (rv); + + if (strength_level) + *strength_level = level; + + return strength; +} diff --git a/panels/user-accounts/pw-utils.h b/panels/user-accounts/pw-utils.h new file mode 100644 index 0000000..d7df491 --- /dev/null +++ b/panels/user-accounts/pw-utils.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#pragma once + +#include <glib.h> + +gint pw_min_length (void); +gchar *pw_generate (void); +gdouble pw_strength (const gchar *password, + const gchar *old_password, + const gchar *username, + const gchar **hint, + gint *strength_level); diff --git a/panels/user-accounts/run-passwd.c b/panels/user-accounts/run-passwd.c new file mode 100644 index 0000000..56eea9f --- /dev/null +++ b/panels/user-accounts/run-passwd.c @@ -0,0 +1,770 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* run-passwd.c: this file is part of users-admin, a gnome-system-tools frontend + * for user administration. + * + * Copyright (C) 2002 Diego Gonzalez + * Copyright (C) 2006 Johannes H. Jensen + * Copyright (C) 2010 Milan Bouchet-Valat + * + * Written by: Diego Gonzalez <diego@pemas.net> + * Modified by: Johannes H. Jensen <joh@deworks.net>, + * Milan Bouchet-Valat <nalimilan@club.fr>. + * + * 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, 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, see <http://www.gnu.org/licenses/>. + * + * Most of this code originally comes from gnome-about-me-password.c, + * from gnome-control-center. + */ + +#include <config.h> +#include <glib/gi18n.h> + +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/wait.h> + +#if __sun +#include <sys/types.h> +#include <signal.h> +#endif + +#include "run-passwd.h" + +/* Passwd states */ +typedef enum { + PASSWD_STATE_NONE, /* Passwd is not asking for anything */ + PASSWD_STATE_AUTH, /* Passwd is asking for our current password */ + PASSWD_STATE_NEW, /* Passwd is asking for our new password */ + PASSWD_STATE_RETYPE, /* Passwd is asking for our retyped new password */ + PASSWD_STATE_DONE, /* Passwd succeeded but has not yet exited */ + PASSWD_STATE_ERR /* Passwd reported an error but has not yet exited */ +} PasswdState; + +struct PasswdHandler { + const char *current_password; + const char *new_password; + + /* Communication with the passwd program */ + GPid backend_pid; + + GIOChannel *backend_stdin; + GIOChannel *backend_stdout; + + GQueue *backend_stdin_queue; /* Write queue to backend_stdin */ + + /* GMainLoop IDs */ + guint backend_child_watch_id; /* g_child_watch_add (PID) */ + guint backend_stdout_watch_id; /* g_io_add_watch (stdout) */ + + /* State of the passwd program */ + PasswdState backend_state; + gboolean changing_password; + + PasswdCallback auth_cb; + gpointer auth_cb_data; + + PasswdCallback chpasswd_cb; + gpointer chpasswd_cb_data; +}; + +/* Buffer size for backend output */ +#define BUFSIZE 64 + + +static GQuark +passwd_error_quark (void) +{ + static GQuark q = 0; + + if (q == 0) { + q = g_quark_from_static_string("passwd_error"); + } + + return q; +} + +/* Error handling */ +#define PASSWD_ERROR (passwd_error_quark ()) + + +static void +stop_passwd (PasswdHandler *passwd_handler); + +static void +free_passwd_resources (PasswdHandler *passwd_handler); + +static gboolean +io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler); + + +/* + * Spawning and closing of backend {{ + */ + +/* Child watcher */ +static void +child_watch_cb (GPid pid, gint status, PasswdHandler *passwd_handler) +{ + if (WIFEXITED (status)) { + if (WEXITSTATUS (status) >= 255) { + g_warning ("Child exited unexpectedly"); + } + if (WEXITSTATUS (status) == 0) { + if (passwd_handler->backend_state == PASSWD_STATE_RETYPE) { + passwd_handler->backend_state = PASSWD_STATE_DONE; + if (passwd_handler->chpasswd_cb) + passwd_handler->chpasswd_cb (passwd_handler, + NULL, + passwd_handler->chpasswd_cb_data); + } + } + } + + free_passwd_resources (passwd_handler); +} + +static void +ignore_sigpipe (gpointer data) +{ + signal (SIGPIPE, SIG_IGN); +} + +/* Spawn passwd backend + * Returns: TRUE on success, FALSE otherwise and sets error appropriately */ +static gboolean +spawn_passwd (PasswdHandler *passwd_handler, GError **error) +{ + gchar *argv[2]; + gchar **envp; + gint my_stdin, my_stdout, my_stderr; + + argv[0] = "/usr/bin/passwd"; /* Is it safe to rely on a hard-coded path? */ + argv[1] = NULL; + + envp = g_get_environ (); + envp = g_environ_setenv (envp, "LC_ALL", "C", TRUE); + + if (!g_spawn_async_with_pipes (NULL, /* Working directory */ + argv, /* Argument vector */ + envp, /* Environment */ + G_SPAWN_DO_NOT_REAP_CHILD, /* Flags */ + ignore_sigpipe, /* Child setup */ + NULL, /* Data to child setup */ + &passwd_handler->backend_pid, /* PID */ + &my_stdin, /* Stdin */ + &my_stdout, /* Stdout */ + &my_stderr, /* Stderr */ + error)) { /* GError */ + + /* An error occurred */ + free_passwd_resources (passwd_handler); + + g_strfreev (envp); + + return FALSE; + } + + g_strfreev (envp); + + /* 2>&1 */ + if (dup2 (my_stderr, my_stdout) == -1) { + /* Failed! */ + g_set_error_literal (error, + PASSWD_ERROR, + PASSWD_ERROR_BACKEND, + strerror (errno)); + + /* Clean up */ + stop_passwd (passwd_handler); + + return FALSE; + } + + /* Open IO Channels */ + passwd_handler->backend_stdin = g_io_channel_unix_new (my_stdin); + passwd_handler->backend_stdout = g_io_channel_unix_new (my_stdout); + + /* Set raw encoding */ + /* Set nonblocking mode */ + if (g_io_channel_set_encoding (passwd_handler->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL || + g_io_channel_set_encoding (passwd_handler->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL || + g_io_channel_set_flags (passwd_handler->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL || + g_io_channel_set_flags (passwd_handler->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) { + + /* Clean up */ + stop_passwd (passwd_handler); + return FALSE; + } + + /* Turn off buffering */ + g_io_channel_set_buffered (passwd_handler->backend_stdin, FALSE); + g_io_channel_set_buffered (passwd_handler->backend_stdout, FALSE); + + /* Add IO Channel watcher */ + passwd_handler->backend_stdout_watch_id = g_io_add_watch (passwd_handler->backend_stdout, + G_IO_IN | G_IO_PRI, + (GIOFunc) io_watch_stdout, passwd_handler); + + /* Add child watcher */ + passwd_handler->backend_child_watch_id = g_child_watch_add (passwd_handler->backend_pid, (GChildWatchFunc) child_watch_cb, passwd_handler); + + /* Success! */ + + return TRUE; +} + +/* Stop passwd backend */ +static void +stop_passwd (PasswdHandler *passwd_handler) +{ + /* This is the standard way of returning from the dialog with passwd. + * If we return this way we can safely kill passwd as it has completed + * its task. + */ + + if (passwd_handler->backend_pid != -1) { + kill (passwd_handler->backend_pid, 9); + } + + /* We must run free_passwd_resources here and not let our child + * watcher do it, since it will access invalid memory after the + * dialog has been closed and cleaned up. + * + * If we had more than a single thread we'd need to remove + * the child watch before trying to kill the child. + */ + free_passwd_resources (passwd_handler); +} + +/* Clean up passwd resources */ +static void +free_passwd_resources (PasswdHandler *passwd_handler) +{ + GError *error = NULL; + + /* Remove the child watcher */ + if (passwd_handler->backend_child_watch_id != 0) { + + g_source_remove (passwd_handler->backend_child_watch_id); + + passwd_handler->backend_child_watch_id = 0; + } + + + /* Close IO channels (internal file descriptors are automatically closed) */ + if (passwd_handler->backend_stdin != NULL) { + + if (g_io_channel_shutdown (passwd_handler->backend_stdin, TRUE, &error) != G_IO_STATUS_NORMAL) { + g_warning ("Could not shutdown backend_stdin IO channel: %s", error->message); + g_error_free (error); + error = NULL; + } + + g_io_channel_unref (passwd_handler->backend_stdin); + passwd_handler->backend_stdin = NULL; + } + + if (passwd_handler->backend_stdout != NULL) { + + if (g_io_channel_shutdown (passwd_handler->backend_stdout, TRUE, &error) != G_IO_STATUS_NORMAL) { + g_warning ("Could not shutdown backend_stdout IO channel: %s", error->message); + g_error_free (error); + error = NULL; + } + + g_io_channel_unref (passwd_handler->backend_stdout); + + passwd_handler->backend_stdout = NULL; + } + + /* Remove IO watcher */ + if (passwd_handler->backend_stdout_watch_id != 0) { + + g_source_remove (passwd_handler->backend_stdout_watch_id); + + passwd_handler->backend_stdout_watch_id = 0; + } + + /* Close PID */ + if (passwd_handler->backend_pid != -1) { + + g_spawn_close_pid (passwd_handler->backend_pid); + + passwd_handler->backend_pid = -1; + } + + /* Clear backend state */ + passwd_handler->backend_state = PASSWD_STATE_NONE; +} + +/* + * }} Spawning and closing of backend + */ + +/* + * Backend communication code {{ + */ + +/* Write the first element of queue through channel */ +static void +io_queue_pop (GQueue *queue, GIOChannel *channel) +{ + gchar *buf; + gsize bytes_written; + GError *error = NULL; + + buf = g_queue_pop_head (queue); + + if (buf != NULL) { + + if (g_io_channel_write_chars (channel, buf, -1, &bytes_written, &error) != G_IO_STATUS_NORMAL) { + g_warning ("Could not write queue element \"%s\" to channel: %s", buf, error->message); + g_error_free (error); + } + + /* Ensure passwords are cleared from memory */ + memset (buf, 0, strlen (buf)); + g_free (buf); + } +} + +/* Goes through the argument list, checking if one of them occurs in str + * Returns: TRUE as soon as an element is found to match, FALSE otherwise */ +static gboolean +is_string_complete (gchar *str, ...) +{ + va_list ap; + gchar *arg; + + if (strlen (str) == 0) { + return FALSE; + } + + va_start (ap, str); + + while ((arg = va_arg (ap, char *)) != NULL) { + if (strstr (str, arg) != NULL) { + va_end (ap); + return TRUE; + } + } + + va_end (ap); + + return FALSE; +} + +/* + * IO watcher for stdout, called whenever there is data to read from the backend. + * This is where most of the actual IO handling happens. + */ +static gboolean +io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler) +{ + static GString *str = NULL; /* Persistent buffer */ + + gchar buf[BUFSIZE]; /* Temporary buffer */ + gsize bytes_read; + GError *gio_error = NULL; /* Error returned by functions */ + GError *error = NULL; /* Error sent to callbacks */ + + gboolean reinit = FALSE; + + /* Initialize buffer */ + if (str == NULL) { + str = g_string_new (""); + } + + if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &gio_error) + != G_IO_STATUS_NORMAL) { + g_warning ("IO Channel read error: %s", gio_error->message); + g_error_free (gio_error); + + return TRUE; + } + + str = g_string_append_len (str, buf, bytes_read); + + /* In which state is the backend? */ + switch (passwd_handler->backend_state) { + case PASSWD_STATE_AUTH: + /* Passwd is asking for our current password */ + + if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) { + + if (strstr (str->str, "assword: ") != NULL && + strstr (str->str, "incorrect") == NULL && + strstr (str->str, "urrent") == NULL) { + /* Authentication successful */ + + passwd_handler->backend_state = PASSWD_STATE_NEW; + + /* Trigger callback to update authentication status */ + if (passwd_handler->auth_cb) + passwd_handler->auth_cb (passwd_handler, + NULL, + passwd_handler->auth_cb_data); + + } else { + /* Authentication failed */ + + error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED, + _("Authentication failed")); + + passwd_handler->changing_password = FALSE; + + /* This error can happen both while authenticating or while changing password: + * if chpasswd_cb is set, this means we're already changing password */ + if (passwd_handler->chpasswd_cb) + passwd_handler->chpasswd_cb (passwd_handler, + error, + passwd_handler->chpasswd_cb_data); + else if (passwd_handler->auth_cb) + passwd_handler->auth_cb (passwd_handler, + error, + passwd_handler->auth_cb_data); + + g_error_free (error); + } + + reinit = TRUE; + } + break; + case PASSWD_STATE_NEW: + /* Passwd is asking for our new password */ + + if (is_string_complete (str->str, "assword: ", NULL)) { + /* Advance to next state */ + passwd_handler->backend_state = PASSWD_STATE_RETYPE; + + /* Pop retyped password from queue and into IO channel */ + io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin); + + reinit = TRUE; + } + break; + case PASSWD_STATE_RETYPE: + /* Passwd is asking for our retyped new password */ + + if (is_string_complete (str->str, + "successfully", + "short", + "longer", + "palindrome", + "dictionary", + "simple", + "simplistic", + "similar", + "case", + "different", + "wrapped", + "recovered", + "recent", + "unchanged", + "match", + "1 numeric or special", + "failure", + "DIFFERENT", + "BAD PASSWORD", + NULL)) { + + if (strstr (str->str, "successfully") != NULL) { + /* Hooray! */ + + passwd_handler->backend_state = PASSWD_STATE_DONE; + /* Trigger callback to update status */ + if (passwd_handler->chpasswd_cb) + passwd_handler->chpasswd_cb (passwd_handler, + NULL, + passwd_handler->chpasswd_cb_data); + } + else { + /* Ohnoes! */ + + if (strstr (str->str, "recovered") != NULL) { + /* What does this indicate? + * "Authentication information cannot be recovered?" from libpam? */ + error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN, + str->str); + } else if (strstr (str->str, "short") != NULL || + strstr (str->str, "longer") != NULL) { + error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, + _("The new password is too short")); + } else if (strstr (str->str, "palindrome") != NULL || + strstr (str->str, "simple") != NULL || + strstr (str->str, "simplistic") != NULL || + strstr (str->str, "dictionary") != NULL) { + error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, + _("The new password is too simple")); + } else if (strstr (str->str, "similar") != NULL || + strstr (str->str, "different") != NULL || + strstr (str->str, "case") != NULL || + strstr (str->str, "wrapped") != NULL) { + error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, + _("The old and new passwords are too similar")); + } else if (strstr (str->str, "recent") != NULL) { + error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, + _("The new password has already been used recently.")); + } else if (strstr (str->str, "1 numeric or special") != NULL) { + error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, + _("The new password must contain numeric or special characters")); + } else if (strstr (str->str, "unchanged") != NULL || + strstr (str->str, "match") != NULL) { + error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, + _("The old and new passwords are the same")); + } else if (strstr (str->str, "failure") != NULL) { + /* Authentication failure */ + error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED, + _("Your password has been changed since you initially authenticated!")); + } + else if (strstr (str->str, "DIFFERENT")) { + error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, + _("The new password does not contain enough different characters")); + } + else { + error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN, + _("Unknown error")); + } + + /* At this point, passwd might have exited, in which case + * child_watch_cb should clean up for us and remove this watcher. + * On some error conditions though, passwd just re-prompts us + * for our new password. */ + passwd_handler->backend_state = PASSWD_STATE_ERR; + + passwd_handler->changing_password = FALSE; + + /* Trigger callback to update status */ + if (passwd_handler->chpasswd_cb) + passwd_handler->chpasswd_cb (passwd_handler, + error, + passwd_handler->chpasswd_cb_data); + + g_error_free (error); + + } + + reinit = TRUE; + + /* child_watch_cb should clean up for us now */ + } + break; + case PASSWD_STATE_NONE: + /* Passwd is not asking for anything yet */ + if (is_string_complete (str->str, "assword: ", NULL)) { + + /* If the user does not have a password set, + * passwd will immediately ask for the new password, + * so skip the AUTH phase */ + if (is_string_complete (str->str, "new", "New", NULL)) { + gchar *pw; + + passwd_handler->backend_state = PASSWD_STATE_NEW; + + /* since passwd didn't ask for our old password + * in this case, simply remove it from the queue */ + pw = g_queue_pop_head (passwd_handler->backend_stdin_queue); + g_free (pw); + + /* Pop the IO queue, i.e. send new password */ + io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin); + + } else { + + passwd_handler->backend_state = PASSWD_STATE_AUTH; + + /* Pop the IO queue, i.e. send current password */ + io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin); + } + + reinit = TRUE; + } + break; + default: + /* Passwd has returned an error */ + reinit = TRUE; + break; + } + + if (reinit) { + g_string_free (str, TRUE); + str = NULL; + } + + /* Continue calling us */ + return TRUE; +} + +/* + * }} Backend communication code + */ + +/* Adds the current password to the IO queue */ +static void +authenticate (PasswdHandler *passwd_handler) +{ + gchar *s; + + s = g_strdup_printf ("%s\n", passwd_handler->current_password); + + g_queue_push_tail (passwd_handler->backend_stdin_queue, s); +} + +/* Adds the new password twice to the IO queue */ +static void +update_password (PasswdHandler *passwd_handler) +{ + gchar *s; + + s = g_strdup_printf ("%s\n", passwd_handler->new_password); + + g_queue_push_tail (passwd_handler->backend_stdin_queue, s); + /* We need to allocate new space because io_queue_pop() g_free()s + * every element of the queue after it's done */ + g_queue_push_tail (passwd_handler->backend_stdin_queue, g_strdup (s)); +} + + +PasswdHandler * +passwd_init (void) +{ + PasswdHandler *passwd_handler; + + passwd_handler = g_new0 (PasswdHandler, 1); + + /* Initialize backend_pid. -1 means the backend is not running */ + passwd_handler->backend_pid = -1; + + /* Initialize IO Channels */ + passwd_handler->backend_stdin = NULL; + passwd_handler->backend_stdout = NULL; + + /* Initialize write queue */ + passwd_handler->backend_stdin_queue = g_queue_new (); + + /* Initialize watchers */ + passwd_handler->backend_child_watch_id = 0; + passwd_handler->backend_stdout_watch_id = 0; + + /* Initialize backend state */ + passwd_handler->backend_state = PASSWD_STATE_NONE; + passwd_handler->changing_password = FALSE; + + return passwd_handler; +} + +void +passwd_destroy (PasswdHandler *passwd_handler) +{ + g_queue_free (passwd_handler->backend_stdin_queue); + stop_passwd (passwd_handler); + g_free (passwd_handler); +} + +void +passwd_authenticate (PasswdHandler *passwd_handler, + const char *current_password, + PasswdCallback cb, + const gpointer user_data) +{ + GError *error = NULL; + + /* Don't stop if we've already started changing password */ + if (passwd_handler->changing_password) + return; + + /* Clear data from possible previous attempts to change password */ + passwd_handler->new_password = NULL; + passwd_handler->chpasswd_cb = NULL; + passwd_handler->chpasswd_cb_data = NULL; + g_queue_foreach (passwd_handler->backend_stdin_queue, (GFunc) g_free, NULL); + g_queue_clear (passwd_handler->backend_stdin_queue); + + passwd_handler->current_password = current_password; + passwd_handler->auth_cb = cb; + passwd_handler->auth_cb_data = user_data; + + /* Spawn backend */ + stop_passwd (passwd_handler); + + if (!spawn_passwd (passwd_handler, &error)) { + g_warning ("%s", error->message); + g_error_free (error); + + return; + } + + authenticate (passwd_handler); + + /* Our IO watcher should now handle the rest */ +} + +gboolean +passwd_change_password (PasswdHandler *passwd_handler, + const char *new_password, + PasswdCallback cb, + const gpointer user_data) +{ + GError *error = NULL; + + passwd_handler->changing_password = TRUE; + + passwd_handler->new_password = new_password; + passwd_handler->chpasswd_cb = cb; + passwd_handler->chpasswd_cb_data = user_data; + + /* Stop passwd if an error occurred and it is still running */ + if (passwd_handler->backend_state == PASSWD_STATE_ERR) { + + /* Stop passwd, free resources */ + stop_passwd (passwd_handler); + } + + /* Check that the backend is still running, or that an error + * has occurred but it has not yet exited */ + if (passwd_handler->backend_pid == -1) { + /* If it is not, re-run authentication */ + + /* Spawn backend */ + stop_passwd (passwd_handler); + + if (!spawn_passwd (passwd_handler, &error)) { + g_warning ("%s", error->message); + g_error_free (error); + + return FALSE; + } + + /* Add current and new passwords to queue */ + authenticate (passwd_handler); + update_password (passwd_handler); + } else { + /* Only add new passwords to queue */ + update_password (passwd_handler); + } + + /* Pop new password through the backend. + * If user has no password, popping the queue would output current + * password, while 'passwd' is waiting for the new one. So wait for + * io_watch_stdout() to remove current password from the queue, + * and output the new one for us. + */ + if (passwd_handler->current_password) + io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin); + + /* Our IO watcher should now handle the rest */ + + return TRUE; +} diff --git a/panels/user-accounts/run-passwd.h b/panels/user-accounts/run-passwd.h new file mode 100644 index 0000000..c0362bd --- /dev/null +++ b/panels/user-accounts/run-passwd.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* run-passwd.h: this file is part of users-admin, a gnome-system-tools frontend + * for user administration. + * + * Copyright (C) 2010 Milan Bouchet-Valat + * + * 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, see <http://www.gnu.org/licenses/>. + * + * Authors: Milan Bouchet-Valat <nalimilan@club.fr> + */ + +#pragma once + +struct PasswdHandler; + +typedef struct PasswdHandler PasswdHandler; + +typedef void (*PasswdCallback) (PasswdHandler *passwd_handler, GError *error, const gpointer user_data); + +/* Error codes */ +typedef enum { + PASSWD_ERROR_REJECTED, /* New password is not secure enough */ + PASSWD_ERROR_AUTH_FAILED, /* Wrong old password, or PAM failure */ + PASSWD_ERROR_REAUTH_FAILED, /* Password has changed since first authentication */ + PASSWD_ERROR_BACKEND, /* Backend error */ + PASSWD_ERROR_UNKNOWN /* General error */ +} PasswdError; + + +PasswdHandler *passwd_init (void); + +void passwd_destroy (PasswdHandler *passwd_handler); + +void passwd_authenticate (PasswdHandler *passwd_handler, + const char *current_password, + PasswdCallback cb, + gpointer user_data); + +gboolean passwd_change_password (PasswdHandler *passwd_handler, + const char *new_password, + PasswdCallback cb, + const gpointer user_data); + diff --git a/panels/user-accounts/user-accounts.gresource.xml b/panels/user-accounts/user-accounts.gresource.xml new file mode 100644 index 0000000..fcd1a7f --- /dev/null +++ b/panels/user-accounts/user-accounts.gresource.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/user-accounts"> + <file preprocess="xml-stripblanks">cc-add-user-dialog.ui</file> + <file preprocess="xml-stripblanks">cc-avatar-chooser.ui</file> + <file preprocess="xml-stripblanks">cc-carousel.ui</file> + <file preprocess="xml-stripblanks">cc-login-history-dialog.ui</file> + <file preprocess="xml-stripblanks">cc-password-dialog.ui</file> + <file preprocess="xml-stripblanks">cc-user-panel.ui</file> + <file preprocess="xml-stripblanks">cc-fingerprint-dialog.ui</file> + <file alias="join-dialog.ui" preprocess="xml-stripblanks">data/join-dialog.ui</file> + <file alias="user-accounts-dialog.css">data/user-accounts-dialog.css</file> + <file alias="carousel.css">data/carousel.css</file> + <file alias="cc-fingerprint-dialog.css">data/cc-fingerprint-dialog.css</file> + </gresource> + + <gresource prefix="/org/gnome/ControlCenter/icons/scalable/status"> + <file preprocess="xml-stripblanks" alias="fingerprint-detection-complete-symbolic.svg">data/icons/fingerprint-detection-complete-symbolic.svg</file> + <file preprocess="xml-stripblanks" alias="fingerprint-detection-symbolic.svg">data/icons/fingerprint-detection-symbolic.svg</file> + <file preprocess="xml-stripblanks" alias="fingerprint-detection-warning-symbolic.svg">data/icons/fingerprint-detection-warning-symbolic.svg</file> + </gresource> +</gresources> diff --git a/panels/user-accounts/user-utils.c b/panels/user-accounts/user-utils.c new file mode 100644 index 0000000..3c17dfe --- /dev/null +++ b/panels/user-accounts/user-utils.c @@ -0,0 +1,772 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#include "config.h" + +#include <math.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <limits.h> +#include <unistd.h> +#include <utmpx.h> +#include <pwd.h> + +#ifdef __FreeBSD__ +#include <sysexits.h> +#endif + +#include <gio/gio.h> +#include <gio/gunixoutputstream.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include "user-utils.h" + +#define IMAGE_SIZE 512 + +typedef struct { + gchar *text; + gchar *placeholder_str; + GIcon *icon; + gunichar placeholder; + gulong query_id; +} IconShapeData; + +static IconShapeData * +icon_shape_data_new (const gchar *text, + const gchar *placeholder, + GIcon *icon) +{ + IconShapeData *data; + + data = g_new0 (IconShapeData, 1); + + data->text = g_strdup (text); + data->placeholder_str = g_strdup (placeholder); + data->placeholder = g_utf8_get_char_validated (placeholder, -1); + data->icon = g_object_ref (icon); + + return data; +} + +static void +icon_shape_data_free (gpointer user_data) +{ + IconShapeData *data = user_data; + + g_free (data->text); + g_free (data->placeholder_str); + g_object_unref (data->icon); + g_free (data); +} + +static void +icon_shape_renderer (cairo_t *cr, + PangoAttrShape *attr, + gboolean do_path, + gpointer user_data) +{ + IconShapeData *data = user_data; + gdouble x, y; + + cairo_get_current_point (cr, &x, &y); + if (GPOINTER_TO_UINT (attr->data) == data->placeholder) { + gdouble ascent; + gdouble height; + GdkPixbuf *pixbuf; + GtkIconInfo *info; + + ascent = pango_units_to_double (attr->ink_rect.y); + height = pango_units_to_double (attr->ink_rect.height); + info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (), + data->icon, + (gint)height, + GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_USE_BUILTIN); + pixbuf = gtk_icon_info_load_icon (info, NULL); + g_object_unref (info); + + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_reset_clip (cr); + gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y + ascent); + cairo_paint (cr); + g_object_unref (pixbuf); + } +} + +static PangoAttrList * +create_shape_attr_list_for_layout (PangoLayout *layout, + IconShapeData *data) +{ + PangoAttrList *attrs; + PangoFontMetrics *metrics; + gint ascent, descent; + PangoRectangle ink_rect, logical_rect; + const gchar *p; + const gchar *text; + gint placeholder_len; + + /* Get font metrics and prepare fancy shape size */ + metrics = pango_context_get_metrics (pango_layout_get_context (layout), + pango_layout_get_font_description (layout), + NULL); + ascent = pango_font_metrics_get_ascent (metrics); + descent = pango_font_metrics_get_descent (metrics); + pango_font_metrics_unref (metrics); + + logical_rect.x = 0; + logical_rect.y = - ascent; + logical_rect.width = ascent + descent; + logical_rect.height = ascent + descent; + + ink_rect = logical_rect; + + attrs = pango_attr_list_new (); + text = pango_layout_get_text (layout); + placeholder_len = strlen (data->placeholder_str); + for (p = text; (p = strstr (p, data->placeholder_str)); p += placeholder_len) { + PangoAttribute *attr; + + attr = pango_attr_shape_new_with_data (&ink_rect, + &logical_rect, + GUINT_TO_POINTER (g_utf8_get_char (p)), + NULL, NULL); + + attr->start_index = p - text; + attr->end_index = attr->start_index + placeholder_len; + + pango_attr_list_insert (attrs, attr); + } + + return attrs; +} + +static gboolean +query_unlock_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tooltip, + GtkTooltip *tooltip) +{ + GtkWidget *label; + PangoLayout *layout; + PangoAttrList *attrs; + IconShapeData *data; + + data = g_object_get_data (G_OBJECT (widget), "icon-shape-data"); + label = g_object_get_data (G_OBJECT (widget), "tooltip-label"); + if (label == NULL) { + label = gtk_label_new (data->text); + g_object_ref_sink (label); + g_object_set_data_full (G_OBJECT (widget), + "tooltip-label", label, g_object_unref); + } + + layout = gtk_label_get_layout (GTK_LABEL (label)); + pango_cairo_context_set_shape_renderer (pango_layout_get_context (layout), + icon_shape_renderer, + data, NULL); + + attrs = create_shape_attr_list_for_layout (layout, data); + gtk_label_set_attributes (GTK_LABEL (label), attrs); + pango_attr_list_unref (attrs); + + gtk_tooltip_set_custom (tooltip, label); + + return TRUE; +} + +void +setup_tooltip_with_embedded_icon (GtkWidget *widget, + const gchar *text, + const gchar *placeholder, + GIcon *icon) +{ + IconShapeData *data; + + data = g_object_get_data (G_OBJECT (widget), "icon-shape-data"); + if (data) { + gtk_widget_set_has_tooltip (widget, FALSE); + g_signal_handler_disconnect (widget, data->query_id); + g_object_set_data (G_OBJECT (widget), "icon-shape-data", NULL); + g_object_set_data (G_OBJECT (widget), "tooltip-label", NULL); + } + + if (!placeholder) { + gtk_widget_set_tooltip_text (widget, text); + return; + } + + data = icon_shape_data_new (text, placeholder, icon); + g_object_set_data_full (G_OBJECT (widget), + "icon-shape-data", + data, + icon_shape_data_free); + + gtk_widget_set_has_tooltip (widget, TRUE); + data->query_id = g_signal_connect (widget, "query-tooltip", + G_CALLBACK (query_unlock_tooltip), NULL); + +} + +gboolean +show_tooltip_now (GtkWidget *widget, + GdkEvent *event) +{ + GtkSettings *settings; + gint timeout; + + settings = gtk_widget_get_settings (widget); + + g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL); + g_object_set (settings, "gtk-tooltip-timeout", 1, NULL); + gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (widget)); + g_object_set (settings, "gtk-tooltip-timeout", timeout, NULL); + + return FALSE; +} + +static gboolean +query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data) +{ + gchar *tip; + + if (GTK_ENTRY_ICON_SECONDARY == gtk_entry_get_icon_at_pos (GTK_ENTRY (widget), x, y)) { + tip = gtk_entry_get_icon_tooltip_text (GTK_ENTRY (widget), + GTK_ENTRY_ICON_SECONDARY); + gtk_tooltip_set_text (tooltip, tip); + g_free (tip); + + return TRUE; + } + else { + return FALSE; + } +} + +static void +icon_released (GtkEntry *entry) +{ + GtkSettings *settings; + gint timeout; + + settings = gtk_widget_get_settings (GTK_WIDGET (entry)); + + g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL); + g_object_set (settings, "gtk-tooltip-timeout", 1, NULL); + gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (GTK_WIDGET (entry))); + g_object_set (settings, "gtk-tooltip-timeout", timeout, NULL); +} + + + +void +set_entry_validation_error (GtkEntry *entry, + const gchar *text) +{ + g_object_set (entry, "caps-lock-warning", FALSE, NULL); + gtk_entry_set_icon_from_icon_name (entry, + GTK_ENTRY_ICON_SECONDARY, + "dialog-warning-symbolic"); + gtk_entry_set_icon_activatable (entry, + GTK_ENTRY_ICON_SECONDARY, + TRUE); + g_signal_connect (entry, "icon-release", + G_CALLBACK (icon_released), NULL); + g_signal_connect (entry, "query-tooltip", + G_CALLBACK (query_tooltip), NULL); + g_object_set (entry, "has-tooltip", TRUE, NULL); + gtk_entry_set_icon_tooltip_text (entry, + GTK_ENTRY_ICON_SECONDARY, + text); +} + +void +set_entry_generation_icon (GtkEntry *entry) +{ + g_object_set (entry, "caps-lock-warning", FALSE, NULL); + gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, "system-run-symbolic"); + gtk_entry_set_icon_activatable (entry, GTK_ENTRY_ICON_SECONDARY, TRUE); +} + +void +set_entry_validation_checkmark (GtkEntry *entry) +{ + g_object_set (entry, "caps-lock-warning", FALSE, NULL); + gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, "object-select-symbolic"); + gtk_entry_set_icon_activatable (entry, GTK_ENTRY_ICON_SECONDARY, FALSE); +} + +void +clear_entry_validation_error (GtkEntry *entry) +{ + gboolean warning; + + g_object_get (entry, "caps-lock-warning", &warning, NULL); + + if (warning) + return; + + g_object_set (entry, "has-tooltip", FALSE, NULL); + gtk_entry_set_icon_from_pixbuf (entry, + GTK_ENTRY_ICON_SECONDARY, + NULL); + g_object_set (entry, "caps-lock-warning", TRUE, NULL); +} + +/* Taken from defines.h in shadow-utils. On Linux, this value is much smaller + * than the sysconf limit LOGIN_NAME_MAX, and values larger than this will + * result in failure when running useradd. We could check UT_NAMESIZE instead, + * but that is nonstandard. Better to use POSIX utmpx. + */ +gsize +get_username_max_length (void) +{ + return sizeof (((struct utmpx *)NULL)->ut_user); +} + +gboolean +is_username_used (const gchar *username) +{ + struct passwd *pwent; + + if (username == NULL || username[0] == '\0') { + return FALSE; + } + + pwent = getpwnam (username); + + return pwent != NULL; +} + +gboolean +is_valid_name (const gchar *name) +{ + gboolean is_empty = TRUE; + const gchar *c; + + /* Valid names must contain: + * 1) at least one character. + * 2) at least one non-"space" character. + */ + for (c = name; *c; c++) { + gunichar unichar; + + unichar = g_utf8_get_char_validated (c, -1); + + /* Partial UTF-8 sequence or end of string */ + if (unichar == (gunichar) -1 || unichar == (gunichar) -2) + break; + + /* Check for non-space character */ + if (!g_unichar_isspace (unichar)) { + is_empty = FALSE; + break; + } + } + + return !is_empty; +} + +typedef struct { + gchar *username; + gchar *tip; +} isValidUsernameData; + +static void +is_valid_username_data_free (isValidUsernameData *data) +{ + g_free (data->username); + g_free (data->tip); + g_free (data); +} + +#ifdef __FreeBSD__ +/* Taken from pw(8) man page. */ +#define E_SUCCESS EX_OK +#define E_BAD_ARG EX_DATAERR +#define E_NOTFOUND EX_NOUSER +#else +/* Taken from usermod.c in shadow-utils. */ +#define E_SUCCESS 0 +#define E_BAD_ARG 3 +#define E_NOTFOUND 6 +#endif + +static void +is_valid_username_child_watch_cb (GPid pid, + gint status, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + isValidUsernameData *data = g_task_get_task_data (task); + GError *error = NULL; + gboolean valid = FALSE; + const gchar *tip = NULL; + + if (WIFEXITED (status)) { + switch (WEXITSTATUS (status)) { + case E_NOTFOUND: + valid = TRUE; + break; + case E_BAD_ARG: + tip = _("The username should usually only consist of lower case letters from a-z, digits and the following characters: - _"); + valid = FALSE; + break; + case E_SUCCESS: + tip = _("Sorry, that user name isn’t available. Please try another."); + valid = FALSE; + break; + } + } + + if (valid || tip != NULL) { + data->tip = g_strdup (tip); + g_task_return_boolean (task, valid); + } + else { + g_spawn_check_exit_status (status, &error); + g_task_return_error (task, error); + } + + g_spawn_close_pid (pid); + g_object_unref (task); +} + +void +is_valid_username_async (const gchar *username, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data) +{ + GTask *task; + isValidUsernameData *data; + gchar *argv[6]; + GPid pid; + GError *error = NULL; + + task = g_task_new (NULL, cancellable, callback, callback_data); + g_task_set_source_tag (task, is_valid_username_async); + + data = g_new0 (isValidUsernameData, 1); + data->username = g_strdup (username); + g_task_set_task_data (task, data, (GDestroyNotify) is_valid_username_data_free); + + if (username == NULL || username[0] == '\0') { + g_task_return_boolean (task, FALSE); + g_object_unref (task); + + return; + } + else if (strlen (username) > get_username_max_length ()) { + data->tip = g_strdup (_("The username is too long.")); + g_task_return_boolean (task, FALSE); + g_object_unref (task); + + return; + } + +#ifdef __FreeBSD__ + /* Abuse "pw usershow -n <name>" in the same way as the code below. We + * don't use "pw usermod -n <name> -N -l <newname>" here because it has + * a special case for "root" to reject changes to the root user. + */ + argv[0] = "pw"; + argv[1] = "usershow"; + argv[2] = "-n"; + argv[3] = data->username; + argv[4] = NULL; +#else + /* "usermod --login" is meant to be used to change a username, but the + * exit codes can be safely abused to check the validity of username. + * However, the current "usermod" implementation may change in the + * future, so it would be nice to have some official way for this + * instead of relying on the current "--login" implementation. + */ + argv[0] = "/usr/sbin/usermod"; + argv[1] = "--login"; + argv[2] = data->username; + argv[3] = "--"; + argv[4] = data->username; + argv[5] = NULL; +#endif + + if (!g_spawn_async (NULL, argv, NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD | + G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, &pid, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + + return; + } + + g_child_watch_add (pid, (GChildWatchFunc) is_valid_username_child_watch_cb, task); +} + +gboolean +is_valid_username_finish (GAsyncResult *result, + gchar **tip, + gchar **username, + GError **error) +{ + GTask *task; + isValidUsernameData *data; + + g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE); + + task = G_TASK (result); + data = g_task_get_task_data (task); + + if (tip != NULL) { + *tip = g_steal_pointer (&data->tip); + if (*tip == NULL) + *tip = g_strdup (_("This will be used to name your home folder and can’t be changed.")); + } + + if (username != NULL) + *username = g_steal_pointer (&data->username); + + return g_task_propagate_boolean (task, error); +} + +GdkPixbuf * +round_image (GdkPixbuf *pixbuf) +{ + GdkPixbuf *dest = NULL; + cairo_surface_t *surface; + cairo_t *cr; + gint size; + + size = gdk_pixbuf_get_width (pixbuf); + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size); + cr = cairo_create (surface); + + /* Clip a circle */ + cairo_arc (cr, size/2, size/2, size/2, 0, 2 * G_PI); + cairo_clip (cr); + cairo_new_path (cr); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_paint (cr); + + dest = gdk_pixbuf_get_from_surface (surface, 0, 0, size, size); + cairo_surface_destroy (surface); + cairo_destroy (cr); + + return dest; +} + +static gchar * +extract_initials_from_name (const gchar *name) +{ + GString *initials; + g_autofree gchar *p = NULL; + g_autofree gchar *normalized = NULL; + gunichar unichar; + gpointer q = NULL; + + g_return_val_if_fail (name != NULL, NULL); + + p = g_utf8_strup (name, -1); + normalized = g_utf8_normalize (g_strstrip (p), -1, G_NORMALIZE_DEFAULT_COMPOSE); + if (normalized == NULL) { + return NULL; + } + + initials = g_string_new (""); + + unichar = g_utf8_get_char (normalized); + g_string_append_unichar (initials, unichar); + + q = g_utf8_strrchr (normalized, -1, ' '); + if (q != NULL && g_utf8_next_char (q) != NULL) { + q = g_utf8_next_char (q); + + unichar = g_utf8_get_char (q); + g_string_append_unichar (initials, unichar); + } + + return g_string_free (initials, FALSE); +} + +static GdkRGBA +get_color_for_name (const gchar *name) +{ + // https://gitlab.gnome.org/Community/Design/HIG-app-icons/blob/master/GNOME%20HIG.gpl + static gdouble gnome_color_palette[][3] = { + { 98, 160, 234 }, + { 53, 132, 228 }, + { 28, 113, 216 }, + { 26, 95, 180 }, + { 87, 227, 137 }, + { 51, 209, 122 }, + { 46, 194, 126 }, + { 38, 162, 105 }, + { 248, 228, 92 }, + { 246, 211, 45 }, + { 245, 194, 17 }, + { 229, 165, 10 }, + { 255, 163, 72 }, + { 255, 120, 0 }, + { 230, 97, 0 }, + { 198, 70, 0 }, + { 237, 51, 59 }, + { 224, 27, 36 }, + { 192, 28, 40 }, + { 165, 29, 45 }, + { 192, 97, 203 }, + { 163, 71, 186 }, + { 129, 61, 156 }, + { 97, 53, 131 }, + { 181, 131, 90 }, + { 152, 106, 68 }, + { 134, 94, 60 }, + { 99, 69, 44 } + }; + + GdkRGBA color = { 255, 255, 255, 1.0 }; + guint hash; + gint number_of_colors; + gint idx; + + if (name == NULL || strlen (name) == 0) + return color; + + hash = g_str_hash (name); + number_of_colors = G_N_ELEMENTS (gnome_color_palette); + idx = hash % number_of_colors; + + color.red = gnome_color_palette[idx][0]; + color.green = gnome_color_palette[idx][1]; + color.blue = gnome_color_palette[idx][2]; + + return color; +} + +static cairo_surface_t * +generate_user_picture (const gchar *name, gint size) +{ + PangoFontDescription *font_desc; + g_autofree gchar *initials = extract_initials_from_name (name); + g_autofree gchar *font = g_strdup_printf ("Sans %d", (int)ceil (size / 2.5)); + PangoLayout *layout; + GdkRGBA color = get_color_for_name (name); + cairo_surface_t *surface; + gint width, height; + cairo_t *cr; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + size, + size); + cr = cairo_create (surface); + cairo_rectangle (cr, 0, 0, size, size); + cairo_set_source_rgb (cr, color.red/255.0, color.green/255.0, color.blue/255.0); + cairo_fill (cr); + + /* Draw the initials on top */ + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + layout = pango_cairo_create_layout (cr); + pango_layout_set_text (layout, initials, -1); + font_desc = pango_font_description_from_string (font); + pango_layout_set_font_description (layout, font_desc); + pango_font_description_free (font_desc); + + pango_layout_get_size (layout, &width, &height); + cairo_translate (cr, size/2, size/2); + cairo_move_to (cr, - ((double)width / PANGO_SCALE)/2, - ((double)height/PANGO_SCALE)/2); + pango_cairo_show_layout (cr, layout); + cairo_destroy (cr); + + return surface; +} + +void +set_user_icon_data (ActUser *user, + GdkPixbuf *pixbuf) +{ + gchar *path; + gint fd; + GOutputStream *stream; + GError *error; + + path = g_build_filename (g_get_tmp_dir (), "gnome-control-center-user-icon-XXXXXX", NULL); + fd = g_mkstemp (path); + + if (fd == -1) { + g_warning ("failed to create temporary file for image data"); + g_free (path); + return; + } + + stream = g_unix_output_stream_new (fd, TRUE); + + error = NULL; + if (!gdk_pixbuf_save_to_stream (pixbuf, stream, "png", NULL, &error, NULL)) { + g_warning ("failed to save image: %s", error->message); + g_error_free (error); + g_object_unref (stream); + return; + } + + g_object_unref (stream); + + act_user_set_icon_file (user, path); + + /* if we ever make the dbus call async, the g_remove call needs + * to wait for its completion + */ + g_remove (path); + + g_free (path); +} + +GdkPixbuf * +generate_default_avatar (ActUser *user, gint size) +{ + const gchar *name; + GdkPixbuf *pixbuf = NULL; + cairo_surface_t *surface; + + name = act_user_get_real_name (user); + if (name == NULL) + name = ""; + surface = generate_user_picture (name, size); + + pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, size, size); + cairo_surface_destroy (surface); + + return pixbuf; +} + +void +set_default_avatar (ActUser *user) +{ + g_autoptr(GdkPixbuf) pixbuf = NULL; + + pixbuf = generate_default_avatar (user, IMAGE_SIZE); + + set_user_icon_data (user, pixbuf); +} diff --git a/panels/user-accounts/user-utils.h b/panels/user-accounts/user-utils.h new file mode 100644 index 0000000..09c6cdd --- /dev/null +++ b/panels/user-accounts/user-utils.h @@ -0,0 +1,59 @@ +/* -*- 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, see <http://www.gnu.org/licenses/>. + * + * Written by: Matthias Clasen <mclasen@redhat.com> + */ + +#pragma once + +#include <gtk/gtk.h> +#include <act/act.h> + +G_BEGIN_DECLS + +void setup_tooltip_with_embedded_icon (GtkWidget *widget, + const gchar *text, + const gchar *placeholder, + GIcon *icon); +gboolean show_tooltip_now (GtkWidget *widget, + GdkEvent *event); + +void set_entry_generation_icon (GtkEntry *entry); +void set_entry_validation_checkmark (GtkEntry *entry); +void set_entry_validation_error (GtkEntry *entry, + const gchar *text); +void clear_entry_validation_error (GtkEntry *entry); + +gsize get_username_max_length (void); +gboolean is_username_used (const gchar *username); +gboolean is_valid_name (const gchar *name); +void is_valid_username_async (const gchar *username, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data); +gboolean is_valid_username_finish (GAsyncResult *result, + gchar **tip, + gchar **username, + GError **error); +GdkPixbuf *round_image (GdkPixbuf *pixbuf); +GdkPixbuf *generate_default_avatar (ActUser *user, + gint size); +void set_default_avatar (ActUser *user); +void set_user_icon_data (ActUser *user, + GdkPixbuf *pixbuf); + +G_END_DECLS |