/* -*- 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-list-row.h"
#include "cc-add-user-dialog.h"
#include "cc-avatar-chooser.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-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"

#define USER_ACCOUNTS_PERMISSION "org.gnome.controlcenter.user-accounts.administration"

struct _CcUserPanel {
        CcPanel parent_instance;

        ActUserManager *um;
        GSettings *login_screen_settings;

        GtkBox          *account_settings_box;
        GtkListBoxRow   *account_type_row;
        GtkSwitch       *account_type_switch;
        GtkWidget       *add_user_button;
        GtkListBoxRow   *autologin_row;
        GtkSwitch       *autologin_switch;
        GtkButton       *back_button;
        CcListRow       *fingerprint_row;
        GtkStack        *full_name_stack;
        GtkLabel        *full_name_label;
        GtkToggleButton *full_name_edit_button;
        GtkEntry        *full_name_entry;
        CcListRow       *language_row;
        CcListRow       *last_login_row;
        GtkWidget       *no_users_box;
        GtkRevealer     *notification_revealer;
        AdwPreferencesGroup *other_users;
        GtkListBox      *other_users_listbox;
        GtkLabel        *password_button_label;
#ifdef HAVE_MALCONTENT
        GtkLabel        *parental_controls_button_label;
        GtkListBoxRow   *parental_controls_row;
#endif
        CcListRow           *password_row;
        CcPermissionInfobar *permission_infobar;
        GtkButton       *remove_user_button;
        GtkStack        *stack;
        AdwAvatar       *user_avatar;
        GtkMenuButton   *user_avatar_edit_button;
        GtkOverlay      *users_overlay;

        ActUser *selected_user;
        ActUser *pending_show_user;
        GPermission *permission;
        CcLanguageChooser *language_chooser;
        GListStore *other_users_model;
        GtkFlattenListModel *other_users_flat_model;

        CcAvatarChooser *avatar_chooser;

        CcFingerprintManager *fingerprint_manager;
};

CC_PANEL_REGISTER (CcUserPanel, cc_user_panel)

static void show_restart_notification (CcUserPanel *self, const gchar *locale);

typedef struct {
        CcUserPanel *self;
        GCancellable *cancellable;
        gchar *login;
} AsyncDeleteData;

static void
async_delete_data_free (AsyncDeleteData *data)
{
        g_clear_object (&data->self);
        g_clear_object (&data->cancellable);
        g_clear_pointer (&data->login, g_free);
        g_slice_free (AsyncDeleteData, data);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (AsyncDeleteData, async_delete_data_free)

static void
show_error_dialog (CcUserPanel *self,
                   const gchar *message,
                   GError *error)
{
        GtkWidget *dialog;

        dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_native (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_window_destroy), NULL);
        gtk_window_present (GTK_WINDOW (dialog));
}

static void show_user (ActUser *user, CcUserPanel *self);

static ActUser *
get_selected_user (CcUserPanel *self)
{
        return self->selected_user;
}

static void
set_selected_user (CcUserPanel  *self,
                   AdwActionRow *row)
{
        uid_t uid;

        /* The user clicked "Add User…" row */
        if (!g_object_get_data (G_OBJECT (row), "uid"))
                return;

        uid = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "uid"));
        g_set_object (&self->selected_user,
                      act_user_manager_get_user_by_id (self->um, uid));
        show_user (self->selected_user, self);
}

static void
show_current_user (CcUserPanel *self)
{
        ActUser *user;

        user = act_user_manager_get_user_by_id (self->um, getuid ()); 
        if (user != NULL)
            show_user (user, self);
}


static void
on_back_button_clicked_cb (CcUserPanel *self)
{

        if (act_user_get_uid (self->selected_user) == getuid ()) {
                gtk_widget_activate_action (GTK_WIDGET (self),
                                            "window.navigate",
                                            "i",
                                            ADW_NAVIGATION_DIRECTION_BACK);
        } else {
                show_current_user (self);
        }
}

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
setup_avatar_for_user (AdwAvatar *avatar, ActUser *user)
{
        const gchar *avatar_file;

        adw_avatar_set_custom_image (avatar, NULL);
        adw_avatar_set_text (avatar, get_real_or_user_name (user));

        avatar_file = act_user_get_icon_file (user);
        if (avatar_file) {
                g_autoptr(GdkPixbuf) pixbuf = NULL;

                pixbuf = gdk_pixbuf_new_from_file_at_size (avatar_file,
                                                           adw_avatar_get_size (avatar),
                                                           adw_avatar_get_size (avatar),
                                                           NULL);
                if (pixbuf) {
                        g_autoptr(GdkTexture) texture = NULL;

                        texture = gdk_texture_new_for_pixbuf (pixbuf);
                        adw_avatar_set_custom_image (avatar, GDK_PAINTABLE (texture));
                }
        }
}

