libfprint/libfprint/fpi-device.c
Marco Trevisan (Treviño) ef805f2341 device: Expose supported features publicly as FpDeviceFeature
It can be convenient for device users to check what it supports, without
having multiple functions to check each single feature.

So expose this and add tests.
2021-04-12 22:14:06 +02:00

1625 lines
47 KiB
C

/*
* FpDevice - A fingerprint reader device - Private APIs
* Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
* Copyright (C) 2019 Marco Trevisan <marco.trevisan@canonical.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define FP_COMPONENT "device"
#include "fpi-log.h"
#include "fp-device-private.h"
/**
* SECTION: fpi-device
* @title: Internal FpDevice
* @short_description: Internal device routines
*
* The methods that are available for drivers to manipulate a device. See
* #FpDeviceClass for more information. Also note that most of these are
* not relevant for image based devices, see #FpImageDeviceClass in that
* case.
*
* Also see the public #FpDevice routines.
*/
/* Manually redefine what G_DEFINE_* macro does */
static inline gpointer
fp_device_get_instance_private (FpDevice *self)
{
FpDeviceClass *dev_class = g_type_class_peek_static (FP_TYPE_DEVICE);
return G_STRUCT_MEMBER_P (self,
g_type_class_get_instance_private_offset (dev_class));
}
/**
* fpi_device_class_auto_initialize_features:
*
* Initializes the #FpDeviceClass @features flags checking what device vfuncs
* are implemented.
* Drivers should call this at the end of the class initialization.
*/
void
fpi_device_class_auto_initialize_features (FpDeviceClass *device_class)
{
g_return_if_fail (FP_IS_DEVICE_CLASS (device_class));
if (device_class->capture)
device_class->features |= FP_DEVICE_FEATURE_CAPTURE;
if (device_class->verify)
device_class->features |= FP_DEVICE_FEATURE_VERIFY;
if (device_class->identify)
device_class->features |= FP_DEVICE_FEATURE_IDENTIFY;
if (device_class->list)
{
device_class->features |= FP_DEVICE_FEATURE_STORAGE;
device_class->features |= FP_DEVICE_FEATURE_STORAGE_LIST;
}
if (device_class->delete)
{
device_class->features |= FP_DEVICE_FEATURE_STORAGE;
device_class->features |= FP_DEVICE_FEATURE_STORAGE_DELETE;
}
}
/**
* fpi_device_retry_new:
* @error: The #FpDeviceRetry error value describing the issue
*
* Create a new retry error code for use with fpi_device_verify_complete()
* and similar calls.
*/
GError *
fpi_device_retry_new (FpDeviceRetry error)
{
const gchar *msg;
switch (error)
{
case FP_DEVICE_RETRY_GENERAL:
msg = "Please try again.";
break;
case FP_DEVICE_RETRY_TOO_SHORT:
msg = "The swipe was too short, please try again.";
break;
case FP_DEVICE_RETRY_CENTER_FINGER:
msg = "The finger was not centered properly, please try again.";
break;
case FP_DEVICE_RETRY_REMOVE_FINGER:
msg = "Please try again after removing the finger first.";
break;
default:
g_warning ("Unsupported error, returning general error instead!");
error = FP_DEVICE_RETRY_GENERAL;
msg = "Please try again.";
}
return g_error_new_literal (FP_DEVICE_RETRY, error, msg);
}
/**
* fpi_device_error_new:
* @error: The #FpDeviceRetry error value describing the issue
*
* Create a new error code for use with fpi_device_verify_complete() and
* similar calls.
*/
GError *
fpi_device_error_new (FpDeviceError error)
{
const gchar *msg;
switch (error)
{
case FP_DEVICE_ERROR_GENERAL:
msg = "An unspecified error occurred!";
break;
case FP_DEVICE_ERROR_NOT_SUPPORTED:
msg = "The operation is not supported on this device!";
break;
case FP_DEVICE_ERROR_NOT_OPEN:
msg = "The device needs to be opened first!";
break;
case FP_DEVICE_ERROR_ALREADY_OPEN:
msg = "The device has already been opened!";
break;
case FP_DEVICE_ERROR_BUSY:
msg = "The device is still busy with another operation, please try again later.";
break;
case FP_DEVICE_ERROR_PROTO:
msg = "The driver encountered a protocol error with the device.";
break;
case FP_DEVICE_ERROR_DATA_INVALID:
msg = "Passed (print) data is not valid.";
break;
case FP_DEVICE_ERROR_DATA_FULL:
msg = "On device storage space is full.";
break;
case FP_DEVICE_ERROR_DATA_NOT_FOUND:
msg = "Print was not found on the devices storage.";
break;
case FP_DEVICE_ERROR_DATA_DUPLICATE:
msg = "This finger has already enrolled, please try a different finger";
break;
case FP_DEVICE_ERROR_REMOVED:
msg = "This device has been removed from the system.";
break;
default:
g_warning ("Unsupported error, returning general error instead!");
error = FP_DEVICE_ERROR_GENERAL;
msg = "An unspecified error occurred!";
}
return g_error_new_literal (FP_DEVICE_ERROR, error, msg);
}
/**
* fpi_device_retry_new_msg:
* @error: The #FpDeviceRetry error value describing the issue
* @msg: Custom message to use with printf-style formatting
* @...: args for @msg
*
* Create a new retry error code for use with fpi_device_verify_complete()
* and similar calls.
*/
GError *
fpi_device_retry_new_msg (FpDeviceRetry device_error,
const gchar *msg,
...)
{
GError *error;
va_list args;
va_start (args, msg);
error = g_error_new_valist (FP_DEVICE_RETRY, device_error, msg, args);
va_end (args);
return error;
}
/**
* fpi_device_error_new_msg:
* @error: The #FpDeviceRetry error value describing the issue
* @msg: Custom message to use with printf-style formatting
* @...: args for @msg
*
* Create a new error code for use with fpi_device_verify_complete()
* and similar calls.
*/
GError *
fpi_device_error_new_msg (FpDeviceError device_error,
const gchar *msg,
...)
{
GError *error;
va_list args;
va_start (args, msg);
error = g_error_new_valist (FP_DEVICE_ERROR, device_error, msg, args);
va_end (args);
return error;
}
/**
* fpi_device_set_nr_enroll_stages:
* @device: The #FpDevice
* @enroll_stages: The number of enroll stages
*
* Updates the reported number of enroll stages that the device needs.
* If all supported devices have the same number of stages, then the
* value can simply be set in the class.
*/
void
fpi_device_set_nr_enroll_stages (FpDevice *device,
gint enroll_stages)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (enroll_stages > 0);
priv->nr_enroll_stages = enroll_stages;
g_object_notify (G_OBJECT (device), "nr-enroll-stages");
}
/**
* fpi_device_set_scan_type:
* @device: The #FpDevice
* @scan_type: The scan type of the device
*
* Updates the the scan type of the device from the default.
* If all supported devices have the same scan type, then the
* value can simply be set in the class.
*/
void
fpi_device_set_scan_type (FpDevice *device,
FpScanType scan_type)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
priv->scan_type = scan_type;
g_object_notify (G_OBJECT (device), "scan-type");
}
typedef struct
{
GSource source;
FpDevice *device;
} FpDeviceTimeoutSource;
static void
timeout_finalize (GSource *source)
{
FpDeviceTimeoutSource *timeout_source = (FpDeviceTimeoutSource *) source;
FpDevicePrivate *priv;
priv = fp_device_get_instance_private (timeout_source->device);
priv->sources = g_slist_remove (priv->sources, source);
}
static gboolean
timeout_dispatch (GSource *source, GSourceFunc gsource_func, gpointer user_data)
{
FpDeviceTimeoutSource *timeout_source = (FpDeviceTimeoutSource *) source;
FpTimeoutFunc callback = (FpTimeoutFunc) gsource_func;
callback (timeout_source->device, user_data);
return G_SOURCE_REMOVE;
}
static GSourceFuncs timeout_funcs = {
NULL, /* prepare */
NULL, /* check */
timeout_dispatch,
timeout_finalize,
NULL, NULL
};
/**
* fpi_device_add_timeout:
* @device: The #FpDevice
* @interval: The interval in milliseconds
* @func: The #FpTimeoutFunc to call on timeout
* @user_data: (nullable): User data to pass to the callback
* @destroy_notify: (nullable): #GDestroyNotify for @user_data
*
* Register a timeout to run. Drivers should always make sure that timers are
* cancelled when appropriate.
*
* Returns: (transfer none): A newly created and attached #GSource
*/
GSource *
fpi_device_add_timeout (FpDevice *device,
gint interval,
FpTimeoutFunc func,
gpointer user_data,
GDestroyNotify destroy_notify)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpDeviceTimeoutSource *source;
source = (FpDeviceTimeoutSource *) g_source_new (&timeout_funcs,
sizeof (FpDeviceTimeoutSource));
source->device = device;
g_source_attach (&source->source, NULL);
g_source_set_callback (&source->source, (GSourceFunc) func, user_data, destroy_notify);
g_source_set_ready_time (&source->source,
g_source_get_time (&source->source) + interval * (guint64) 1000);
priv->sources = g_slist_prepend (priv->sources, source);
g_source_unref (&source->source);
return &source->source;
}
/**
* fpi_device_get_usb_device:
* @device: The #FpDevice
*
* Get the #GUsbDevice for this #FpDevice. Only permissible to call if the
* #FpDevice is of type %FP_DEVICE_TYPE_USB.
*
* Returns: The #GUsbDevice
*/
GUsbDevice *
fpi_device_get_usb_device (FpDevice *device)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_USB, NULL);
return priv->usb_device;
}
/**
* fpi_device_get_udev_data:
* @device: The #FpDevice
* @subtype: Which subtype to get information about
*
* Get a subtype-specific hardware resource for this #FpDevice. Only permissible to call if the
* #FpDevice is of type %FP_DEVICE_TYPE_UDEV.
*
* Returns: Depends on @subtype; for SPIDEV/HIDRAW returns a path to the relevant device.
*/
gpointer
fpi_device_get_udev_data (FpDevice *device, FpiDeviceUdevSubtypeFlags subtype)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_UDEV, NULL);
switch (subtype)
{
case FPI_DEVICE_UDEV_SUBTYPE_HIDRAW:
return priv->udev_data.hidraw_path;
case FPI_DEVICE_UDEV_SUBTYPE_SPIDEV:
return priv->udev_data.spidev_path;
default:
g_return_val_if_reached (NULL);
return NULL;
}
}
/**
* fpi_device_get_virtual_env:
* @device: The #FpDevice
*
* Get the value of the environment variable that caused the virtual #FpDevice to be
* generated. Only permissible to call if the #FpDevice is of type %FP_DEVICE_TYPE_VIRTUAL.
*
* Returns: The value of the environment variable
*/
const gchar *
fpi_device_get_virtual_env (FpDevice *device)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_VIRTUAL, NULL);
return priv->virtual_env;
}
/**
* fpi_device_get_current_action:
* @device: The #FpDevice
*
* Get the currently ongoing action or %FPI_DEVICE_ACTION_NONE if there
* is no operation at this time.
*
* This is useful for drivers that might share code paths between different
* actions (e.g. verify and identify) and want to find out again later which
* action was started in the beginning.
*
* Returns: The ongoing #FpiDeviceAction
*/
FpiDeviceAction
fpi_device_get_current_action (FpDevice *device)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_val_if_fail (FP_IS_DEVICE (device), FPI_DEVICE_ACTION_NONE);
return priv->current_action;
}
/**
* fpi_device_action_is_cancelled:
* @device: The #FpDevice
*
* Checks whether the current action has been cancelled by the user.
* This is equivalent to first getting the cancellable using
* fpi_device_get_cancellable() and then checking whether it has been
* cancelled (if it is non-NULL).
*
* Returns: %TRUE if action should be cancelled
*/
gboolean
fpi_device_action_is_cancelled (FpDevice *device)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
GCancellable *cancellable;
g_return_val_if_fail (FP_IS_DEVICE (device), TRUE);
g_return_val_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE, TRUE);
cancellable = g_task_get_cancellable (priv->current_task);
return g_cancellable_is_cancelled (cancellable);
}
/**
* fpi_device_get_driver_data:
* @device: The #FpDevice
*
* Returns: The driver data from the #FpIdEntry table entry
*/
guint64
fpi_device_get_driver_data (FpDevice *device)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_val_if_fail (FP_IS_DEVICE (device), 0);
return priv->driver_data;
}
void
enroll_data_free (FpEnrollData *data)
{
if (data->enroll_progress_destroy)
data->enroll_progress_destroy (data->enroll_progress_data);
data->enroll_progress_data = NULL;
g_clear_object (&data->print);
g_free (data);
}
void
match_data_free (FpMatchData *data)
{
g_clear_object (&data->print);
g_clear_object (&data->match);
g_clear_error (&data->error);
if (data->match_destroy)
data->match_destroy (data->match_data);
data->match_data = NULL;
g_clear_object (&data->enrolled_print);
g_clear_pointer (&data->gallery, g_ptr_array_unref);
g_free (data);
}
/**
* fpi_device_get_enroll_data:
* @device: The #FpDevice
* @print: (out) (transfer none): The user provided template print
*
* Get data for enrollment.
*/
void
fpi_device_get_enroll_data (FpDevice *device,
FpPrint **print)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpEnrollData *data;
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_ENROLL);
data = g_task_get_task_data (priv->current_task);
g_assert (data);
if (print)
*print = data->print;
}
/**
* fpi_device_get_capture_data:
* @device: The #FpDevice
* @wait_for_finger: (out): Whether to wait for finger or not
*
* Get data for capture.
*/
void
fpi_device_get_capture_data (FpDevice *device,
gboolean *wait_for_finger)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_CAPTURE);
if (wait_for_finger)
*wait_for_finger = priv->wait_for_finger;
}
/**
* fpi_device_get_verify_data:
* @device: The #FpDevice
* @print: (out) (transfer none): The enrolled print
*
* Get data for verify.
*/
void
fpi_device_get_verify_data (FpDevice *device,
FpPrint **print)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpMatchData *data;
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_VERIFY);
data = g_task_get_task_data (priv->current_task);
g_assert (data);
if (print)
*print = data->enrolled_print;
}
/**
* fpi_device_get_identify_data:
* @device: The #FpDevice
* @prints: (out) (transfer none) (element-type FpPrint): The gallery of prints
*
* Get data for identify.
*/
void
fpi_device_get_identify_data (FpDevice *device,
GPtrArray **prints)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpMatchData *data;
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_IDENTIFY);
data = g_task_get_task_data (priv->current_task);
g_assert (data);
if (prints)
*prints = data->gallery;
}
/**
* fpi_device_get_delete_data:
* @device: The #FpDevice
* @print: (out) (transfer none): The print to delete
*
* Get data for delete.
*/
void
fpi_device_get_delete_data (FpDevice *device,
FpPrint **print)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_DELETE);
if (print)
*print = g_task_get_task_data (priv->current_task);
}
/**
* fpi_device_get_cancellable:
* @device: The #FpDevice
*
* Retrieve the #GCancellable that may cancel the currently ongoing operation. This
* is primarily useful to pass directly to e.g. fpi_usb_transfer_submit() for cancellable
* transfers.
* In many cases the cancel vfunc may be more convenient to react to cancellation in some
* way.
*
* Returns: (transfer none): The #GCancellable for the current action.
*/
GCancellable *
fpi_device_get_cancellable (FpDevice *device)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
g_return_val_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE, NULL);
return g_task_get_cancellable (priv->current_task);
}
static void
emit_removed_on_task_completed (FpDevice *device)
{
g_signal_emit_by_name (device, "removed");
}
/**
* fpi_device_remove:
* @device: The #FpDevice
*
* Called to signal to the #FpDevice that it has been unplugged (physically
* removed from the system).
*
* For USB devices, this API is called automatically by #FpContext.
*/
void
fpi_device_remove (FpDevice *device)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (!priv->is_removed);
priv->is_removed = TRUE;
g_object_notify (G_OBJECT (device), "removed");
/* If there is a pending action, we wait for it to fail, otherwise we
* immediately emit the "removed" signal. */
if (priv->current_task)
{
g_signal_connect_object (priv->current_task,
"notify::completed",
(GCallback) emit_removed_on_task_completed,
device,
G_CONNECT_SWAPPED);
}
else
{
g_signal_emit_by_name (device, "removed");
}
}
/**
* fpi_device_action_error:
* @device: The #FpDevice
* @error: The #GError to return
*
* Finish an ongoing action with an error. This is the same as calling
* the corresponding complete function such as fpi_device_open_complete()
* with an error set. If possible, use the correct complete function as
* that results in improved error detection.
*/
void
fpi_device_action_error (FpDevice *device,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE);
if (error != NULL)
{
g_autofree char *action_str = NULL;
action_str = g_enum_to_string (FPI_TYPE_DEVICE_ACTION, priv->current_action);
g_debug ("Device reported generic error (%s) during action; action was: %s",
error->message, action_str);
}
else
{
g_warning ("Device failed to pass an error to generic action error function");
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Device reported error but did not provide an error condition");
}
switch (priv->current_action)
{
case FPI_DEVICE_ACTION_PROBE:
fpi_device_probe_complete (device, NULL, NULL, error);
break;
case FPI_DEVICE_ACTION_OPEN:
fpi_device_open_complete (device, error);
break;
case FPI_DEVICE_ACTION_CLOSE:
fpi_device_close_complete (device, error);
break;
case FPI_DEVICE_ACTION_ENROLL:
fpi_device_enroll_complete (device, NULL, error);
break;
case FPI_DEVICE_ACTION_VERIFY:
fpi_device_verify_complete (device, error);
break;
case FPI_DEVICE_ACTION_IDENTIFY:
fpi_device_identify_complete (device, error);
break;
case FPI_DEVICE_ACTION_CAPTURE:
fpi_device_capture_complete (device, NULL, error);
break;
case FPI_DEVICE_ACTION_DELETE:
fpi_device_delete_complete (device, error);
break;
case FPI_DEVICE_ACTION_LIST:
fpi_device_list_complete (device, NULL, error);
break;
default:
case FPI_DEVICE_ACTION_NONE:
g_return_if_reached ();
break;
}
}
static void
clear_device_cancel_action (FpDevice *device)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_clear_pointer (&priv->current_idle_cancel_source, g_source_destroy);
if (priv->current_cancellable_id)
{
g_cancellable_disconnect (g_task_get_cancellable (priv->current_task),
priv->current_cancellable_id);
priv->current_cancellable_id = 0;
}
}
typedef enum _FpDeviceTaskReturnType {
FP_DEVICE_TASK_RETURN_INT,
FP_DEVICE_TASK_RETURN_BOOL,
FP_DEVICE_TASK_RETURN_OBJECT,
FP_DEVICE_TASK_RETURN_PTR_ARRAY,
FP_DEVICE_TASK_RETURN_ERROR,
} FpDeviceTaskReturnType;
typedef struct _FpDeviceTaskReturnData
{
FpDevice *device;
FpDeviceTaskReturnType type;
gpointer result;
} FpDeviceTaskReturnData;
static gboolean
fp_device_task_return_in_idle_cb (gpointer user_data)
{
FpDeviceTaskReturnData *data = user_data;
FpDevicePrivate *priv = fp_device_get_instance_private (data->device);
g_autofree char *action_str = NULL;
FpiDeviceAction action;
g_autoptr(GTask) task = NULL;
action_str = g_enum_to_string (FPI_TYPE_DEVICE_ACTION, priv->current_action);
g_debug ("Completing action %s in idle!", action_str);
task = g_steal_pointer (&priv->current_task);
action = priv->current_action;
priv->current_action = FPI_DEVICE_ACTION_NONE;
priv->current_task_idle_return_source = NULL;
if (action == FPI_DEVICE_ACTION_OPEN &&
data->type != FP_DEVICE_TASK_RETURN_ERROR)
{
priv->is_open = TRUE;
g_object_notify (G_OBJECT (data->device), "open");
}
else if (action == FPI_DEVICE_ACTION_CLOSE)
{
/* Always consider the device closed. Drivers should try hard to close the
* device. Generally, e.g. cancellations should be ignored.
*/
priv->is_open = FALSE;
g_object_notify (G_OBJECT (data->device), "open");
}
/* Return FP_DEVICE_ERROR_REMOVED if the device is removed,
* with the exception of a successful open, which is an odd corner case. */
if (priv->is_removed &&
((action != FPI_DEVICE_ACTION_OPEN) ||
(action == FPI_DEVICE_ACTION_OPEN && data->type == FP_DEVICE_TASK_RETURN_ERROR)))
{
g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_REMOVED));
/* NOTE: The removed signal will be emitted from the GTask
* notify::completed if that is necessary. */
return G_SOURCE_REMOVE;
}
switch (data->type)
{
case FP_DEVICE_TASK_RETURN_INT:
g_task_return_int (task, GPOINTER_TO_INT (data->result));
break;
case FP_DEVICE_TASK_RETURN_BOOL:
g_task_return_boolean (task, GPOINTER_TO_UINT (data->result));
break;
case FP_DEVICE_TASK_RETURN_OBJECT:
g_task_return_pointer (task, g_steal_pointer (&data->result),
g_object_unref);
break;
case FP_DEVICE_TASK_RETURN_PTR_ARRAY:
g_task_return_pointer (task, g_steal_pointer (&data->result),
(GDestroyNotify) g_ptr_array_unref);
break;
case FP_DEVICE_TASK_RETURN_ERROR:
g_task_return_error (task, g_steal_pointer (&data->result));
break;
default:
g_assert_not_reached ();
}
return G_SOURCE_REMOVE;
}
static void
fpi_device_task_return_data_free (FpDeviceTaskReturnData *data)
{
if (data->result)
{
switch (data->type)
{
case FP_DEVICE_TASK_RETURN_INT:
case FP_DEVICE_TASK_RETURN_BOOL:
break;
case FP_DEVICE_TASK_RETURN_OBJECT:
g_clear_object ((GObject **) &data->result);
break;
case FP_DEVICE_TASK_RETURN_PTR_ARRAY:
g_clear_pointer ((GPtrArray **) &data->result, g_ptr_array_unref);
break;
case FP_DEVICE_TASK_RETURN_ERROR:
g_clear_error ((GError **) &data->result);
break;
default:
g_assert_not_reached ();
}
}
g_object_unref (data->device);
g_free (data);
}
static void
fpi_device_return_task_in_idle (FpDevice *device,
FpDeviceTaskReturnType return_type,
gpointer return_data)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpDeviceTaskReturnData *data;
data = g_new0 (FpDeviceTaskReturnData, 1);
data->device = g_object_ref (device);
data->type = return_type;
data->result = return_data;
priv->current_task_idle_return_source = g_idle_source_new ();
g_source_set_priority (priv->current_task_idle_return_source,
g_task_get_priority (priv->current_task));
g_source_set_callback (priv->current_task_idle_return_source,
fp_device_task_return_in_idle_cb,
data,
(GDestroyNotify) fpi_device_task_return_data_free);
g_source_attach (priv->current_task_idle_return_source, NULL);
g_source_unref (priv->current_task_idle_return_source);
}
/**
* fpi_device_probe_complete:
* @device: The #FpDevice
* @device_id: Unique ID for the device or %NULL
* @device_name: Human readable name or %NULL for driver name
* @error: The #GError or %NULL on success
*
* Finish an ongoing probe operation. If error is %NULL success is assumed.
*/
void
fpi_device_probe_complete (FpDevice *device,
const gchar *device_id,
const gchar *device_name,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_PROBE);
g_debug ("Device reported probe completion");
clear_device_cancel_action (device);
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE);
if (!error)
{
if (device_id)
{
g_clear_pointer (&priv->device_id, g_free);
priv->device_id = g_strdup (device_id);
g_object_notify (G_OBJECT (device), "device-id");
}
if (device_name)
{
g_clear_pointer (&priv->device_name, g_free);
priv->device_name = g_strdup (device_name);
g_object_notify (G_OBJECT (device), "name");
}
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
GUINT_TO_POINTER (TRUE));
}
else
{
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
}
}
/**
* fpi_device_open_complete:
* @device: The #FpDevice
* @error: The #GError or %NULL on success
*
* Finish an ongoing open operation. If error is %NULL success is assumed.
*/
void
fpi_device_open_complete (FpDevice *device, GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_OPEN);
g_debug ("Device reported open completion");
clear_device_cancel_action (device);
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE);
if (!error)
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
GUINT_TO_POINTER (TRUE));
else
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
}
/**
* fpi_device_close_complete:
* @device: The #FpDevice
* @error: The #GError or %NULL on success
*
* Finish an ongoing close operation. If error is %NULL success is assumed.
*/
void
fpi_device_close_complete (FpDevice *device, GError *error)
{
GError *nested_error = NULL;
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_CLOSE);
g_debug ("Device reported close completion");
clear_device_cancel_action (device);
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE);
switch (priv->type)
{
case FP_DEVICE_TYPE_USB:
if (!g_usb_device_close (priv->usb_device, &nested_error))
{
if (error == NULL)
error = nested_error;
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
return;
}
break;
case FP_DEVICE_TYPE_VIRTUAL:
case FP_DEVICE_TYPE_UDEV:
break;
default:
g_assert_not_reached ();
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR,
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
return;
}
if (!error)
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
GUINT_TO_POINTER (TRUE));
else
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
}
/**
* fpi_device_enroll_complete:
* @device: The #FpDevice
* @print: (nullable) (transfer full): The #FpPrint or %NULL on failure
* @error: The #GError or %NULL on success
*
* Finish an ongoing enroll operation. The #FpPrint can be stored by the
* caller for later verification.
*/
void
fpi_device_enroll_complete (FpDevice *device, FpPrint *print, GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_ENROLL);
g_debug ("Device reported enroll completion");
clear_device_cancel_action (device);
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE);
if (!error)
{
if (FP_IS_PRINT (print))
{
FpiPrintType print_type;
g_autofree char *finger_str = NULL;
g_object_get (print, "fpi-type", &print_type, NULL);
if (print_type == FPI_PRINT_UNDEFINED)
{
g_warning ("Driver did not set the type on the returned print!");
g_clear_object (&print);
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Driver provided incorrect print data!");
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
return;
}
finger_str = g_enum_to_string (FP_TYPE_FINGER, fp_print_get_finger (print));
g_debug ("Print for finger %s enrolled", finger_str);
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_OBJECT, print);
}
else
{
g_warning ("Driver did not provide a valid print and failed to provide an error!");
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Driver failed to provide print data!");
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
}
}
else
{
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
if (FP_IS_PRINT (print))
{
g_warning ("Driver passed an error but also provided a print, returning error!");
g_object_unref (print);
}
}
}
/**
* fpi_device_verify_complete:
* @device: The #FpDevice
* @error: A #GError if result is %FPI_MATCH_ERROR
*
* Finish an ongoing verify operation. The returned print should be
* representing the new scan and not the one passed for verification.
*
* Note that @error should only be set for actual errors. In the case
* of retry errors, report these using fpi_device_verify_report()
* and then call this function without any error argument.
*/
void
fpi_device_verify_complete (FpDevice *device,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpMatchData *data;
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_VERIFY);
g_debug ("Device reported verify completion");
data = g_task_get_task_data (priv->current_task);
clear_device_cancel_action (device);
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE);
if (!error)
{
if (!data->result_reported)
{
g_warning ("Driver reported successful verify complete but did not report the result earlier. Reporting error instead");
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR,
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
}
else if (data->error)
{
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, g_steal_pointer (&data->error));
}
else
{
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_INT,
GINT_TO_POINTER (data->match != NULL ? FPI_MATCH_SUCCESS : FPI_MATCH_FAIL));
}
}
else
{
/* Replace a retry error with a general error, this is a driver bug. */
if (error->domain == FP_DEVICE_RETRY)
{
g_warning ("Driver reported a retry error to fpi_device_verify_complete. "
"This is not permissible and needs to be reported using "
"fpi_device_verify_report, reporting general verification "
"failure instead.");
g_clear_error (&error);
error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
}
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
}
}
/**
* fpi_device_identify_complete:
* @device: The #FpDevice
* @error: The #GError or %NULL on success
*
* Finish an ongoing identify operation. The match that was identified is
* returned in @match. The @print parameter returns the newly created scan
* that was used for matching.
*/
void
fpi_device_identify_complete (FpDevice *device,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpMatchData *data;
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_IDENTIFY);
g_debug ("Device reported identify completion");
data = g_task_get_task_data (priv->current_task);
clear_device_cancel_action (device);
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE);
if (!error)
{
if (!data->result_reported)
{
g_warning ("Driver reported successful identify complete but did not report the result earlier. Reporting error instead");
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR,
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
}
else if (data->error)
{
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, g_steal_pointer (&data->error));
}
else
{
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, GUINT_TO_POINTER (TRUE));
}
}
else
{
/* Replace a retry error with a general error, this is a driver bug. */
if (error->domain == FP_DEVICE_RETRY)
{
g_warning ("Driver reported a retry error to fpi_device_identify_complete. "
"This is not permissible and needs to be reported using "
"fpi_device_identify_report, reporting general identification "
"failure instead.");
g_clear_error (&error);
error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL);
}
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
}
}
/**
* fpi_device_capture_complete:
* @device: The #FpDevice
* @image: The #FpImage, or %NULL on error
* @error: The #GError or %NULL on success
*
* Finish an ongoing capture operation.
*/
void
fpi_device_capture_complete (FpDevice *device,
FpImage *image,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_CAPTURE);
g_debug ("Device reported capture completion");
clear_device_cancel_action (device);
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE);
if (!error)
{
if (image)
{
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_OBJECT, image);
}
else
{
g_warning ("Driver did not provide an error for a failed capture operation!");
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Driver failed to provide an error!");
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
}
}
else
{
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
if (image)
{
g_warning ("Driver passed an error but also provided an image, returning error!");
g_clear_object (&image);
}
}
}
/**
* fpi_device_delete_complete:
* @device: The #FpDevice
* @error: The #GError or %NULL on success
*
* Finish an ongoing delete operation.
*/
void
fpi_device_delete_complete (FpDevice *device,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_DELETE);
g_debug ("Device reported deletion completion");
clear_device_cancel_action (device);
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE);
if (!error)
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
GUINT_TO_POINTER (TRUE));
else
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
}
/**
* fpi_device_list_complete:
* @device: The #FpDevice
* @prints: (element-type FpPrint) (transfer container): Possibly empty array of prints or %NULL on error
* @error: The #GError or %NULL on success
*
* Finish an ongoing list operation.
*
* Please note that the @prints array will be free'ed using
* g_ptr_array_unref() and the elements are destroyed automatically.
* As such, you must use g_ptr_array_new_with_free_func() with
* g_object_unref() as free func to create the array.
*/
void
fpi_device_list_complete (FpDevice *device,
GPtrArray *prints,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_LIST);
g_debug ("Device reported listing completion");
clear_device_cancel_action (device);
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE);
if (prints && error)
{
g_warning ("Driver reported back prints and error, ignoring prints");
g_clear_pointer (&prints, g_ptr_array_unref);
}
else if (!prints && !error)
{
g_warning ("Driver did not pass array but failed to provide an error");
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Driver failed to provide a list of prints");
}
if (!error)
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_PTR_ARRAY, prints);
else
fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
}
/**
* fpi_device_enroll_progress:
* @device: The #FpDevice
* @completed_stages: The number of stages that are completed at this point
* @print: (transfer floating): The #FpPrint for the newly completed stage or %NULL on failure
* @error: (transfer full): The #GError or %NULL on success
*
* Notify about the progress of the enroll operation. This is important for UI interaction.
* The passed error may be used if a scan needs to be retried, use fpi_device_retry_new().
*/
void
fpi_device_enroll_progress (FpDevice *device,
gint completed_stages,
FpPrint *print,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpEnrollData *data;
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_ENROLL);
g_return_if_fail (error == NULL || error->domain == FP_DEVICE_RETRY);
g_debug ("Device reported enroll progress, reported %i of %i have been completed", completed_stages, priv->nr_enroll_stages);
if (print)
g_object_ref_sink (print);
if (error && print)
{
g_warning ("Driver passed an error and also provided a print, returning error!");
g_clear_object (&print);
}
data = g_task_get_task_data (priv->current_task);
if (data->enroll_progress_cb)
{
data->enroll_progress_cb (device,
completed_stages,
print,
data->enroll_progress_data,
error);
}
g_clear_error (&error);
g_clear_object (&print);
}
/**
* fpi_device_verify_report:
* @device: The #FpDevice
* @result: The #FpiMatchResult of the operation
* @print: (transfer floating) The scanned #FpPrint
* @error: A #GError if result is %FPI_MATCH_ERROR
*
* Report the result of a verify operation. Note that the passed @error must be
* a retry error with the %FP_DEVICE_RETRY domain. For all other error cases,
* the error should passed to fpi_device_verify_complete().
*/
void
fpi_device_verify_report (FpDevice *device,
FpiMatchResult result,
FpPrint *print,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpMatchData *data = g_task_get_task_data (priv->current_task);
gboolean call_cb = TRUE;
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_VERIFY);
g_return_if_fail (data->result_reported == FALSE);
data->result_reported = TRUE;
g_debug ("Device reported verify result");
if (print)
print = g_object_ref_sink (print);
if (error || result == FPI_MATCH_ERROR)
{
if (result != FPI_MATCH_ERROR)
g_warning ("Driver reported an error code without setting match result to error!");
if (error == NULL)
{
g_warning ("Driver reported an error without specifying a retry code, assuming general retry error!");
error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL);
}
if (print)
{
g_warning ("Driver reported a print together with an error!");
g_clear_object (&print);
}
data->error = error;
if (error->domain != FP_DEVICE_RETRY)
{
g_warning ("Driver reported a verify error that was not in the retry domain, delaying report!");
call_cb = FALSE;
}
}
else
{
if (result == FPI_MATCH_SUCCESS)
{
fpi_device_get_verify_data (device, &data->match);
g_object_ref (data->match);
}
data->print = g_steal_pointer (&print);
}
if (call_cb && data->match_cb)
data->match_cb (device, data->match, data->print, data->match_data, data->error);
}
/**
* fpi_device_identify_report:
* @device: The #FpDevice
* @match: (transfer none): The #FpPrint from the gallery that matched
* @print: (transfer floating): The scanned #FpPrint
* @error: A #GError if result is %FPI_MATCH_ERROR
*
* Report the result of a identify operation. Note that the passed @error must be
* a retry error with the %FP_DEVICE_RETRY domain. For all other error cases,
* the error should passed to fpi_device_identify_complete().
*/
void
fpi_device_identify_report (FpDevice *device,
FpPrint *match,
FpPrint *print,
GError *error)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpMatchData *data = g_task_get_task_data (priv->current_task);
gboolean call_cb = TRUE;
g_return_if_fail (FP_IS_DEVICE (device));
g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_IDENTIFY);
g_return_if_fail (data->result_reported == FALSE);
data->result_reported = TRUE;
if (match)
g_object_ref (match);
if (print)
print = g_object_ref_sink (print);
if (match && !g_ptr_array_find (data->gallery, match, NULL))
{
g_warning ("Driver reported a match to a print that was not in the gallery, ignoring match.");
g_clear_object (&match);
}
g_debug ("Device reported identify result");
if (error)
{
if (match != NULL)
{
g_warning ("Driver reported an error code but also provided a match!");
g_clear_object (&match);
}
if (print)
{
g_warning ("Driver reported a print together with an error!");
g_clear_object (&print);
}
data->error = error;
if (error->domain != FP_DEVICE_RETRY)
{
g_warning ("Driver reported a verify error that was not in the retry domain, delaying report!");
call_cb = FALSE;
}
}
else
{
if (match)
data->match = g_steal_pointer (&match);
if (print)
data->print = g_steal_pointer (&print);
}
if (call_cb && data->match_cb)
data->match_cb (device, data->match, data->print, data->match_data, data->error);
}
/**
* fpi_device_report_finger_status:
* @device: The #FpDevice
* @finger_status: The current #FpFingerStatusFlags to report
*
* Report the finger status for the @device.
* This can be used by UI to give a feedback
*
* Returns: %TRUE if changed
*/
gboolean
fpi_device_report_finger_status (FpDevice *device,
FpFingerStatusFlags finger_status)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_autofree char *status_string = NULL;
if (priv->finger_status == finger_status)
return FALSE;
status_string = g_flags_to_string (FP_TYPE_FINGER_STATUS_FLAGS, finger_status);
fp_dbg ("Device reported finger status change: %s", status_string);
priv->finger_status = finger_status;
g_object_notify (G_OBJECT (device), "finger-status");
return TRUE;
}
/**
* fpi_device_report_finger_status_changes:
* @device: The #FpDevice
* @added_status: The #FpFingerStatusFlags to add
* @removed_status: The #FpFingerStatusFlags to remove
*
* Report the finger status for the @device adding the @added_status flags
* and removing the @removed_status flags.
*
* This can be used by UI to give a feedback
*
* Returns: %TRUE if changed
*/
gboolean
fpi_device_report_finger_status_changes (FpDevice *device,
FpFingerStatusFlags added_status,
FpFingerStatusFlags removed_status)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
FpFingerStatusFlags finger_status = priv->finger_status;
finger_status |= added_status;
finger_status &= ~removed_status;
return fpi_device_report_finger_status (device, finger_status);
}