gnome-control-center/panels/user-accounts/um-fingerprint-dialog.c
Ondrej Holy af93bb814e user-accounts: don't crash when closing fingerprint dialog
Enroll data are used after destroying when closing dialog. It causes
crash while setting label. Therefor we have to store the pointer for
fingerprint button and use it to set the label after destroying the
data.

https://bugzilla.gnome.org/show_bug.cgi?id=741635
2014-12-17 12:34:30 +01:00

762 lines
28 KiB
C

/* gnome-about-me-fingerprint.h
* Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdlib.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#include "um-editable-button.h"
#include "um-fingerprint-dialog.h"
#include "fingerprint-strings.h"
/* Retrieve a widget from the UI object */
#define WID(s) GTK_WIDGET (gtk_builder_get_object (dialog, s))
/* Translate fprintd strings */
#define TR(s) dgettext("fprintd", s)
/* This must match the number of images on the 2nd page in the UI file */
#define MAX_ENROLL_STAGES 5
static GDBusProxy *manager = NULL;
static GDBusConnection *connection = NULL;
static gboolean is_disable = FALSE;
enum {
STATE_NONE,
STATE_CLAIMED,
STATE_ENROLLING
};
typedef struct {
GtkWidget *editable_button;
GtkWidget *ass;
GtkBuilder *dialog;
GDBusProxy *device;
gboolean is_swipe;
int num_enroll_stages;
int num_stages_done;
char *name;
const char *finger;
gint state;
} EnrollData;
static void create_manager (void)
{
GError *error = NULL;
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
if (connection == NULL) {
g_warning ("Failed to connect to session bus: %s", error->message);
g_error_free (error);
return;
}
manager = g_dbus_proxy_new_sync (connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"net.reactivated.Fprint",
"/net/reactivated/Fprint/Manager",
"net.reactivated.Fprint.Manager",
NULL,
&error);
if (manager == NULL) {
g_warning ("Failed to create fingerprint manager proxy: %s", error->message);
g_error_free (error);
}
}
static GDBusProxy *
get_first_device (void)
{
GDBusProxy *device;
GVariant *result;
char *device_str = NULL;
GError *error = NULL;
result = g_dbus_proxy_call_sync (manager,
"GetDefaultDevice",
g_variant_new ("()"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
NULL);
if (result == NULL)
return NULL;
if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(o)")))
g_warning ("net.reactivated.Fprint.Manager.GetDefaultDevice returns unknown result %s", g_variant_get_type_string (result));
else
g_variant_get (result, "(o)", &device_str);
g_variant_unref (result);
if (device_str == NULL)
return NULL;
device = g_dbus_proxy_new_sync (connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"net.reactivated.Fprint",
device_str,
"net.reactivated.Fprint.Device",
NULL,
&error);
if (device == NULL) {
g_warning ("Failed to create fingerprint device proxy: %s", error->message);
g_error_free (error);
}
g_free (device_str);
return device;
}
static const char *
get_reason_for_error (const char *dbus_error)
{
if (g_str_equal (dbus_error, "net.reactivated.Fprint.Error.PermissionDenied"))
return N_("You are not allowed to access the device. Contact your system administrator.");
if (g_str_equal (dbus_error, "net.reactivated.Fprint.Error.AlreadyInUse"))
return N_("The device is already in use.");
if (g_str_equal (dbus_error, "net.reactivated.Fprint.Error.Internal"))
return N_("An internal error occurred.");
return NULL;
}
static GtkWidget *
get_error_dialog (const char *title,
const char *dbus_error,
GtkWindow *parent)
{
GtkWidget *error_dialog;
const char *reason;
if (dbus_error == NULL)
g_warning ("get_error_dialog called with reason == NULL");
error_dialog =
gtk_message_dialog_new (parent,
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"%s", title);
reason = get_reason_for_error (dbus_error);
gtk_message_dialog_format_secondary_text
(GTK_MESSAGE_DIALOG (error_dialog), "%s", reason ? _(reason) : _(dbus_error));
gtk_window_set_title (GTK_WINDOW (error_dialog), ""); /* as per HIG */
gtk_container_set_border_width (GTK_CONTAINER (error_dialog), 5);
gtk_dialog_set_default_response (GTK_DIALOG (error_dialog),
GTK_RESPONSE_OK);
gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE);
gtk_window_set_position (GTK_WINDOW (error_dialog), GTK_WIN_POS_CENTER_ON_PARENT);
return error_dialog;
}
gboolean
set_fingerprint_label (GtkWidget *editable_button)
{
GDBusProxy *device;
GVariant *result;
GVariantIter *fingers;
GError *error = NULL;
if (manager == NULL) {
create_manager ();
if (manager == NULL) {
return FALSE;
}
}
device = get_first_device ();
if (device == NULL)
return FALSE;
result = g_dbus_proxy_call_sync (device, "ListEnrolledFingers", g_variant_new ("(s)", ""), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
if (!result) {
if (!g_dbus_error_is_remote_error (error) ||
strcmp (g_dbus_error_get_remote_error(error), "net.reactivated.Fprint.Error.NoEnrolledPrints") != 0) {
g_object_unref (device);
return FALSE;
}
}
if (result && g_variant_is_of_type (result, G_VARIANT_TYPE ("(as)")))
g_variant_get (result, "(as)", &fingers);
else
fingers = NULL;
if (fingers == NULL || g_variant_iter_n_children (fingers) == 0) {
is_disable = FALSE;
um_editable_button_set_text (UM_EDITABLE_BUTTON (editable_button), _("Disabled"));
} else {
is_disable = TRUE;
um_editable_button_set_text (UM_EDITABLE_BUTTON (editable_button), _("Enabled"));
}
if (result != NULL)
g_variant_unref (result);
if (fingers != NULL)
g_variant_iter_free (fingers);
g_object_unref (device);
return TRUE;
}
static void
delete_fingerprints (void)
{
GDBusProxy *device;
GVariant *result;
if (manager == NULL) {
create_manager ();
if (manager == NULL)
return;
}
device = get_first_device ();
if (device == NULL)
return;
result = g_dbus_proxy_call_sync (device, "DeleteEnrolledFingers", g_variant_new ("(s)", ""), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);
if (result)
g_variant_unref (result);
g_object_unref (device);
}
static void
delete_fingerprints_question (GtkWindow *parent,
GtkWidget *editable_button,
ActUser *user)
{
GtkWidget *question;
GtkWidget *button;
question = gtk_message_dialog_new (parent,
GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
_("Delete registered fingerprints?"));
gtk_dialog_add_button (GTK_DIALOG (question), _("_Cancel"), GTK_RESPONSE_CANCEL);
gtk_window_set_modal (GTK_WINDOW (question), TRUE);
button = gtk_button_new_with_mnemonic (_("_Delete Fingerprints"));
gtk_widget_set_can_default (button, TRUE);
gtk_widget_show (button);
gtk_dialog_add_action_widget (GTK_DIALOG (question), button, GTK_RESPONSE_OK);
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (question),
_("Do you want to delete your registered fingerprints so fingerprint login is disabled?"));
gtk_container_set_border_width (GTK_CONTAINER (question), 5);
gtk_dialog_set_default_response (GTK_DIALOG (question), GTK_RESPONSE_OK);
gtk_window_set_position (GTK_WINDOW (question), GTK_WIN_POS_CENTER_ON_PARENT);
gtk_window_set_modal (GTK_WINDOW (question), TRUE);
if (gtk_dialog_run (GTK_DIALOG (question)) == GTK_RESPONSE_OK) {
delete_fingerprints ();
set_fingerprint_label (editable_button);
}
gtk_widget_destroy (question);
}
static gboolean
enroll_start (EnrollData *data, GError **error)
{
GVariant *result;
result = g_dbus_proxy_call_sync (data->device, "EnrollStart", g_variant_new ("(s)", data->finger), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
if (result == NULL)
return FALSE;
g_variant_unref (result);
return TRUE;
}
static gboolean
enroll_stop (EnrollData *data, GError **error)
{
GVariant *result;
result = g_dbus_proxy_call_sync (data->device, "EnrollStop", g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
if (result == NULL)
return FALSE;
g_variant_unref (result);
return TRUE;
}
static gboolean
claim (EnrollData *data, GError **error)
{
GVariant *result;
result = g_dbus_proxy_call_sync (data->device, "Claim", g_variant_new ("(s)", ""), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
if (result == NULL)
return FALSE;
g_variant_unref (result);
return TRUE;
}
static gboolean
release (EnrollData *data, GError **error)
{
GVariant *result;
result = g_dbus_proxy_call_sync (data->device, "Release", g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
if (result == NULL)
return FALSE;
g_variant_unref (result);
return TRUE;
}
static void
enroll_data_destroy (EnrollData *data)
{
switch (data->state) {
case STATE_ENROLLING:
enroll_stop (data, NULL);
/* fall-through */
case STATE_CLAIMED:
release (data, NULL);
/* fall-through */
case STATE_NONE:
g_free (data->name);
g_object_unref (data->device);
g_object_unref (data->dialog);
gtk_widget_destroy (data->ass);
g_free (data);
}
}
static const char *
selected_finger (GtkBuilder *dialog)
{
int index;
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (WID ("radiobutton1")))) {
gtk_widget_set_sensitive (WID ("finger_combobox"), FALSE);
return "right-index-finger";
}
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (WID ("radiobutton2")))) {
gtk_widget_set_sensitive (WID ("finger_combobox"), FALSE);
return "left-index-finger";
}
gtk_widget_set_sensitive (WID ("finger_combobox"), TRUE);
index = gtk_combo_box_get_active (GTK_COMBO_BOX (WID ("finger_combobox")));
switch (index) {
case 0:
return "left-thumb";
case 1:
return "left-middle-finger";
case 2:
return "left-ring-finger";
case 3:
return "left-little-finger";
case 4:
return "right-thumb";
case 5:
return "right-middle-finger";
case 6:
return "right-ring-finger";
case 7:
return "right-little-finger";
default:
g_assert_not_reached ();
}
return NULL;
}
static void
finger_radio_button_toggled (GtkToggleButton *button, EnrollData *data)
{
GtkBuilder *dialog = data->dialog;
char *msg;
data->finger = selected_finger (data->dialog);
msg = g_strdup_printf (TR(finger_str_to_msg (data->finger, data->is_swipe)), data->name);
gtk_label_set_text (GTK_LABEL (WID("enroll-label")), msg);
g_free (msg);
}
static void
finger_combobox_changed (GtkComboBox *combobox, EnrollData *data)
{
GtkBuilder *dialog = data->dialog;
char *msg;
data->finger = selected_finger (data->dialog);
msg = g_strdup_printf (TR(finger_str_to_msg (data->finger, data->is_swipe)), data->name);
gtk_label_set_text (GTK_LABEL (WID("enroll-label")), msg);
g_free (msg);
}
static void
assistant_cancelled (GtkAssistant *ass, EnrollData *data)
{
GtkWidget *editable_button = data->editable_button;
enroll_data_destroy (data);
set_fingerprint_label (editable_button);
}
static void
enroll_result (EnrollData *data, const char *result, gboolean done)
{
GtkBuilder *dialog = data->dialog;
char *msg;
if (g_str_equal (result, "enroll-completed") || g_str_equal (result, "enroll-stage-passed")) {
char *name, *path;
data->num_stages_done++;
name = g_strdup_printf ("image%d", data->num_stages_done);
path = g_strdup_printf ("/org/gnome/control-center/user-accounts/print_ok.png");
gtk_image_set_from_resource (GTK_IMAGE (WID (name)), path);
g_free (name);
g_free (path);
}
if (g_str_equal (result, "enroll-completed")) {
gtk_label_set_text (GTK_LABEL (WID ("status-label")), _("Done!"));
gtk_label_set_text (GTK_LABEL (WID("enroll-label")), "");
gtk_assistant_set_page_complete (GTK_ASSISTANT (data->ass), WID ("page2"), TRUE);
}
if (done != FALSE) {
enroll_stop (data, NULL);
data->state = STATE_CLAIMED;
if (g_str_equal (result, "enroll-completed") == FALSE) {
/* The enrollment failed, restart it */
enroll_start (data, NULL);
data->state = STATE_ENROLLING;
result = "enroll-retry-scan";
} else {
return;
}
}
msg = g_strdup_printf (TR(enroll_result_str_to_msg (result, data->is_swipe)), data->name);
gtk_label_set_text (GTK_LABEL (WID ("status-label")), msg);
g_free (msg);
}
static void
device_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, EnrollData *data)
{
if (strcmp (signal_name, "EnrollStatus") == 0) {
if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sb)"))) {
gchar *result;
gboolean done;
g_variant_get (parameters, "(&sb)", &result, &done);
enroll_result (data, result, done);
}
}
}
static void
assistant_prepare (GtkAssistant *ass, GtkWidget *page, EnrollData *data)
{
const char *name;
name = g_object_get_data (G_OBJECT (page), "name");
if (name == NULL)
return;
if (g_str_equal (name, "enroll")) {
GError *error = NULL;
GtkBuilder *dialog = data->dialog;
char *path;
guint i;
GVariant *result;
gint num_enroll_stages;
if (!claim (data, &error)) {
GtkWidget *d;
char *msg;
/* translators:
* The variable is the name of the device, for example:
* "Could you not access "Digital Persona U.are.U 4000/4000B" device */
msg = g_strdup_printf (_("Could not access '%s' device"), data->name);
d = get_error_dialog (msg, error->message, GTK_WINDOW (data->ass));
g_error_free (error);
gtk_dialog_run (GTK_DIALOG (d));
gtk_widget_destroy (d);
g_free (msg);
enroll_data_destroy (data);
return;
}
data->state = STATE_CLAIMED;
result = g_dbus_connection_call_sync (connection,
"net.reactivated.Fprint",
g_dbus_proxy_get_object_path (data->device),
"org.freedesktop.DBus.Properties",
"Get",
g_variant_new ("(ss)", "net.reactivated.Fprint.Device", "num-enroll-stages"),
G_VARIANT_TYPE ("(v)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
num_enroll_stages = 0;
if (result) {
GVariant *v;
g_variant_get (result, "(v)", &v);
num_enroll_stages = g_variant_get_int32 (v);
g_variant_unref (result);
g_variant_unref (v);
}
if (num_enroll_stages < 1) {
GtkWidget *d;
char *msg;
/* translators:
* The variable is the name of the device, for example:
* "Could you not access "Digital Persona U.are.U 4000/4000B" device */
msg = g_strdup_printf (_("Could not access '%s' device"), data->name);
d = get_error_dialog (msg, "net.reactivated.Fprint.Error.Internal", GTK_WINDOW (data->ass));
gtk_dialog_run (GTK_DIALOG (d));
gtk_widget_destroy (d);
g_free (msg);
enroll_data_destroy (data);
return;
}
data->num_enroll_stages = num_enroll_stages;
/* Hide the extra "bulbs" if not needed */
for (i = MAX_ENROLL_STAGES; i > data->num_enroll_stages; i--) {
char *name;
name = g_strdup_printf ("image%d", i);
gtk_widget_hide (WID (name));
g_free (name);
}
/* And set the right image */
{
path = g_strdup_printf ("/org/gnome/control-center/user-accounts/%s.png", data->finger);
}
for (i = 1; i <= data->num_enroll_stages; i++) {
char *name;
name = g_strdup_printf ("image%d", i);
gtk_image_set_from_resource (GTK_IMAGE (WID (name)), path);
g_free (name);
}
g_free (path);
g_signal_connect (data->device, "g-signal", G_CALLBACK (device_signal_cb), data);
if (!enroll_start (data, &error)) {
GtkWidget *d;
char *msg;
/* translators:
* The variable is the name of the device, for example:
* "Could you not access "Digital Persona U.are.U 4000/4000B" device */
msg = g_strdup_printf (_("Could not start finger capture on '%s' device"), data->name);
d = get_error_dialog (msg, error->message, GTK_WINDOW (data->ass));
g_error_free (error);
gtk_dialog_run (GTK_DIALOG (d));
gtk_widget_destroy (d);
g_free (msg);
enroll_data_destroy (data);
return;
}
data->state = STATE_ENROLLING;;
} else {
if (data->state == STATE_ENROLLING) {
enroll_stop (data, NULL);
data->state = STATE_CLAIMED;
}
if (data->state == STATE_CLAIMED) {
release (data, NULL);
data->state = STATE_NONE;
}
}
}
static void
enroll_fingerprints (GtkWindow *parent,
GtkWidget *editable_button,
ActUser *user)
{
GDBusProxy *device;
GtkBuilder *dialog;
EnrollData *data;
GtkWidget *ass;
char *msg;
GVariant *result;
GError *error = NULL;
device = NULL;
if (manager == NULL) {
create_manager ();
if (manager != NULL)
device = get_first_device ();
} else {
device = get_first_device ();
}
if (manager == NULL || device == NULL) {
GtkWidget *d;
d = get_error_dialog (_("Could not access any fingerprint readers"),
_("Please contact your system administrator for help."),
parent);
gtk_dialog_run (GTK_DIALOG (d));
gtk_widget_destroy (d);
return;
}
data = g_new0 (EnrollData, 1);
data->device = device;
data->editable_button = editable_button;
/* Get some details about the device */
result = g_dbus_connection_call_sync (connection,
"net.reactivated.Fprint",
g_dbus_proxy_get_object_path (data->device),
"org.freedesktop.DBus.Properties",
"GetAll",
g_variant_new ("(s)", "net.reactivated.Fprint.Device"),
G_VARIANT_TYPE ("(a{sv})"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
NULL);
if (result) {
GVariant *props;
gchar *scan_type;
g_variant_get (result, "(@a{sv})", &props);
g_variant_lookup (props, "name", "s", &data->name);
g_variant_lookup (props, "scan-type", "s", &scan_type);
if (g_strcmp0 (scan_type, "swipe") == 0)
data->is_swipe = TRUE;
g_free (scan_type);
g_variant_unref (props);
g_variant_unref (result);
}
dialog = gtk_builder_new ();
if (!gtk_builder_add_from_resource (dialog,
"/org/gnome/control-center/user-accounts/account-fingerprint.ui",
&error)) {
g_error ("%s", error->message);
g_error_free (error);
return;
}
data->dialog = dialog;
ass = WID ("assistant");
gtk_window_set_title (GTK_WINDOW (ass), _("Enable Fingerprint Login"));
gtk_window_set_transient_for (GTK_WINDOW (ass), parent);
gtk_window_set_modal (GTK_WINDOW (ass), TRUE);
gtk_window_set_resizable (GTK_WINDOW (ass), FALSE);
gtk_window_set_type_hint (GTK_WINDOW (ass), GDK_WINDOW_TYPE_HINT_DIALOG);
g_signal_connect (G_OBJECT (ass), "cancel",
G_CALLBACK (assistant_cancelled), data);
g_signal_connect (G_OBJECT (ass), "close",
G_CALLBACK (assistant_cancelled), data);
g_signal_connect (G_OBJECT (ass), "prepare",
G_CALLBACK (assistant_prepare), data);
/* Page 1 */
gtk_combo_box_set_active (GTK_COMBO_BOX (WID ("finger_combobox")), 0);
g_signal_connect (G_OBJECT (WID ("radiobutton1")), "toggled",
G_CALLBACK (finger_radio_button_toggled), data);
g_signal_connect (G_OBJECT (WID ("radiobutton2")), "toggled",
G_CALLBACK (finger_radio_button_toggled), data);
g_signal_connect (G_OBJECT (WID ("radiobutton3")), "toggled",
G_CALLBACK (finger_radio_button_toggled), data);
g_signal_connect (G_OBJECT (WID ("finger_combobox")), "changed",
G_CALLBACK (finger_combobox_changed), data);
data->finger = selected_finger (dialog);
g_object_set_data (G_OBJECT (WID("page1")), "name", "intro");
/* translators:
* The variable is the name of the device, for example:
* "To enable fingerprint login, you need to save one of your fingerprints, using the
* 'Digital Persona U.are.U 4000/4000B' device."
*/
msg = g_strdup_printf (_("To enable fingerprint login, you need to save one of your fingerprints, using the '%s' device."),
data->name);
gtk_label_set_text (GTK_LABEL (WID("intro-label")), msg);
g_free (msg);
gtk_assistant_set_page_complete (GTK_ASSISTANT (ass), WID("page1"), TRUE);
gtk_assistant_set_page_title (GTK_ASSISTANT (ass), WID("page1"), _("Selecting finger"));
gtk_assistant_set_page_title (GTK_ASSISTANT (ass), WID("page2"), _("Enrolling fingerprints"));
gtk_assistant_set_page_title (GTK_ASSISTANT (ass), WID("page3"), _("Summary"));
/* Page 2 */
g_object_set_data (G_OBJECT (WID("page2")), "name", "enroll");
msg = g_strdup_printf (TR(finger_str_to_msg (data->finger, data->is_swipe)), data->name);
gtk_label_set_text (GTK_LABEL (WID("enroll-label")), msg);
g_free (msg);
/* Page 3 */
g_object_set_data (G_OBJECT (WID("page3")), "name", "summary");
data->ass = ass;
gtk_widget_show_all (ass);
}
void
fingerprint_button_clicked (GtkWindow *parent,
GtkWidget *editable_button,
ActUser *user)
{
bindtextdomain ("fprintd", GNOMELOCALEDIR);
bind_textdomain_codeset ("fprintd", "UTF-8");
if (is_disable != FALSE) {
delete_fingerprints_question (parent, editable_button, user);
} else {
enroll_fingerprints (parent, editable_button, user);
}
}