static void add_user (CcUserPanel *self);

static GtkWidget *
create_user_row (gpointer item,
                 gpointer user_data)
{
        CcUserPanel *self = user_data;
        ActUser *user;
        GtkWidget *row, *user_image;

        row = adw_action_row_new ();
        gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);

        if (GTK_IS_STRING_OBJECT (item)) {
                g_assert (!self->add_user_button);
                self->add_user_button = row;

                adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row),
                                               _("Add User…"));
                adw_action_row_set_icon_name (ADW_ACTION_ROW (row), "list-add-symbolic");
                g_signal_connect_object (row, "activated",
                                         G_CALLBACK (add_user),
                                         self, G_CONNECT_SWAPPED);
                return row;
        }

        user = item;
        g_object_set_data (G_OBJECT (row), "uid", GINT_TO_POINTER (act_user_get_uid (user)));
        adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row),
                                       get_real_or_user_name (user));
        user_image = adw_avatar_new (32, NULL, TRUE);
        setup_avatar_for_user (ADW_AVATAR (user_image), user);
        adw_action_row_add_prefix (ADW_ACTION_ROW (row), user_image);

        return row;
}

static gint
sort_users (gconstpointer a, gconstpointer b, gpointer user_data)
{
        ActUser *ua, *ub;

        ua = ACT_USER (a);
        ub = ACT_USER (b);

        /* Make sure the current user is shown first */
        if (act_user_get_uid (ua) == getuid ()) {
                return -G_MAXINT32;
        }
        else if (act_user_get_uid (ub) == getuid ()) {
                return G_MAXINT32;
        }
        else {
                g_autofree gchar *name1 = NULL;
                g_autofree gchar *name2 = NULL;

                name1 = g_utf8_collate_key (get_real_or_user_name (ua), -1);
                name2 = g_utf8_collate_key (get_real_or_user_name (ub), -1);

                return strcmp (name1, name2);
        }
}

static void
user_changed (CcUserPanel *self, ActUser *user)
{
        g_autoptr(GSList) user_list = NULL;
        GSList *l;

        g_list_store_remove_all (self->other_users_model);
        user_list = act_user_manager_list_users (self->um);
        for (l = user_list; l; l = l->next) {
                ActUser *other_user = ACT_USER (l->data);

                if (act_user_is_system_account (other_user)) {
                        continue;
                }

                if (act_user_get_uid (other_user) == getuid ()) {
                        continue;
                }

                g_list_store_insert_sorted (self->other_users_model,
                                            other_user,
                                            sort_users,
                                            self);
        }

        if (self->selected_user == user)
                show_user (user, self);
}

static void
on_add_user_dialog_response (CcUserPanel     *self,
                             gint             response,
                             CcAddUserDialog *dialog)
{
        ActUser *user;

        user = cc_add_user_dialog_get_user (dialog);
        if (user != NULL) {
                set_default_avatar (user);
                show_user (user, self);
        }

        gtk_window_destroy (GTK_WINDOW (dialog));
}

static void
add_user (CcUserPanel *self)
{
        CcAddUserDialog *dialog;
        GtkWindow *toplevel;

        dialog = cc_add_user_dialog_new (self->permission);
        toplevel = GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self)));
        gtk_window_set_transient_for (GTK_WINDOW (dialog), toplevel);

        gtk_window_present (GTK_WINDOW (dialog));
        g_signal_connect_object (dialog, "response", G_CALLBACK (on_add_user_dialog_response),
                                 self, G_CONNECT_SWAPPED);
}

