gnome-control-center/panels/system/users/cc-fingerprint-dialog.c
Felipe Borges d52ec68f8d system: Add "Users" panel
This moves the UserAccounts panel to a page in the System panel.

This simplifies a lot of the existing code in the UserAccounts panel.

I did minimal changes to the sub dialogs so that those can be touched
in following changes, making it easier to review this one alone.

The main panel widget is now CcUsersPage, and is an AdwNavigationView
widget that has a default "current_user_page" page. Each page is a
CcUserPage (careful with the one-character difference between these
two classes).

Each CcUserPage has an associated ActUser object.
2024-01-08 13:59:26 +01:00

1521 lines
49 KiB
C

/* -*- 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"
#define CC_FPRINTD_NAME "net.reactivated.Fprint"
/* Translate fprintd strings */
#define TR(s) dgettext ("fprintd", s)
#include "fingerprint-strings.h"
typedef enum {
DIALOG_STATE_NONE = 0,
DIALOG_STATE_DEVICES_LISTING = (1 << 0),
DIALOG_STATE_DEVICE_CLAIMING = (1 << 1),
DIALOG_STATE_DEVICE_CLAIMED = (1 << 2),
DIALOG_STATE_DEVICE_PRINTS_LISTING = (1 << 3),
DIALOG_STATE_DEVICE_RELEASING = (1 << 4),
DIALOG_STATE_DEVICE_ENROLL_STARTING = (1 << 5),
DIALOG_STATE_DEVICE_ENROLLING = (1 << 6),
DIALOG_STATE_DEVICE_ENROLL_STOPPING = (1 << 7),
DIALOG_STATE_DEVICE_DELETING = (1 << 8),
DIALOG_STATE_IDLE = DIALOG_STATE_DEVICE_CLAIMED | DIALOG_STATE_DEVICE_ENROLLING,
} DialogState;
struct _CcFingerprintDialog
{
GtkWindow parent_instance;
GtkButton *back_button;
GtkButton *cancel_button;
GtkButton *delete_prints_button;
GtkButton *done_button;
GtkBox *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;
GtkPopover *add_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;
DialogState dialog_state;
CcFprintdDevice *device;
gulong device_signal_id;
gulong device_name_owner_id;
GCancellable *cancellable;
GStrv enrolled_fingers;
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 gboolean
update_dialog_state (CcFingerprintDialog *self,
DialogState state)
{
if (self->dialog_state == state)
return FALSE;
self->dialog_state = state;
if (self->dialog_state == DIALOG_STATE_NONE ||
self->dialog_state == (self->dialog_state & DIALOG_STATE_IDLE))
{
gtk_spinner_stop (self->spinner);
}
else
{
gtk_spinner_start (self->spinner);
}
return TRUE;
}
static gboolean
add_dialog_state (CcFingerprintDialog *self,
DialogState state)
{
return update_dialog_state (self, (self->dialog_state | state));
}
static gboolean
remove_dialog_state (CcFingerprintDialog *self,
DialogState state)
{
return update_dialog_state (self, (self->dialog_state & ~state));
}
typedef struct
{
CcFingerprintDialog *dialog;
DialogState state;
} DialogStateRemover;
static DialogStateRemover *
auto_state_remover (CcFingerprintDialog *self,
DialogState state)
{
DialogStateRemover *state_remover;
state_remover = g_new0 (DialogStateRemover, 1);
state_remover->dialog = g_object_ref (self);
state_remover->state = state;
return state_remover;
}
static void
auto_state_remover_cleanup (DialogStateRemover *state_remover)
{
remove_dialog_state (state_remover->dialog, state_remover->state);
g_clear_object (&state_remover->dialog);
g_free (state_remover);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (DialogStateRemover, auto_state_remover_cleanup);
static const char *
dbus_error_to_human (CcFingerprintDialog *self,
GError *error)
{
g_autofree char *dbus_error = g_dbus_error_get_remote_error (error);
if (dbus_error == NULL)
{ /* Fallback to generic */ }
else if (g_str_equal (dbus_error, CC_FPRINTD_NAME ".Error.ClaimDevice"))
return _("the device needs to be claimed to perform this action");
else if (g_str_equal (dbus_error, CC_FPRINTD_NAME ".Error.AlreadyInUse"))
return _("the device is already claimed by another process");
else if (g_str_equal (dbus_error, CC_FPRINTD_NAME ".Error.PermissionDenied"))
return _("you do not have permission to perform the action");
else if (g_str_equal (dbus_error, CC_FPRINTD_NAME ".Error.NoEnrolledPrints"))
return _("no prints have been enrolled");
else if (g_str_equal (dbus_error, CC_FPRINTD_NAME ".Error.NoActionInProgress"))
{ /* Fallback to generic */ }
else if (g_str_equal (dbus_error, CC_FPRINTD_NAME ".Error.InvalidFingername"))
{ /* Fallback to generic */ }
else if (g_str_equal (dbus_error, CC_FPRINTD_NAME ".Error.Internal"))
{ /* Fallback to generic */ }
if (self->dialog_state & DIALOG_STATE_DEVICE_ENROLLING)
return _("Failed to communicate with the device during enrollment");
if (self->dialog_state & DIALOG_STATE_DEVICE_CLAIMED ||
self->dialog_state & DIALOG_STATE_DEVICE_CLAIMING)
return _("Failed to communicate with the fingerprint reader");
return _("Failed to communicate with the fingerprint daemon");
}
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_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 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)
{
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);
if (icon_widget_type == GTK_TYPE_IMAGE)
icon_widget = image;
else
icon_widget = g_object_new (icon_widget_type, NULL);
if (g_type_is_a (icon_widget_type, GTK_TYPE_MENU_BUTTON))
{
gtk_menu_button_set_child (GTK_MENU_BUTTON (icon_widget), image);
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_box_append (GTK_BOX (box), icon_widget);
gtk_widget_add_css_class (icon_widget, "circular");
label = gtk_label_new_with_mnemonic (label_text);
gtk_box_append (GTK_BOX (box), label);
gtk_widget_add_css_class (box, "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_flow_box_child_set_child (GTK_FLOW_BOX_CHILD (flowbox_child), box);
g_object_set_data (G_OBJECT (flowbox_child), "button", button);
g_object_set_data (G_OBJECT (flowbox_child), "icon",
GTK_IMAGE (gtk_menu_button_get_child (GTK_MENU_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 GList *
get_container_children (GtkWidget *container)
{
GtkWidget *child;
GList *list = NULL;
child = gtk_widget_get_first_child (container);
while (child) {
GtkWidget *next = gtk_widget_get_next_sibling (child);
list = g_list_append (list, child);
child = next;
}
return list;
}
static void
update_prints_to_add_visibility (CcFingerprintDialog *self)
{
g_autoptr(GList) print_buttons = NULL;
GList *l;
guint i;
print_buttons = get_container_children (GTK_WIDGET (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;
g_autoptr(DialogStateRemover) state_remover = 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;
state_remover = auto_state_remover (self, DIALOG_STATE_DEVICE_PRINTS_LISTING);
gtk_widget_set_sensitive (GTK_WIDGET (self->add_print_icon), TRUE);
if (self->dialog_state & DIALOG_STATE_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_equal (dbus_error, CC_FPRINTD_NAME ".Error.NoEnrolledPrints"))
{
g_autofree char *error_message = NULL;
error_message = g_strdup_printf (_("Failed to list fingerprints: %s"),
dbus_error_to_human (self, error));
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_set_visible (GTK_WIDGET (self->delete_prints_button), TRUE);
}
static void
update_prints_store (CcFingerprintDialog *self)
{
ActUser *user;
g_assert_true (CC_FPRINTD_IS_DEVICE (self->device));
if (!add_dialog_state (self, DIALOG_STATE_DEVICE_PRINTS_LISTING))
return;
gtk_widget_set_sensitive (GTK_WIDGET (self->add_print_icon), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->delete_prints_button), FALSE);
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;
error_message = g_strdup_printf (_("Failed to delete saved fingerprints: %s"),
dbus_error_to_human (self, error));
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->dialog_state & DIALOG_STATE_DEVICE_CLAIMED);
if (!add_dialog_state (self, DIALOG_STATE_DEVICE_DELETING))
return;
gtk_widget_set_sensitive (GTK_WIDGET (self->prints_manager), FALSE);
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 = get_container_children (GTK_WIDGET (self->devices_list));
return devices_rows && devices_rows->next;
}
static void
set_enroll_result_message (CcFingerprintDialog *self,
EnrollState enroll_state,
const char *message)
{
const char *icon_name;
guint i;
g_return_if_fail (enroll_state >= 0 && enroll_state < N_ENROLL_STATES);
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_widget_remove_css_class (self->enroll_result_icon, ENROLL_STATE_CLASSES[i]);
gtk_widget_add_css_class (self->enroll_result_icon, ENROLL_STATE_CLASSES[enroll_state]);
gtk_image_set_from_icon_name (self->enroll_result_image, icon_name);
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->dialog_state & DIALOG_STATE_DEVICE_ENROLLING);
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 = 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");
remove_dialog_state (self, DIALOG_STATE_DEVICE_CLAIMED |
DIALOG_STATE_DEVICE_ENROLLING);
}
else if (g_str_equal (result, "enroll-data-full"))
{
message = _("Fingerprint device storage is full");
}
else if (g_str_equal (result, "enroll-duplicate"))
{
message = _("Fingerprint is duplicate");
}
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;
g_autoptr(DialogStateRemover) state_remover = 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;
state_remover = auto_state_remover (self, DIALOG_STATE_DEVICE_ENROLL_STARTING);
if (error)
{
g_autofree char *error_message = NULL;
remove_dialog_state (self, DIALOG_STATE_DEVICE_ENROLLING);
error_message = g_strdup_printf (_("Failed to start enrollment: %s"),
dbus_error_to_human (self, error));
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;
g_autoptr(DialogStateRemover) state_remover = 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;
state_remover = auto_state_remover (self, DIALOG_STATE_DEVICE_ENROLLING |
DIALOG_STATE_DEVICE_ENROLL_STOPPING);
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;
error_message = g_strdup_printf (_("Failed to stop enrollment: %s"),
dbus_error_to_human (self, error));
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->dialog_state & DIALOG_STATE_DEVICE_ENROLLING);
if (!add_dialog_state (self, DIALOG_STATE_DEVICE_ENROLL_STOPPING))
return;
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);
if (!add_dialog_state (self, DIALOG_STATE_DEVICE_ENROLLING |
DIALOG_STATE_DEVICE_ENROLL_STARTING))
return;
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_editable_set_text (GTK_EDITABLE (self->enroll_print_entry), finger_name);
cc_fprintd_device_call_enroll_start (self->device, finger_id, self->cancellable,
enroll_start_cb, self);
}
static void
populate_enrollment_view (CcFingerprintDialog *self)
{
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_box_prepend (GTK_BOX (self->enroll_print_bin), self->enroll_result_icon);
gtk_widget_add_css_class (self->enroll_result_icon, "enroll-status");
}
static void
on_print_activated_cb (CcFingerprintDialog *self,
GtkFlowBoxChild *child)
{
GtkWidget *selected_button;
selected_button = g_object_get_data (G_OBJECT (child), "button");
g_signal_emit_by_name (GTK_MENU_BUTTON (selected_button), "activate");
}
static void
on_enroll_cb (CcFingerprintDialog *self,
GtkMenuButton *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_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_box_prepend (GTK_BOX (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;
guint i;
g_return_if_fail (!GTK_IS_WIDGET (self->add_print_icon));
for (i = 0; i < N_VALID_FINGERS; ++i)
{
GtkWidget *flowbox_child;
GtkWidget *popover;
GtkWidget *reenroll_button;
flowbox_child = fingerprint_menu_button ("fingerprint-detection-symbolic",
get_finger_name (FINGER_IDS[i]));
button = g_object_get_data (G_OBJECT (flowbox_child), "button");
popover = gtk_popover_new ();
reenroll_button = gtk_button_new ();
gtk_button_set_use_underline (GTK_BUTTON (reenroll_button), TRUE);
gtk_button_set_label (GTK_BUTTON (reenroll_button), _("_Re-enroll this finger…"));
g_object_set_data (G_OBJECT (reenroll_button), "finger-id",
(gpointer) FINGER_IDS[i]);
g_signal_connect_object (reenroll_button, "clicked", G_CALLBACK (on_enroll_cb), self, G_CONNECT_SWAPPED);
gtk_popover_set_child (GTK_POPOVER (popover), reenroll_button);
gtk_menu_button_set_popover (GTK_MENU_BUTTON (button),
popover);
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);
gtk_widget_add_css_class (self->add_print_icon, "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_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;
error_message = g_strdup_printf (_("Failed to release fingerprint device %s: %s"),
cc_fprintd_device_get_name (fprintd_device),
dbus_error_to_human (self, error));
g_warning ("Releasing device %s failed: %s",
cc_fprintd_device_get_name (self->device), error->message);
notify_error (self, error_message);
return;
}
remove_dialog_state (self, DIALOG_STATE_DEVICE_CLAIMED);
}
static void
release_device (CcFingerprintDialog *self)
{
if (!self->device || !(self->dialog_state & DIALOG_STATE_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->dialog_state & DIALOG_STATE_DEVICE_CLAIMED)
{
disconnect_device_signals (self);
if (self->dialog_state & DIALOG_STATE_DEVICE_ENROLLING)
{
set_enroll_result_message (self, ENROLL_STATE_ERROR,
C_("Fingerprint enroll state",
"Problem Reading Device"));
}
remove_dialog_state (self, DIALOG_STATE_DEVICE_CLAIMED);
claim_device (self);
}
}
}
static void
claim_device_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GError) error = NULL;
g_autoptr(DialogStateRemover) state_remover = 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;
state_remover = auto_state_remover (self, DIALOG_STATE_DEVICE_CLAIMING);
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_equal (dbus_error, CC_FPRINTD_NAME ".Error.AlreadyInUse") &&
(self->dialog_state & DIALOG_STATE_DEVICE_CLAIMED))
return;
error_message = g_strdup_printf (_("Failed to claim fingerprint device %s: %s"),
cc_fprintd_device_get_name (self->device),
dbus_error_to_human (self, error));
g_warning ("Claiming device %s failed: %s",
cc_fprintd_device_get_name (self->device), error->message);
notify_error (self, error_message);
return;
}
if (!add_dialog_state (self, DIALOG_STATE_DEVICE_CLAIMED))
return;
gtk_widget_set_sensitive (self->prints_manager, 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->dialog_state & DIALOG_STATE_DEVICE_CLAIMED));
if (!add_dialog_state (self, DIALOG_STATE_DEVICE_CLAIMING))
return;
user = cc_fingerprint_manager_get_user (self->manager);
gtk_widget_set_sensitive (self->prints_manager, FALSE);
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_set_visible (GTK_WIDGET (self->back_button), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->cancel_button), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->done_button), FALSE);
adw_header_bar_set_show_start_title_buttons (ADW_HEADER_BAR (self->titlebar), TRUE);
adw_header_bar_set_show_end_title_buttons (ADW_HEADER_BAR (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->dialog_state & DIALOG_STATE_DEVICE_CLAIMED))
claim_device (self);
}
else if (visible_child == self->enrollment_view)
{
adw_header_bar_set_show_start_title_buttons (ADW_HEADER_BAR (self->titlebar), FALSE);
adw_header_bar_set_show_end_title_buttons (ADW_HEADER_BAR (self->titlebar), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->cancel_button), TRUE);
gtk_widget_set_sensitive (GTK_WIDGET (self->cancel_button), TRUE);
gtk_widget_set_visible (GTK_WIDGET (self->done_button), TRUE);
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/system/users/cc-fingerprint-dialog.css");
gtk_style_context_add_provider_for_display (gdk_display_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(DialogStateRemover) state_remover = 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);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
state_remover = auto_state_remover (self, DIALOG_STATE_DEVICES_LISTING);
if (fprintd_devices == NULL)
{
if (error)
{
g_autofree char *error_message = NULL;
error_message = g_strdup_printf (_("Failed to get fingerprint devices: %s"),
dbus_error_to_human (self, error));
g_warning ("Retrieving fingerprint devices failed: %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");
add_dialog_state (self, DIALOG_STATE_DEVICES_LISTING);
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_set_visible (self->delete_confirmation_infobar, FALSE);
delete_enrolled_prints (self);
}
static void
cancel_deletion_button_clicked_cb (CcFingerprintDialog *self)
{
gtk_widget_set_sensitive (self->prints_manager, TRUE);
gtk_widget_set_visible (self->delete_confirmation_infobar, FALSE);
}
static void
delete_prints_button_clicked_cb (CcFingerprintDialog *self)
{
gtk_widget_set_sensitive (self->prints_manager, FALSE);
gtk_widget_set_visible (self->delete_confirmation_infobar, TRUE);
}
static void
cancel_button_clicked_cb (CcFingerprintDialog *self)
{
if (self->dialog_state & DIALOG_STATE_DEVICE_ENROLLING)
{
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->dialog_state & DIALOG_STATE_DEVICE_ENROLLING);
g_debug ("Completing enroll operation");
enroll_stop (self);
}
static gboolean
cc_fingerprint_dialog_close_request (GtkWindow *window)
{
CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (window);
cc_fingerprint_manager_update_state (self->manager, NULL, NULL);
g_clear_handle_id (&self->enroll_stage_passed_id, g_source_remove);
if (self->device && (self->dialog_state & DIALOG_STATE_DEVICE_CLAIMED))
{
disconnect_device_signals (self);
if (self->dialog_state & DIALOG_STATE_DEVICE_ENROLLING)
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);
return GTK_WINDOW_CLASS (cc_fingerprint_dialog_parent_class)->close_request (window);
}
static void
cc_fingerprint_dialog_class_init (CcFingerprintDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GtkWindowClass *window_class = GTK_WINDOW_CLASS (klass);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "window.close", NULL);
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/control-center/system/users/cc-fingerprint-dialog.ui");
object_class->constructed = cc_fingerprint_dialog_constructed;
object_class->get_property = cc_fingerprint_dialog_get_property;
object_class->set_property = cc_fingerprint_dialog_set_property;
window_class->close_request = cc_fingerprint_dialog_close_request;
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, 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, on_print_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, select_device_row);
}