From 3b368f67de150199ba2dfb083ab22e7fd2e4204f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 10 Dec 2019 20:24:04 +0100 Subject: [PATCH 105/181] fp-device: Move fpi code into its own unit that can be compiled a part In order to be able to test the private device code (used by drivers) we need to have that split a part in a different .c file so that we can compile it alone and link with it both the shared library and the test executables. Redefine fp_device_get_instance_private for private usage, not to move the private struct as part of FpDevice. --- libfprint/fp-device-private.h | 65 ++ libfprint/fp-device.c | 1186 +-------------------------------- libfprint/fpi-device.c | 1177 ++++++++++++++++++++++++++++++++ libfprint/meson.build | 1 + 4 files changed, 1245 insertions(+), 1184 deletions(-) create mode 100644 libfprint/fp-device-private.h create mode 100644 libfprint/fpi-device.c diff --git a/libfprint/fp-device-private.h b/libfprint/fp-device-private.h new file mode 100644 index 0000000..65fb1cb --- /dev/null +++ b/libfprint/fp-device-private.h @@ -0,0 +1,65 @@ +/* + * FpDevice - A fingerprint reader device + * Copyright (C) 2019 Benjamin Berg + * Copyright (C) 2019 Marco Trevisan + * + * 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 + */ + +#pragma once + +#include "fpi-device.h" + +typedef struct +{ + FpDeviceType type; + + GUsbDevice *usb_device; + const gchar *virtual_env; + + gboolean is_open; + + gchar *device_id; + gchar *device_name; + FpScanType scan_type; + + guint64 driver_data; + + gint nr_enroll_stages; + GSList *sources; + + /* We always make sure that only one task is run at a time. */ + FpDeviceAction current_action; + GTask *current_task; + GAsyncReadyCallback current_user_cb; + gulong current_cancellable_id; + GSource *current_idle_cancel_source; + GSource *current_task_idle_return_source; + + /* State for tasks */ + gboolean wait_for_finger; +} FpDevicePrivate; + + +typedef struct +{ + FpPrint *print; + + FpEnrollProgress enroll_progress_cb; + gpointer enroll_progress_data; + GDestroyNotify enroll_progress_destroy; +} FpEnrollData; + +void enroll_data_free (FpEnrollData *enroll_data); diff --git a/libfprint/fp-device.c b/libfprint/fp-device.c index 91a3aae..c49e5a9 100644 --- a/libfprint/fp-device.c +++ b/libfprint/fp-device.c @@ -21,17 +21,7 @@ #define FP_COMPONENT "device" #include "fpi-log.h" -#include "fpi-device.h" - -/** - * SECTION: fp-device - * @title: FpDevice - * @short_description: Fingerprint device handling - * - * The #FpDevice object allows you to interact with fingerprint readers. - * Befor doing any other operation you need to fp_device_open() the device - * and after you are done you need to fp_device_close() it again. - */ +#include "fp-device-private.h" /** * SECTION: fpi-device @@ -46,36 +36,6 @@ * Also see the public #FpDevice routines. */ -typedef struct -{ - FpDeviceType type; - - GUsbDevice *usb_device; - const gchar *virtual_env; - - gboolean is_open; - - gchar *device_id; - gchar *device_name; - FpScanType scan_type; - - guint64 driver_data; - - gint nr_enroll_stages; - GSList *sources; - - /* We always make sure that only one task is run at a time. */ - FpDeviceAction current_action; - GTask *current_task; - GAsyncReadyCallback current_user_cb; - gulong current_cancellable_id; - GSource *current_idle_cancel_source; - GSource *current_task_idle_return_source; - - /* State for tasks */ - gboolean wait_for_finger; -} FpDevicePrivate; - static void fp_device_async_initable_iface_init (GAsyncInitableIface *iface); G_DEFINE_TYPE_EXTENDED (FpDevice, fp_device, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, @@ -99,27 +59,6 @@ enum { static GParamSpec *properties[N_PROPS]; -typedef struct -{ - FpPrint *print; - - FpEnrollProgress enroll_progress_cb; - gpointer enroll_progress_data; - GDestroyNotify enroll_progress_destroy; -} FpEnrollData; - -static void -enroll_data_free (gpointer free_data) -{ - FpEnrollData *data = free_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); -} - /** * fp_device_retry_quark: * @@ -134,150 +73,6 @@ G_DEFINE_QUARK (fp - device - retry - quark, fp_device_retry) **/ G_DEFINE_QUARK (fp - device - error - quark, fp_device_error) -/** - * 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 occured!"; - 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; - - default: - g_warning ("Unsupported error, returning general error instead!"); - error = FP_DEVICE_ERROR_GENERAL; - msg = "An unspecified error occured!"; - } - - 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 - * - * 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 - * - * 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; -} - static gboolean fp_device_cancel_in_idle_cb (gpointer user_data) { @@ -330,21 +125,6 @@ maybe_cancel_on_cancelled (FpDevice *device, NULL); } -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; - } -} - static void fp_device_constructed (GObject *object) { @@ -976,7 +756,7 @@ fp_device_enroll (FpDevice *device, data->enroll_progress_data = progress_data; // Attach the progress data as task data so that it is destroyed - g_task_set_task_data (priv->current_task, data, enroll_data_free); + g_task_set_task_data (priv->current_task, data, (GDestroyNotify) enroll_data_free); FP_DEVICE_GET_CLASS (device)->enroll (device); } @@ -1406,968 +1186,6 @@ fp_device_list_prints_finish (FpDevice *device, return g_task_propagate_pointer (G_TASK (result), error); } -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 -}; - -/* Private API functions */ - -/** - * 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)); - - priv->nr_enroll_stages = enroll_stages; - g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_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_by_pspec (G_OBJECT (device), properties[PROP_SCAN_TYPE]); -} - -/** - * 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_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 %FP_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 #FpDeviceAction - */ -FpDeviceAction -fpi_device_get_current_action (FpDevice *device) -{ - FpDevicePrivate *priv = fp_device_get_instance_private (device); - - g_return_val_if_fail (FP_IS_DEVICE (device), FP_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 != FP_DEVICE_ACTION_NONE, TRUE); - - cancellable = g_task_get_cancellable (priv->current_task); - - return cancellable ? g_cancellable_is_cancelled (cancellable) : FALSE; -} - -/** - * 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; -} - -/** - * 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 == FP_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 == FP_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); - - g_return_if_fail (FP_IS_DEVICE (device)); - g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_VERIFY); - - if (print) - *print = g_task_get_task_data (priv->current_task); -} - -/** - * 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); - - g_return_if_fail (FP_IS_DEVICE (device)); - g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_IDENTIFY); - - if (prints) - *prints = g_task_get_task_data (priv->current_task); -} - -/** - * 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 == FP_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 != FP_DEVICE_ACTION_NONE, NULL); - - return g_task_get_cancellable (priv->current_task); -} - -/** - * 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 != FP_DEVICE_ACTION_NONE); - - if (error != NULL) - { - g_debug ("Device reported generic error during action; action was: %i", priv->current_action); - } - 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 FP_DEVICE_ACTION_PROBE: - fpi_device_probe_complete (device, NULL, NULL, error); - break; - - case FP_DEVICE_ACTION_OPEN: - fpi_device_open_complete (device, error); - break; - - case FP_DEVICE_ACTION_CLOSE: - fpi_device_close_complete (device, error); - break; - - case FP_DEVICE_ACTION_ENROLL: - fpi_device_enroll_complete (device, NULL, error); - break; - - case FP_DEVICE_ACTION_VERIFY: - fpi_device_verify_complete (device, FPI_MATCH_ERROR, NULL, error); - break; - - case FP_DEVICE_ACTION_IDENTIFY: - fpi_device_identify_complete (device, NULL, NULL, error); - break; - - case FP_DEVICE_ACTION_CAPTURE: - fpi_device_capture_complete (device, NULL, error); - break; - - case FP_DEVICE_ACTION_DELETE: - fpi_device_delete_complete (device, error); - break; - - case FP_DEVICE_ACTION_LIST: - fpi_device_list_complete (device, NULL, error); - break; - - default: - case FP_DEVICE_ACTION_NONE: - g_return_if_reached (); - break; - } -} - -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_autoptr(GTask) task = NULL; - - g_debug ("Completing action %d in idle!", priv->current_action); - - task = g_steal_pointer (&priv->current_task); - priv->current_action = FP_DEVICE_ACTION_NONE; - priv->current_task_idle_return_source = NULL; - - 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, data->result, g_object_unref); - break; - - case FP_DEVICE_TASK_RETURN_PTR_ARRAY: - g_task_return_pointer (task, data->result, - (GDestroyNotify) g_ptr_array_unref); - break; - - case FP_DEVICE_TASK_RETURN_ERROR: - g_task_return_error (task, data->result); - break; - - default: - g_assert_not_reached (); - } - - return G_SOURCE_REMOVE; -} - -static void -fp_device_task_return_data_free (FpDeviceTaskReturnData *data) -{ - g_object_unref (data->device); - g_free (data); -} - -static void -fp_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) fp_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 == FP_DEVICE_ACTION_PROBE); - - g_debug ("Device reported probe completion"); - - clear_device_cancel_action (device); - - if (!error) - { - if (device_id) - { - g_clear_pointer (&priv->device_id, g_free); - priv->device_id = g_strdup (device_id); - g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_DEVICE_ID]); - } - if (device_name) - { - g_clear_pointer (&priv->device_name, g_free); - priv->device_name = g_strdup (device_name); - g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_NAME]); - } - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, - GUINT_TO_POINTER (TRUE)); - } - else - { - fp_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 == FP_DEVICE_ACTION_OPEN); - - g_debug ("Device reported open completion"); - - clear_device_cancel_action (device); - - if (!error) - { - priv->is_open = TRUE; - g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_OPEN]); - } - - if (!error) - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, - GUINT_TO_POINTER (TRUE)); - else - fp_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 == FP_DEVICE_ACTION_CLOSE); - - g_debug ("Device reported close completion"); - - clear_device_cancel_action (device); - priv->is_open = FALSE; - g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_OPEN]); - - switch (priv->type) - { - case FP_DEVICE_TYPE_USB: - if (!g_usb_device_close (priv->usb_device, &nested_error)) - { - if (error == NULL) - error = nested_error; - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); - return; - } - break; - - case FP_DEVICE_TYPE_VIRTUAL: - break; - - default: - g_assert_not_reached (); - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, - fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); - return; - } - - if (!error) - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, - GUINT_TO_POINTER (TRUE)); - else - fp_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 == FP_DEVICE_ACTION_ENROLL); - - g_debug ("Device reported enroll completion"); - - clear_device_cancel_action (device); - - if (!error) - { - if (FP_IS_PRINT (print)) - { - fp_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!"); - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); - } - } - else - { - fp_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 - * @result: The #FpiMatchResult of the operation - * @print: The scanned #FpPrint - * @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. - */ -void -fpi_device_verify_complete (FpDevice *device, - FpiMatchResult result, - 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 == FP_DEVICE_ACTION_VERIFY); - - g_debug ("Device reported verify completion"); - - clear_device_cancel_action (device); - - g_object_set_data_full (G_OBJECT (priv->current_task), - "print", - print, - g_object_unref); - - if (!error) - { - if (result != FPI_MATCH_ERROR) - { - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_INT, - GINT_TO_POINTER (result)); - } - else - { - g_warning ("Driver did not provide an error for a failed verify operation!"); - error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, - "Driver failed to provide an error!"); - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); - } - } - else - { - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); - if (result != FPI_MATCH_ERROR) - { - g_warning ("Driver passed an error but also provided a match result, returning error!"); - g_object_unref (print); - } - } -} - -/** - * fpi_device_identify_complete: - * @device: The #FpDevice - * @match: The matching #FpPrint from the passed gallery, or %NULL if none matched - * @print: The scanned #FpPrint, may be %NULL - * @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, - FpPrint *match, - 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 == FP_DEVICE_ACTION_IDENTIFY); - - g_debug ("Device reported identify completion"); - - clear_device_cancel_action (device); - - g_object_set_data_full (G_OBJECT (priv->current_task), - "print", - print, - g_object_unref); - g_object_set_data_full (G_OBJECT (priv->current_task), - "match", - match, - g_object_unref); - if (!error) - { - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, - GUINT_TO_POINTER (TRUE)); - } - else - { - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); - if (match) - { - g_warning ("Driver passed an error but also provided a match result, returning error!"); - g_clear_object (&match); - } - } -} - - -/** - * 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 == FP_DEVICE_ACTION_CAPTURE); - - g_debug ("Device reported capture completion"); - - clear_device_cancel_action (device); - - if (!error) - { - if (image) - { - fp_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!"); - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); - } - } - else - { - fp_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 == FP_DEVICE_ACTION_DELETE); - - g_debug ("Device reported deletion completion"); - - clear_device_cancel_action (device); - - if (!error) - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, - GUINT_TO_POINTER (TRUE)); - else - fp_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 == FP_DEVICE_ACTION_LIST); - - g_debug ("Device reported listing completion"); - - clear_device_cancel_action (device); - - 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) - fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_PTR_ARRAY, prints); - else - fp_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: The #FpPrint for the newly completed stage or %NULL on failure - * @error: 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 == FP_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 (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); -} - - static void async_result_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { diff --git a/libfprint/fpi-device.c b/libfprint/fpi-device.c new file mode 100644 index 0000000..3eee062 --- /dev/null +++ b/libfprint/fpi-device.c @@ -0,0 +1,1177 @@ +/* + * FpDevice - A fingerprint reader device - Private APIs + * Copyright (C) 2019 Benjamin Berg + * Copyright (C) 2019 Marco Trevisan + * + * 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 availabe 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_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 occured!"; + 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; + + default: + g_warning ("Unsupported error, returning general error instead!"); + error = FP_DEVICE_ERROR_GENERAL; + msg = "An unspecified error occured!"; + } + + 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)); + + 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_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 %FP_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 #FpDeviceAction + */ +FpDeviceAction +fpi_device_get_current_action (FpDevice *device) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + g_return_val_if_fail (FP_IS_DEVICE (device), FP_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 != FP_DEVICE_ACTION_NONE, TRUE); + + cancellable = g_task_get_cancellable (priv->current_task); + + return cancellable ? g_cancellable_is_cancelled (cancellable) : FALSE; +} + +/** + * 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); +} + +/** + * 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 == FP_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 == FP_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); + + g_return_if_fail (FP_IS_DEVICE (device)); + g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_VERIFY); + + if (print) + *print = g_task_get_task_data (priv->current_task); +} + +/** + * 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); + + g_return_if_fail (FP_IS_DEVICE (device)); + g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_IDENTIFY); + + if (prints) + *prints = g_task_get_task_data (priv->current_task); +} + +/** + * 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 == FP_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 != FP_DEVICE_ACTION_NONE, NULL); + + return g_task_get_cancellable (priv->current_task); +} + +/** + * 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 != FP_DEVICE_ACTION_NONE); + + if (error != NULL) + { + g_debug ("Device reported generic error during action; action was: %i", priv->current_action); + } + 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 FP_DEVICE_ACTION_PROBE: + fpi_device_probe_complete (device, NULL, NULL, error); + break; + + case FP_DEVICE_ACTION_OPEN: + fpi_device_open_complete (device, error); + break; + + case FP_DEVICE_ACTION_CLOSE: + fpi_device_close_complete (device, error); + break; + + case FP_DEVICE_ACTION_ENROLL: + fpi_device_enroll_complete (device, NULL, error); + break; + + case FP_DEVICE_ACTION_VERIFY: + fpi_device_verify_complete (device, FPI_MATCH_ERROR, NULL, error); + break; + + case FP_DEVICE_ACTION_IDENTIFY: + fpi_device_identify_complete (device, NULL, NULL, error); + break; + + case FP_DEVICE_ACTION_CAPTURE: + fpi_device_capture_complete (device, NULL, error); + break; + + case FP_DEVICE_ACTION_DELETE: + fpi_device_delete_complete (device, error); + break; + + case FP_DEVICE_ACTION_LIST: + fpi_device_list_complete (device, NULL, error); + break; + + default: + case FP_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_autoptr(GTask) task = NULL; + + g_debug ("Completing action %d in idle!", priv->current_action); + + task = g_steal_pointer (&priv->current_task); + priv->current_action = FP_DEVICE_ACTION_NONE; + priv->current_task_idle_return_source = NULL; + + 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, data->result, g_object_unref); + break; + + case FP_DEVICE_TASK_RETURN_PTR_ARRAY: + g_task_return_pointer (task, data->result, + (GDestroyNotify) g_ptr_array_unref); + break; + + case FP_DEVICE_TASK_RETURN_ERROR: + g_task_return_error (task, data->result); + break; + + default: + g_assert_not_reached (); + } + + return G_SOURCE_REMOVE; +} + +static void +fpi_device_task_return_data_free (FpDeviceTaskReturnData *data) +{ + 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 == FP_DEVICE_ACTION_PROBE); + + g_debug ("Device reported probe completion"); + + clear_device_cancel_action (device); + + 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 == FP_DEVICE_ACTION_OPEN); + + g_debug ("Device reported open completion"); + + clear_device_cancel_action (device); + + if (!error) + { + priv->is_open = TRUE; + g_object_notify (G_OBJECT (device), "open"); + } + + 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 == FP_DEVICE_ACTION_CLOSE); + + g_debug ("Device reported close completion"); + + clear_device_cancel_action (device); + priv->is_open = FALSE; + g_object_notify (G_OBJECT (device), "open"); + + 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: + 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 == FP_DEVICE_ACTION_ENROLL); + + g_debug ("Device reported enroll completion"); + + clear_device_cancel_action (device); + + if (!error) + { + if (FP_IS_PRINT (print)) + { + 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 + * @result: The #FpiMatchResult of the operation + * @print: The scanned #FpPrint + * @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. + */ +void +fpi_device_verify_complete (FpDevice *device, + FpiMatchResult result, + 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 == FP_DEVICE_ACTION_VERIFY); + + g_debug ("Device reported verify completion"); + + clear_device_cancel_action (device); + + g_object_set_data_full (G_OBJECT (priv->current_task), + "print", + print, + g_object_unref); + + if (!error) + { + if (result != FPI_MATCH_ERROR) + { + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_INT, + GINT_TO_POINTER (result)); + } + else + { + g_warning ("Driver did not provide an error for a failed verify 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 (result != FPI_MATCH_ERROR) + { + g_warning ("Driver passed an error but also provided a match result, returning error!"); + g_object_unref (print); + } + } +} + +/** + * fpi_device_identify_complete: + * @device: The #FpDevice + * @match: The matching #FpPrint from the passed gallery, or %NULL if none matched + * @print: The scanned #FpPrint, may be %NULL + * @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, + FpPrint *match, + 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 == FP_DEVICE_ACTION_IDENTIFY); + + g_debug ("Device reported identify completion"); + + clear_device_cancel_action (device); + + g_object_set_data_full (G_OBJECT (priv->current_task), + "print", + print, + g_object_unref); + g_object_set_data_full (G_OBJECT (priv->current_task), + "match", + match, + g_object_unref); + 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); + if (match) + { + g_warning ("Driver passed an error but also provided a match result, returning error!"); + g_clear_object (&match); + } + } +} + + +/** + * 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 == FP_DEVICE_ACTION_CAPTURE); + + g_debug ("Device reported capture completion"); + + clear_device_cancel_action (device); + + 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 == FP_DEVICE_ACTION_DELETE); + + g_debug ("Device reported deletion completion"); + + clear_device_cancel_action (device); + + 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 == FP_DEVICE_ACTION_LIST); + + g_debug ("Device reported listing completion"); + + clear_device_cancel_action (device); + + 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: The #FpPrint for the newly completed stage or %NULL on failure + * @error: 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 == FP_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 (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); +} diff --git a/libfprint/meson.build b/libfprint/meson.build index 1e98e2d..4a34cbd 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -8,6 +8,7 @@ libfprint_sources = [ libfprint_private_sources = [ 'fpi-assembling.c', + 'fpi-device.c', 'fpi-ssm.c', 'fpi-usb-transfer.c', 'fpi-byte-reader.c', -- 2.24.1