static void
delete_user_done (ActUserManager *manager,
                  GAsyncResult   *res,
                  CcUserPanel    *self)
{
        g_autoptr(GError) 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);
        }

        show_current_user (self);
}

static void
delete_user_response (CcUserPanel *self,
                      gint         response_id,
                      GtkWidget   *dialog)
{
        ActUser *user;
        gboolean remove_files;

        gtk_window_destroy (GTK_WINDOW (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)
{
        g_autoptr(AsyncDeleteData) data = user_data;
        CcUserPanel *self = data->self;
        CcRealmCommon *common = CC_REALM_COMMON (source);
        g_autoptr(GError) error = NULL;

        if (g_cancellable_is_cancelled (data->cancellable)) {
                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);
        }
}

static CcRealmCommon *
find_matching_realm (CcRealmManager *realm_manager, const gchar *login)
{
        CcRealmCommon *common = NULL;
        GList *realms;

        realms = cc_realm_manager_get_realms (realm_manager);
        for (GList *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)
{
        g_autoptr(AsyncDeleteData) data = user_data;
        CcUserPanel *self = data->self;
        g_autoptr(CcRealmCommon) common = NULL;
        CcRealmManager *realm_manager;
        const gchar *add[1];
        const gchar *remove[2];
        GVariant *options;
        g_autoptr(GError) error = NULL;

        if (g_cancellable_is_cancelled (data->cancellable)) {
                return;
        }

        realm_manager = cc_realm_manager_new_finish (result, &error);
        if (error != NULL) {
                show_error_dialog (self, _("Failed to revoke remotely managed user"), error);
                return;
        }

        /* Find matching realm */
        common = find_matching_realm (realm_manager, data->login);
        if (common == NULL) {
                /* The realm was probably left */
                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,
                                                  g_steal_pointer (&data));
}

static void
enterprise_user_uncached (GObject           *source,
                          GAsyncResult      *res,
                          gpointer           user_data)
{
        g_autoptr(AsyncDeleteData) data = user_data;
        CcUserPanel *self = data->self;
        ActUserManager *manager = ACT_USER_MANAGER (source);
        g_autoptr(GError) error = NULL;

        if (g_cancellable_is_cancelled (data->cancellable)) {
                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, g_steal_pointer (&data));
        }
        else {
                show_error_dialog (self, _("Failed to revoke remotely managed user"), error);
        }
}

static void
delete_enterprise_user_response (CcUserPanel *self,
                                 gint         response_id,
                                 GtkWidget   *dialog)
{
        AsyncDeleteData *data;
        ActUser *user;

        gtk_window_destroy (GTK_WINDOW (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_native (GTK_WIDGET (self))),
                                                 0,
                                                 GTK_MESSAGE_INFO,
                                                 GTK_BUTTONS_CLOSE,
                                                 _("You cannot delete your own account."));
                g_signal_connect (dialog, "response",
                                  G_CALLBACK (gtk_window_destroy), NULL);
        }
        else if (act_user_is_logged_in_anywhere (user)) {
                dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_native (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_window_destroy), NULL);
        }
        else if (act_user_is_local_account (user)) {
                dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_native (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_native (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_window_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)
{
        gint64 time;

        time = act_user_get_login_time (user);
        if (act_user_is_logged_in (user)) {
                return g_strdup (_("Logged in"));
        }
        else if (time > 0) {
                g_autoptr(GDateTime) date_time = NULL;
                g_autofree gchar *date_str = NULL;
                g_autofree gchar *time_str = NULL;

                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. */
                return g_strdup_printf(C_("login date-time", "%s, %s"), date_str, time_str);
        }
        else {
                return g_strdup ("—");
        }
}

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)
                cc_list_row_set_secondary_label (self->fingerprint_row, _("Enabled"));
        else if (state == CC_FINGERPRINT_STATE_DISABLED)
                cc_list_row_set_secondary_label (self->fingerprint_row, _("Disabled"));
}

static void
show_or_hide_back_button (CcUserPanel *self)
{
        gboolean show;
        gboolean folded;

        g_object_get(self, "folded", &folded, NULL);

        show = folded || act_user_get_uid (self->selected_user) != getuid();

        gtk_widget_set_visible (GTK_WIDGET (self->back_button), show);
}

