gnome-control-center/panels/user-accounts/cc-fingerprint-dialog.c
Marco Trevisan (Treviño) b031bb5758 fingerprint-dialog: Use auto-cleanup to manage removal of dialog states
For sake of simplicity we were just releasing a state once we got a
callback, however during the handling of the callback itself we are
still technically under that scenario, so we should release a state only
after the callback is completed.

This is more relevant now since when converting errors to human readable
strings we check the state we are in, and so we need to ensure that this
state is preserved until this point.
2020-12-11 17:46:38 +00:00

1588 lines
51 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;
GtkContainer *add_print_popover_box;
GtkEntry *enroll_print_entry;
GtkFlowBox *prints_gallery;
GtkHeaderBar *titlebar;
GtkImage *enroll_result_image;
GtkLabel *enroll_message;
GtkLabel *enroll_result_message;
GtkLabel *infobar_error;
GtkLabel *title;
GtkListBox *devices_list;
GtkPopoverMenu *add_print_popover;
GtkPopoverMenu *print_popover;
GtkSpinner *spinner;
GtkStack *stack;
GtkWidget *add_print_icon;
GtkWidget *delete_confirmation_infobar;
GtkWidget *device_selector;
GtkWidget *enroll_print_bin;
GtkWidget *enroll_result_icon;
GtkWidget *enrollment_view;
GtkWidget *error_infobar;
GtkWidget *no_devices_found;
GtkWidget *prints_manager;
CcFingerprintManager *manager;
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_dispose (GObject *object)
{
CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object);
g_clear_handle_id (&self->enroll_stage_passed_id, g_source_remove);
if (self->device && (self->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);
G_OBJECT_CLASS (cc_fingerprint_dialog_parent_class)->dispose (object);
}
static void
cc_fingerprint_dialog_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object);
switch (prop_id)
{
case PROP_MANAGER:
g_value_set_object (value, self->manager);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
cc_fingerprint_dialog_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CcFingerprintDialog *self = CC_FINGERPRINT_DIALOG (object);
switch (prop_id)
{
case PROP_MANAGER:
g_set_object (&self->manager, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
notify_error (CcFingerprintDialog *self,
const char *error_message)
{
if (error_message)
gtk_label_set_label (self->infobar_error, error_message);
gtk_widget_set_visible (self->error_infobar, error_message != NULL);
}
static gboolean
fingerprint_icon_draw (GtkWidget *widget,
cairo_t *cr,
gdouble *progress_data)
{
gdouble progress = 0.0f;
if (progress_data)
progress = *progress_data;
if (G_APPROX_VALUE (progress, 0.f, FLT_EPSILON) || progress > 1)
return FALSE;
GTK_WIDGET_GET_CLASS (widget)->draw (widget, cr);
if (progress > 0)
{
g_autoptr(GdkRGBA) outline_color = NULL;
GtkStyleContext *context;
GtkStateFlags state;
int outline_width;
int outline_offset;
int width;
int height;
int radius;
int delta;
context = gtk_widget_get_style_context (widget);
gtk_style_context_save (context);
state = gtk_style_context_get_state (context);
gtk_style_context_add_class (context, "progress");
gtk_style_context_get (context, state,
"outline-width", &outline_width,
"outline-offset", &outline_offset,
"outline-color", &outline_color,
NULL);
width = gtk_widget_get_allocated_width (widget);
height = gtk_widget_get_allocated_height (widget);
radius = MIN (width / 2, height / 2) + outline_offset;
delta = radius - outline_width / 2;
cairo_arc (cr, width / 2., height / 2., delta,
1.5 * G_PI, (1.5 + progress * 2) * G_PI);
gdk_cairo_set_source_rgba (cr, outline_color);
cairo_set_line_width (cr, MIN (outline_width, radius));
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
cairo_stroke (cr);
gtk_style_context_restore (context);
}
return TRUE;
}
static GtkWidget *
fingerprint_icon_new (const char *icon_name,
const char *label_text,
GType icon_widget_type,
gpointer progress_data,
GtkWidget **out_icon,
GtkWidget **out_label)
{
GtkStyleContext *context;
GtkWidget *box;
GtkWidget *label;
GtkWidget *image;
GtkWidget *icon_widget;
g_return_val_if_fail (g_type_is_a (icon_widget_type, GTK_TYPE_WIDGET), NULL);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
gtk_widget_set_name (box, "fingerprint-box");
gtk_widget_set_hexpand (box, TRUE);
image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DND);
if (icon_widget_type == GTK_TYPE_IMAGE)
icon_widget = image;
else
icon_widget = g_object_new (icon_widget_type, NULL);
if (progress_data)
g_signal_connect (image, "draw", G_CALLBACK (fingerprint_icon_draw),
progress_data);
if (g_type_is_a (icon_widget_type, GTK_TYPE_BUTTON))
{
gtk_button_set_image (GTK_BUTTON (icon_widget), image);
gtk_button_set_relief (GTK_BUTTON (icon_widget), GTK_RELIEF_NONE);
gtk_widget_set_can_focus (icon_widget, FALSE);
}
gtk_widget_set_halign (icon_widget, GTK_ALIGN_CENTER);
gtk_widget_set_valign (icon_widget, GTK_ALIGN_CENTER);
gtk_widget_set_name (icon_widget, "fingerprint-image");
gtk_container_add (GTK_CONTAINER (box), icon_widget);
context = gtk_widget_get_style_context (icon_widget);
gtk_style_context_add_class (context, "fingerprint-image");
label = gtk_label_new_with_mnemonic (label_text);
gtk_container_add (GTK_CONTAINER (box), label);
context = gtk_widget_get_style_context (box);
gtk_style_context_add_class (context, "fingerprint-icon");
if (out_icon)
*out_icon = icon_widget;
if (out_label)
*out_label = label;
return box;
}
static GtkWidget *
fingerprint_menu_button (const char *icon_name,
const char *label_text)
{
GtkWidget *flowbox_child;
GtkWidget *button;
GtkWidget *label;
GtkWidget *box;
box = fingerprint_icon_new (icon_name, label_text, GTK_TYPE_MENU_BUTTON, NULL,
&button, &label);
flowbox_child = gtk_flow_box_child_new ();
gtk_widget_set_focus_on_click (flowbox_child, FALSE);
gtk_widget_set_name (flowbox_child, "fingerprint-flowbox");
gtk_container_add (GTK_CONTAINER (flowbox_child), box);
g_object_set_data (G_OBJECT (flowbox_child), "button", button);
g_object_set_data (G_OBJECT (flowbox_child), "icon",
gtk_button_get_image (GTK_BUTTON (button)));
g_object_set_data (G_OBJECT (flowbox_child), "label", label);
g_object_set_data (G_OBJECT (button), "flowbox-child", flowbox_child);
return flowbox_child;
}
static gboolean
prints_visibility_filter (GtkFlowBoxChild *child,
gpointer user_data)
{
CcFingerprintDialog *self = user_data;
const char *finger_id;
if (gtk_stack_get_visible_child (self->stack) != self->prints_manager)
return FALSE;
finger_id = g_object_get_data (G_OBJECT (child), "finger-id");
if (!finger_id)
return TRUE;
if (!self->enrolled_fingers)
return FALSE;
return g_strv_contains ((const gchar **) self->enrolled_fingers, finger_id);
}
static void
update_prints_to_add_visibility (CcFingerprintDialog *self)
{
g_autoptr(GList) print_buttons = NULL;
GList *l;
guint i;
print_buttons = gtk_container_get_children (self->add_print_popover_box);
for (i = 0, l = print_buttons; i < N_VALID_FINGERS && l; ++i, l = l->next)
{
GtkWidget *button = l->data;
gboolean enrolled;
enrolled = self->enrolled_fingers &&
g_strv_contains ((const gchar **) self->enrolled_fingers,
FINGER_IDS[i]);
gtk_widget_set_visible (button, !enrolled);
}
}
static void
update_prints_visibility (CcFingerprintDialog *self)
{
update_prints_to_add_visibility (self);
gtk_flow_box_invalidate_filter (self->prints_gallery);
}
static void
list_enrolled_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
g_auto(GStrv) enrolled_fingers = NULL;
g_autoptr(GError) error = NULL;
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_show (GTK_WIDGET (self->delete_prints_button));
}
static void
update_prints_store (CcFingerprintDialog *self)
{
ActUser *user;
g_assert_true (CC_FPRINTD_IS_DEVICE (self->device));
if (!add_dialog_state (self, DIALOG_STATE_DEVICE_PRINTS_LISTING))
return;
gtk_widget_set_sensitive (GTK_WIDGET (self->add_print_icon), FALSE);
gtk_widget_hide (GTK_WIDGET (self->delete_prints_button));
g_clear_pointer (&self->enrolled_fingers, g_strfreev);
user = cc_fingerprint_manager_get_user (self->manager);
cc_fprintd_device_call_list_enrolled_fingers (self->device,
act_user_get_user_name (user),
self->cancellable,
list_enrolled_cb,
self);
}
static void
delete_prints_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GError) error = NULL;
CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object);
CcFingerprintDialog *self = user_data;
cc_fprintd_device_call_delete_enrolled_fingers2_finish (fprintd_device, res, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
if (error)
{
g_autofree char *error_message = NULL;
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 = gtk_container_get_children (GTK_CONTAINER (self->devices_list));
return devices_rows && devices_rows->next;
}
static void
set_enroll_result_message (CcFingerprintDialog *self,
EnrollState enroll_state,
const char *message)
{
GtkStyleContext *style_context;
const char *icon_name;
guint i;
g_return_if_fail (enroll_state >= 0 && enroll_state < N_ENROLL_STATES);
style_context = gtk_widget_get_style_context (self->enroll_result_icon);
switch (enroll_state)
{
case ENROLL_STATE_WARNING:
case ENROLL_STATE_ERROR:
icon_name = "fingerprint-detection-warning-symbolic";
break;
case ENROLL_STATE_COMPLETED:
icon_name = "fingerprint-detection-complete-symbolic";
break;
default:
icon_name = "fingerprint-detection-symbolic";
}
for (i = 0; i < N_ENROLL_STATES; ++i)
gtk_style_context_remove_class (style_context, ENROLL_STATE_CLASSES[i]);
gtk_style_context_add_class (style_context, ENROLL_STATE_CLASSES[enroll_state]);
gtk_image_set_from_icon_name (self->enroll_result_image, icon_name, GTK_ICON_SIZE_DND);
gtk_label_set_label (self->enroll_result_message, message);
}
static gboolean
stage_passed_timeout_cb (gpointer user_data)
{
CcFingerprintDialog *self = user_data;
const char *current_message;
current_message = gtk_label_get_label (self->enroll_result_message);
set_enroll_result_message (self, ENROLL_STATE_NORMAL, current_message);
self->enroll_stage_passed_id = 0;
return FALSE;
}
static void
handle_enroll_signal (CcFingerprintDialog *self,
const char *result,
gboolean done)
{
gboolean completed;
g_return_if_fail (self->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
{
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_entry_set_text (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)
{
GtkStyleContext *style_context;
self->enroll_result_icon =
fingerprint_icon_new ("fingerprint-detection-symbolic",
NULL,
GTK_TYPE_IMAGE,
&self->enroll_progress,
(GtkWidget **) &self->enroll_result_image,
(GtkWidget **) &self->enroll_result_message);
gtk_container_add (GTK_CONTAINER (self->enroll_print_bin), self->enroll_result_icon);
style_context = gtk_widget_get_style_context (self->enroll_result_icon);
gtk_style_context_add_class (style_context, "enroll-status");
gtk_widget_show_all (self->enroll_print_bin);
}
static void
reenroll_finger_cb (CcFingerprintDialog *self)
{
GtkWidget *button;
GtkWidget *flowbox_child;
const char *finger_id;
button = gtk_popover_get_relative_to (GTK_POPOVER (self->print_popover));
flowbox_child = g_object_get_data (G_OBJECT (button), "flowbox-child");
finger_id = g_object_get_data (G_OBJECT (flowbox_child), "finger-id");
enroll_finger (self, finger_id);
}
static void
on_print_activated_cb (GtkFlowBox *flowbox,
GtkFlowBoxChild *child,
CcFingerprintDialog *self)
{
GtkWidget *selected_button;
selected_button = g_object_get_data (G_OBJECT (child), "button");
gtk_button_clicked (GTK_BUTTON (selected_button));
}
static void
on_enroll_cb (CcFingerprintDialog *self,
GtkModelButton *button)
{
const char *finger_id;
finger_id = g_object_get_data (G_OBJECT (button), "finger-id");
enroll_finger (self, finger_id);
}
static void
populate_add_print_popover (CcFingerprintDialog *self)
{
guint i;
for (i = 0; i < N_VALID_FINGERS; ++i)
{
GtkWidget *finger_item;
finger_item = gtk_model_button_new ();
gtk_button_set_label (GTK_BUTTON (finger_item), get_finger_name (FINGER_IDS[i]));
gtk_button_set_use_underline (GTK_BUTTON (finger_item), TRUE);
g_object_set_data (G_OBJECT (finger_item), "finger-id", (gpointer) FINGER_IDS[i]);
gtk_container_add (self->add_print_popover_box, finger_item);
g_signal_connect_object (finger_item, "clicked", G_CALLBACK (on_enroll_cb),
self, G_CONNECT_SWAPPED);
}
}
static void
populate_prints_gallery (CcFingerprintDialog *self)
{
const char *add_print_label;
GtkWidget *button;
GtkStyleContext *style_context;
guint i;
g_return_if_fail (!GTK_IS_WIDGET (self->add_print_icon));
for (i = 0; i < N_VALID_FINGERS; ++i)
{
GtkWidget *flowbox_child;
flowbox_child = fingerprint_menu_button ("fingerprint-detection-symbolic",
get_finger_name (FINGER_IDS[i]));
button = g_object_get_data (G_OBJECT (flowbox_child), "button");
gtk_menu_button_set_popover (GTK_MENU_BUTTON (button),
GTK_WIDGET (self->print_popover));
/* Move the popover on click, so we can just reuse the same instance */
g_signal_connect_object (button, "clicked",
G_CALLBACK (gtk_popover_set_relative_to),
self->print_popover, G_CONNECT_SWAPPED);
g_object_set_data (G_OBJECT (flowbox_child), "finger-id",
(gpointer) FINGER_IDS[i]);
gtk_flow_box_insert (self->prints_gallery, flowbox_child, i);
}
/* TRANSLATORS: This is the label for the button to enroll a new finger */
add_print_label = _("Scan new fingerprint");
self->add_print_icon = fingerprint_menu_button ("list-add-symbolic",
add_print_label);
style_context = gtk_widget_get_style_context (self->add_print_icon);
gtk_style_context_add_class (style_context, "fingerprint-print-add");
populate_add_print_popover (self);
button = g_object_get_data (G_OBJECT (self->add_print_icon), "button");
gtk_menu_button_set_popover (GTK_MENU_BUTTON (button),
GTK_WIDGET (self->add_print_popover));
gtk_flow_box_insert (self->prints_gallery, self->add_print_icon, -1);
gtk_flow_box_set_max_children_per_line (self->prints_gallery, 1);
gtk_widget_show_all (GTK_WIDGET (self->prints_gallery));
gtk_flow_box_set_filter_func (self->prints_gallery, prints_visibility_filter,
self, NULL);
update_prints_visibility (self);
}
static void
release_device_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GError) error = NULL;
CcFprintdDevice *fprintd_device = CC_FPRINTD_DEVICE (object);
CcFingerprintDialog *self = user_data;
cc_fprintd_device_call_release_finish (fprintd_device, res, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
if (error)
{
g_autofree char *error_message = NULL;
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_hide (GTK_WIDGET (self->back_button));
gtk_widget_hide (GTK_WIDGET (self->cancel_button));
gtk_widget_hide (GTK_WIDGET (self->done_button));
gtk_header_bar_set_show_close_button (self->titlebar, TRUE);
gtk_flow_box_invalidate_filter (self->prints_gallery);
if (visible_child == self->prints_manager)
{
gtk_widget_set_visible (GTK_WIDGET (self->back_button),
have_multiple_devices (self));
notify_error (self, NULL);
update_prints_store (self);
if (!(self->dialog_state & DIALOG_STATE_DEVICE_CLAIMED))
claim_device (self);
}
else if (visible_child == self->enrollment_view)
{
gtk_header_bar_set_show_close_button (self->titlebar, FALSE);
gtk_widget_show (GTK_WIDGET (self->cancel_button));
gtk_widget_set_sensitive (GTK_WIDGET (self->cancel_button), TRUE);
gtk_widget_show (GTK_WIDGET (self->done_button));
gtk_widget_set_sensitive (GTK_WIDGET (self->done_button), FALSE);
}
else
{
release_device (self);
g_clear_object (&self->device);
}
}
static void
cc_fingerprint_dialog_init (CcFingerprintDialog *self)
{
g_autoptr(GtkCssProvider) provider = NULL;
self->cancellable = g_cancellable_new ();
gtk_widget_init_template (GTK_WIDGET (self));
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_resource (provider,
"/org/gnome/control-center/user-accounts/cc-fingerprint-dialog.css");
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
on_stack_child_changed (self);
g_signal_connect_object (self->stack, "notify::visible-child",
G_CALLBACK (on_stack_child_changed), self,
G_CONNECT_SWAPPED);
g_object_bind_property (self->stack, "visible-child-name",
self->title, "label", G_BINDING_SYNC_CREATE);
populate_prints_gallery (self);
populate_enrollment_view (self);
}
static void
select_device_row (CcFingerprintDialog *self,
GtkListBoxRow *row,
GtkListBox *listbox)
{
CcFprintdDevice *device = g_object_get_data (G_OBJECT (row), "device");
g_return_if_fail (CC_FPRINTD_DEVICE (device));
g_set_object (&self->device, device);
gtk_stack_set_visible_child (self->stack, self->prints_manager);
}
static void
on_devices_list (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
g_autolist (CcFprintdDevice) fprintd_devices = NULL;
g_autoptr(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_hide (self->delete_confirmation_infobar);
delete_enrolled_prints (self);
}
static void
cancel_deletion_button_clicked_cb (CcFingerprintDialog *self)
{
gtk_widget_set_sensitive (self->prints_manager, TRUE);
gtk_widget_hide (self->delete_confirmation_infobar);
}
static void
delete_prints_button_clicked_cb (CcFingerprintDialog *self)
{
gtk_widget_set_sensitive (self->prints_manager, FALSE);
gtk_widget_show (self->delete_confirmation_infobar);
}
static void
cancel_button_clicked_cb (CcFingerprintDialog *self)
{
if (self->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 void
fingerprint_dialog_delete_cb (CcFingerprintDialog *self)
{
cc_fingerprint_manager_update_state (self->manager, NULL, NULL);
gtk_widget_destroy (GTK_WIDGET (self));
}
static void
cc_fingerprint_dialog_class_init (CcFingerprintDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/control-center/user-accounts/cc-fingerprint-dialog.ui");
object_class->constructed = cc_fingerprint_dialog_constructed;
object_class->dispose = cc_fingerprint_dialog_dispose;
object_class->get_property = cc_fingerprint_dialog_get_property;
object_class->set_property = cc_fingerprint_dialog_set_property;
properties[PROP_MANAGER] =
g_param_spec_object ("fingerprint-manager",
"FingerprintManager",
"The CC fingerprint manager",
CC_TYPE_FINGERPRINT_MANAGER,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_properties (object_class, N_PROPS, properties);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, add_print_popover);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, add_print_popover_box);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, back_button);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, cancel_button);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, delete_confirmation_infobar);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, delete_prints_button);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, device_selector);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, devices_list);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, done_button);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enroll_message);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enroll_print_bin);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enroll_print_entry);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, enrollment_view);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, error_infobar);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, infobar_error);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, no_devices_found);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, print_popover);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, prints_gallery);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, prints_manager);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, spinner);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, stack);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, title);
gtk_widget_class_bind_template_child (widget_class, CcFingerprintDialog, titlebar);
gtk_widget_class_bind_template_callback (widget_class, back_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, cancel_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, cancel_deletion_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, confirm_deletion_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, delete_prints_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, done_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, fingerprint_dialog_delete_cb);
gtk_widget_class_bind_template_callback (widget_class, on_print_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, reenroll_finger_cb);
gtk_widget_class_bind_template_callback (widget_class, select_device_row);
}