static void
on_pending_show_user_is_loaded (ActUser     *user,
                                GParamSpec  *param,
                                CcUserPanel *self)
{
        if (!act_user_is_loaded (user)) {
                return;
        }

        show_user (user, self);
}

static void
show_user (ActUser *user, CcUserPanel *self)
{
        g_autofree gchar *lang = NULL;
        g_autofree gchar *name = NULL;
        gboolean show, enable;
        ActUser *current;
#ifdef HAVE_MALCONTENT
        g_autofree gchar *malcontent_control_path = NULL;
#endif

        if (self->pending_show_user != NULL) {
                g_signal_handlers_disconnect_by_func (G_OBJECT (self->pending_show_user),
                                                      on_pending_show_user_is_loaded,
                                                      self);
                g_clear_object (&self->pending_show_user);
        }

        if (!act_user_is_loaded (user)) {
                g_set_object (&self->pending_show_user, user);
                g_signal_connect_object (G_OBJECT (self->pending_show_user),
                                         "notify::is-loaded",
                                         G_CALLBACK (on_pending_show_user_is_loaded),
                                         self,
                                         0);
                return;
        }

        g_set_object (&self->selected_user, user);

        setup_avatar_for_user (self->user_avatar, user);
        cc_avatar_chooser_set_user (self->avatar_chooser, user);

        gtk_label_set_label (self->full_name_label, get_real_or_user_name (user));
        gtk_editable_set_text (GTK_EDITABLE (self->full_name_entry), gtk_label_get_label (self->full_name_label));
        gtk_widget_set_tooltip_text (GTK_WIDGET (self->full_name_label), get_real_or_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);

        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));

        lang = g_strdup (act_user_get_language (user));
        if (lang && *lang != '\0') {
                name = gnome_get_language_from_locale (lang, NULL);
        } else {
                name = g_strdup ("—");
        }
        cc_list_row_set_secondary_label (self->language_row, 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 or if
         * malcontent-control is not available (which can happen if
         * libmalcontent is installed but malcontent-control is not). */
        malcontent_control_path = g_find_program_in_path ("malcontent-control");

        if (act_user_get_account_type (user) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR ||
            malcontent_control_path == NULL) {
                gtk_widget_hide (GTK_WIDGET (self->parental_controls_row));
        } 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_remove_css_class (GTK_WIDGET (self->parental_controls_button_label), "dim-label");
                gtk_widget_show (GTK_WIDGET (self->parental_controls_row));
        }
#endif

        /* Current user */
        show = act_user_get_uid (user) == getuid();
        gtk_widget_set_visible (GTK_WIDGET (self->account_settings_box), !show);
        gtk_widget_set_visible (GTK_WIDGET (self->remove_user_button), !show);
        gtk_widget_set_visible (GTK_WIDGET (self->back_button), !show);
        show_or_hide_back_button(self);
        gtk_widget_set_visible (GTK_WIDGET (self->other_users), 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) {
                g_autofree gchar *text = NULL;

                text = get_login_time_text (user);
                cc_list_row_set_secondary_label (self->last_login_row, 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_editable_get_text (GTK_EDITABLE (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 (GtkEventController *controller,
                              guint               keyval,
                              guint               keycode,
                              GdkModifierType     state,
                              CcUserPanel        *self)
{
        if (keyval == GDK_KEY_Escape) {
                gtk_editable_set_text (GTK_EDITABLE (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)
{
        g_autoptr(GDBusConnection) bus = NULL;

        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);
}

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);
                cc_list_row_set_secondary_label (self->language_row, 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_native (GTK_WIDGET (self))));

                g_signal_connect_object (self->language_chooser, "response",
                                         G_CALLBACK (language_response), self, G_CONNECT_SWAPPED);
        }

        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 = (GtkWindow *) gtk_widget_get_native (GTK_WIDGET (self));
        gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);

        gtk_window_present (GTK_WINDOW (dialog));
}

static void
change_fingerprint (CcUserPanel *self)
{
        ActUser *user;
        GtkWindow *parent;
        CcFingerprintDialog *dialog;

        user = get_selected_user (self);
        parent = (GtkWindow *) gtk_widget_get_native (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), parent);
        gtk_window_present (GTK_WINDOW (dialog));
}

static void
show_history (CcUserPanel *self)
{
        CcLoginHistoryDialog *dialog;
        ActUser *user;
        GtkWindow *parent;

        user = get_selected_user (self);
        dialog = cc_login_history_dialog_new (user);

        parent = (GtkWindow *) gtk_widget_get_native (GTK_WIDGET (self));
        gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);

        gtk_window_present (GTK_WINDOW (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",
#ifdef HAVE_MALCONTENT_0_10
                        "--user",
                        act_user_get_user_name (user),
#endif  /* HAVE_MALCONTENT_0_10 */
                        NULL
                };
                g_autoptr(GError) error = NULL;
                if (!g_spawn_async (NULL, (char **)argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error))
                        g_debug ("Couldn't launch malcontent-control: %s", error->message);
        } else {
                g_debug ("Not launching malcontent because selected user is an admin");
        }
}
#endif

static void
users_loaded (CcUserPanel *self)
{
        GtkWidget *dialog;

        if (act_user_manager_no_service (self->um)) {
                GtkWidget *toplevel;

                toplevel = (GtkWidget *)gtk_widget_get_native (GTK_WIDGET (self));
                dialog = gtk_message_dialog_new (GTK_WINDOW (toplevel ),
                                                 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_window_destroy),
                                  NULL);
                gtk_widget_show (dialog);

                gtk_stack_set_visible_child (self->stack, self->no_users_box);
        } else {
                gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->users_overlay));
                show_current_user (self);
        }

        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_changed), self, G_CONNECT_SWAPPED);
        g_signal_connect_object (self->um, "user-removed", G_CALLBACK (user_changed), self, G_CONNECT_SWAPPED);
}

static void
add_unlock_tooltip (GtkWidget *widget)
{
        gtk_widget_set_tooltip_text (widget,
                                     _("This panel must be unlocked to change this setting"));
}

static void
remove_unlock_tooltip (GtkWidget *widget)
{
        gtk_widget_set_tooltip_text (widget, 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_sensitive (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) {
                gtk_widget_set_tooltip_text (GTK_WIDGET (self->remove_user_button), _("Delete the selected user account"));
        }
        else {
                gtk_widget_set_tooltip_text (GTK_WIDGET (self->remove_user_button),
                                             _("To delete the selected user account,\nclick the * icon first"));
        }

        if (!act_user_is_local_account (user)) {
                gtk_widget_set_visible (GTK_WIDGET (self->account_type_row), FALSE);
                gtk_widget_set_visible (GTK_WIDGET (self->autologin_row), FALSE);
        } else if (is_authorized && act_user_is_local_account (user)) {
                if (would_demote_only_admin (user)) {
                        gtk_widget_set_visible (GTK_WIDGET (self->account_type_row), FALSE);
                } else {
                        gtk_widget_set_visible (GTK_WIDGET (self->account_type_row), TRUE);
                }

                if (get_autologin_possible (user)) {
                        gtk_widget_set_visible (GTK_WIDGET (self->autologin_row), TRUE);
                        gtk_widget_set_sensitive (GTK_WIDGET (self->autologin_row), TRUE);

                        remove_unlock_tooltip (GTK_WIDGET (self->autologin_row));
                }
        }
        else {
                gtk_widget_set_visible (GTK_WIDGET (self->account_type_row), FALSE);
                if (would_demote_only_admin (user)) {
                        gtk_widget_set_visible (GTK_WIDGET (self->account_type_row), FALSE);
                } else {
                        gtk_widget_set_visible (GTK_WIDGET (self->account_type_row), TRUE);
                }
                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_widget_set_sensitive (GTK_WIDGET (self->user_avatar_edit_button), TRUE);
                remove_unlock_tooltip (GTK_WIDGET (self->user_avatar_edit_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));
        }
        else {
                gtk_widget_set_sensitive (GTK_WIDGET (self->user_avatar_edit_button), FALSE);
                add_unlock_tooltip (GTK_WIDGET (self->user_avatar_edit_button));

                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));
        }
}

/*
 * This is a workaround to add an additional item to the end
 * of the users list, which will be used to show "Add User…"
 * row.  We do this way instead of appending a row to avoid
 * some imperfections in the GUI.  See
 * https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/1926
 */
static void
setup_other_users_list (CcUserPanel *self)
{
        g_autoptr(GListStore) add_user = NULL;
        g_autoptr(GtkStringObject) str = NULL;
        GListStore *users_store;

        users_store = g_list_store_new (G_TYPE_LIST_MODEL);
        add_user = g_list_store_new (GTK_TYPE_STRING_OBJECT);

        str = gtk_string_object_new ("add-user");
        g_list_store_append (add_user, str);

        g_list_store_append (users_store, self->other_users_model);
        g_list_store_append (users_store, add_user);

        self->other_users_flat_model = gtk_flatten_list_model_new (G_LIST_MODEL (users_store));
}

static void
setup_main_window (CcUserPanel *self)
{
        g_autoptr(GError) error = NULL;
        gboolean loaded;

        self->other_users_model = g_list_store_new (ACT_TYPE_USER);

        setup_other_users_list (self);

        gtk_list_box_bind_model (self->other_users_listbox,
                                 G_LIST_MODEL (self->other_users_flat_model),
                                 (GtkListBoxCreateWidgetFunc)create_user_row,
                                 self,
                                 NULL);

        add_unlock_tooltip (GTK_WIDGET (self->user_avatar));

        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);
        }

#ifdef HAVE_MALCONTENT
        g_signal_connect_object (self->parental_controls_row, "activated", G_CALLBACK (spawn_malcontent_control), self, G_CONNECT_SWAPPED);
#endif

        gtk_widget_set_tooltip_text (GTK_WIDGET (self->remove_user_button),
                                     _("To delete the selected user account,\nclick the * icon first"));

        self->avatar_chooser = cc_avatar_chooser_new (GTK_WIDGET (self));
        gtk_menu_button_set_popover (self->user_avatar_edit_button,
                                     GTK_WIDGET (self->avatar_chooser));

        g_object_get (self->um, "is-loaded", &loaded, NULL);
        if (loaded) {
                users_loaded (self);
                user_changed (self, NULL);
        } else {
                g_signal_connect_object (self->um, "notify::is-loaded", G_CALLBACK (users_loaded), self, G_CONNECT_SWAPPED);
        }
}

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);

        G_OBJECT_CLASS (cc_user_panel_parent_class)->constructed (object);

        cc_permission_infobar_set_permission (self->permission_infobar, self->permission);
        cc_permission_infobar_set_title (self->permission_infobar, _("Unlock to Add Users and Change Settings"));
}

static void
cc_user_panel_init (CcUserPanel *self)
{
        volatile GType type G_GNUC_UNUSED;
        g_autoptr(GtkCssProvider) provider = NULL;

        g_resources_register (cc_user_accounts_get_resource ());

        /* register types that the builder might need */
        type = cc_permission_infobar_get_type ();
        g_type_ensure (CC_TYPE_AVATAR_CHOOSER);
        g_type_ensure (CC_TYPE_LIST_ROW);

        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_display (gdk_display_get_default (),
                                                    GTK_STYLE_PROVIDER (provider),
                                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

        self->login_screen_settings = settings_or_null ("org.gnome.login-screen");

        setup_main_window (self);

        g_signal_connect_swapped (self,
                          "notify::folded",
                          G_CALLBACK (show_or_hide_back_button),
                          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->pending_show_user);
        g_clear_object (&self->login_screen_settings);
        g_clear_pointer ((GtkWindow **)&self->language_chooser, gtk_window_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, account_settings_box);
        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, autologin_row);
        gtk_widget_class_bind_template_child (widget_class, CcUserPanel, autologin_switch);
        gtk_widget_class_bind_template_child (widget_class, CcUserPanel, back_button);
        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_row);
        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);
        gtk_widget_class_bind_template_child (widget_class, CcUserPanel, other_users);
        gtk_widget_class_bind_template_child (widget_class, CcUserPanel, other_users_listbox);
#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_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_avatar);
        gtk_widget_class_bind_template_child (widget_class, CcUserPanel, user_avatar_edit_button);
        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, 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);
        gtk_widget_class_bind_template_callback (widget_class, on_back_button_clicked_cb);
        gtk_widget_class_bind_template_callback (widget_class, show_history);
}
