From 1de79db6506bb388423d518a8bd1d243ee06b631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 11 Dec 2019 13:40:47 +0100 Subject: [PATCH 106/181] fp-image-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_image_device_get_instance_private for private usage, not to move the private struct as part of FpDevice. --- libfprint/fp-image-device-private.h | 43 ++ libfprint/fp-image-device.c | 585 +-------------------------- libfprint/fpi-image-device.c | 595 ++++++++++++++++++++++++++++ libfprint/meson.build | 1 + 4 files changed, 643 insertions(+), 581 deletions(-) create mode 100644 libfprint/fp-image-device-private.h create mode 100644 libfprint/fpi-image-device.c diff --git a/libfprint/fp-image-device-private.h b/libfprint/fp-image-device-private.h new file mode 100644 index 0000000..01454fd --- /dev/null +++ b/libfprint/fp-image-device-private.h @@ -0,0 +1,43 @@ +/* + * FpImageDevice - An image based fingerprint reader device + * Copyright (C) 2019 Benjamin Berg + * + * 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-image-device.h" + +#define IMG_ENROLL_STAGES 5 + +typedef struct +{ + FpImageDeviceState state; + gboolean active; + gboolean cancelling; + + gboolean enroll_await_on_pending; + gint enroll_stage; + + guint pending_activation_timeout_id; + gboolean pending_activation_timeout_waiting_finger_off; + + gint bz3_threshold; +} FpImageDevicePrivate; + + +void fpi_image_device_activate (FpImageDevice *image_device); +void fpi_image_device_deactivate (FpImageDevice *image_device); diff --git a/libfprint/fp-image-device.c b/libfprint/fp-image-device.c index 252f414..24d324d 100644 --- a/libfprint/fp-image-device.c +++ b/libfprint/fp-image-device.c @@ -20,14 +20,9 @@ #define FP_COMPONENT "image_device" #include "fpi-log.h" -#include "fpi-image-device.h" -#include "fpi-enums.h" -#include "fpi-print.h" -#include "fpi-image.h" +#include "fp-image-device-private.h" -#define MIN_ACCEPTABLE_MINUTIAE 10 #define BOZORTH3_DEFAULT_THRESHOLD 40 -#define IMG_ENROLL_STAGES 5 /** * SECTION: fp-image-device @@ -37,30 +32,6 @@ * This is a helper class for the commonly found image based devices. */ -/** - * SECTION: fpi-image-device - * @title: Internal FpImageDevice - * @short_description: Internal image device routines - * - * See #FpImageDeviceClass for more details. Also see the public - * #FpImageDevice routines. - */ - -typedef struct -{ - FpImageDeviceState state; - gboolean active; - gboolean cancelling; - - gboolean enroll_await_on_pending; - gint enroll_stage; - - guint pending_activation_timeout_id; - gboolean pending_activation_timeout_waiting_finger_off; - - gint bz3_threshold; -} FpImageDevicePrivate; - G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpImageDevice, fp_image_device, FP_TYPE_DEVICE) enum { @@ -87,70 +58,6 @@ static guint signals[LAST_SIGNAL] = { 0 }; /* Static helper functions */ -static void -fp_image_device_change_state (FpImageDevice *self, FpImageDeviceState state) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - - /* Cannot change to inactive using this function. */ - g_assert (state != FP_IMAGE_DEVICE_STATE_INACTIVE); - - /* We might have been waiting for the finger to go OFF to start the - * next operation. */ - g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove); - - fp_dbg ("Image device internal state change from %d to %d\n", priv->state, state); - - priv->state = state; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]); - g_signal_emit (self, signals[FPI_STATE_CHANGED], 0, priv->state); -} - -static void -fp_image_device_activate (FpImageDevice *self) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self); - - g_assert (!priv->active); - - /* We don't have a neutral ACTIVE state, but we always will - * go into WAIT_FINGER_ON afterwards. */ - priv->state = FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]); - - /* We might have been waiting for deactivation to finish before - * starting the next operation. */ - g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove); - - fp_dbg ("Activating image device\n"); - cls->activate (self); -} - -static void -fp_image_device_deactivate (FpDevice *device) -{ - FpImageDevice *self = FP_IMAGE_DEVICE (device); - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (device); - - if (!priv->active) - { - /* XXX: We currently deactivate both from minutiae scan result - * and finger off report. */ - fp_dbg ("Already deactivated, ignoring request."); - return; - } - if (!priv->cancelling && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) - g_warning ("Deactivating image device while waiting for finger, this should not happen."); - - priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]); - - fp_dbg ("Deactivating image device\n"); - cls->deactivate (self); -} - static gboolean pending_activation_timeout (gpointer user_data) { @@ -200,7 +107,7 @@ fp_image_device_close (FpDevice *device) if (!priv->active) cls->img_close (self); else if (priv->state != FP_IMAGE_DEVICE_STATE_INACTIVE) - fp_image_device_deactivate (device); + fpi_image_device_deactivate (self); } static void @@ -220,7 +127,7 @@ fp_image_device_cancel_action (FpDevice *device) action == FP_DEVICE_ACTION_CAPTURE) { priv->cancelling = TRUE; - fp_image_device_deactivate (FP_DEVICE (self)); + fpi_image_device_deactivate (self); priv->cancelling = FALSE; /* XXX: Some nicer way of doing this would be good. */ @@ -288,7 +195,7 @@ fp_image_device_start_capture_action (FpDevice *device) /* And activate the device; we rely on fpi_image_device_activate_complete() * to be called when done (or immediately). */ - fp_image_device_activate (self); + fpi_image_device_activate (self); } @@ -391,488 +298,4 @@ fp_image_device_init (FpImageDevice *self) priv->bz3_threshold = BOZORTH3_DEFAULT_THRESHOLD; if (cls->bz3_threshold > 0) priv->bz3_threshold = cls->bz3_threshold; - -} - -static void -fp_image_device_enroll_maybe_await_finger_on (FpImageDevice *self) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - - if (priv->enroll_await_on_pending) - { - priv->enroll_await_on_pending = FALSE; - fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON); - } - else - { - priv->enroll_await_on_pending = TRUE; - } -} - -static void -fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, gpointer user_data) -{ - g_autoptr(FpImage) image = FP_IMAGE (source_object); - g_autoptr(FpPrint) print = NULL; - GError *error = NULL; - FpDevice *device = FP_DEVICE (user_data); - FpImageDevicePrivate *priv; - FpDeviceAction action; - - /* Note: We rely on the device to not disappear during an operation. */ - - if (!fp_image_detect_minutiae_finish (image, res, &error)) - { - /* Cancel operation . */ - if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - { - fpi_device_action_error (device, g_steal_pointer (&error)); - fp_image_device_deactivate (device); - return; - } - - /* Replace error with a retry condition. */ - g_warning ("Failed to detect minutiae: %s", error->message); - g_clear_pointer (&error, g_error_free); - - error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_GENERAL, "Minutiae detection failed, please retry"); - } - - priv = fp_image_device_get_instance_private (FP_IMAGE_DEVICE (device)); - action = fpi_device_get_current_action (device); - - if (action == FP_DEVICE_ACTION_CAPTURE) - { - fpi_device_capture_complete (device, g_steal_pointer (&image), error); - fp_image_device_deactivate (device); - return; - } - - if (!error) - { - print = fp_print_new (device); - fpi_print_set_type (print, FP_PRINT_NBIS); - if (!fpi_print_add_from_image (print, image, &error)) - g_clear_object (&print); - } - - if (action == FP_DEVICE_ACTION_ENROLL) - { - FpPrint *enroll_print; - fpi_device_get_enroll_data (device, &enroll_print); - - if (print) - { - fpi_print_add_print (enroll_print, print); - priv->enroll_stage += 1; - } - - fpi_device_enroll_progress (device, priv->enroll_stage, - g_steal_pointer (&print), error); - - /* Start another scan or deactivate. */ - if (priv->enroll_stage == IMG_ENROLL_STAGES) - { - fpi_device_enroll_complete (device, g_object_ref (enroll_print), NULL); - fp_image_device_deactivate (device); - } - else - { - fp_image_device_enroll_maybe_await_finger_on (FP_IMAGE_DEVICE (device)); - } - } - else if (action == FP_DEVICE_ACTION_VERIFY) - { - FpPrint *template; - FpiMatchResult result; - - fpi_device_get_verify_data (device, &template); - if (print) - result = fpi_print_bz3_match (template, print, priv->bz3_threshold, &error); - else - result = FPI_MATCH_ERROR; - - fpi_device_verify_complete (device, result, g_steal_pointer (&print), error); - fp_image_device_deactivate (device); - } - else if (action == FP_DEVICE_ACTION_IDENTIFY) - { - gint i; - GPtrArray *templates; - FpPrint *result = NULL; - - fpi_device_get_identify_data (device, &templates); - for (i = 0; !error && i < templates->len; i++) - { - FpPrint *template = g_ptr_array_index (templates, i); - - if (fpi_print_bz3_match (template, print, priv->bz3_threshold, &error) == FPI_MATCH_SUCCESS) - { - result = g_object_ref (template); - break; - } - } - - fpi_device_identify_complete (device, result, g_steal_pointer (&print), error); - fp_image_device_deactivate (device); - } - else - { - /* XXX: This can be hit currently due to a race condition in the enroll code! - * In that case we scan a further image even though the minutiae for the previous - * one have not yet been detected. - * We need to keep track on the pending minutiae detection and the fact that - * it will finish eventually (or we may need to retry on error and activate the - * device again). */ - g_assert_not_reached (); - } -} - -/*********************************************************/ -/* Private API */ - -/** - * fpi_image_device_set_bz3_threshold: - * @self: a #FpImageDevice imaging fingerprint device - * @bz3_threshold: BZ3 threshold to use - * - * Dynamically adjust the bz3 threshold. This is only needed for drivers - * that support devices with different properties. It should generally be - * called from the probe callback, but is acceptable to call from the open - * callback. - */ -void -fpi_image_device_set_bz3_threshold (FpImageDevice *self, - gint bz3_threshold) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - - g_return_if_fail (FP_IS_IMAGE_DEVICE (self)); - g_return_if_fail (bz3_threshold > 0); - - priv->bz3_threshold = bz3_threshold; -} - -/** - * fpi_image_device_report_finger_status: - * @self: a #FpImageDevice imaging fingerprint device - * @present: whether the finger is present on the sensor - * - * Reports from the driver whether the user's finger is on - * the sensor. - */ -void -fpi_image_device_report_finger_status (FpImageDevice *self, - gboolean present) -{ - FpDevice *device = FP_DEVICE (self); - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - FpDeviceAction action; - - if (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE) - { - /* Do we really want to always ignore such reports? We could - * also track the state in case the user had the finger on - * the device at initialisation time and the driver reports - * this early. - */ - g_debug ("Ignoring finger presence report as the device is not active!"); - return; - } - - action = fpi_device_get_current_action (device); - - g_assert (action != FP_DEVICE_ACTION_OPEN); - g_assert (action != FP_DEVICE_ACTION_CLOSE); - - g_debug ("Image device reported finger status: %s", present ? "on" : "off"); - - if (present && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) - { - fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_CAPTURE); - } - else if (!present && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF) - { - /* We need to deactivate or continue to await finger */ - - /* There are three possible situations: - * 1. We are deactivating the device and the action is still in progress - * (minutiae detection). - * 2. We are still deactivating the device after an action completed - * 3. We were waiting for finger removal to start the new action - * Either way, we always end up deactivating except for the enroll case. - * - * The enroll case is special as AWAIT_FINGER_ON should only happen after - * minutiae detection to prevent deactivation (without cancellation) - * from the AWAIT_FINGER_ON state. - */ - if (action != FP_DEVICE_ACTION_ENROLL) - fp_image_device_deactivate (device); - else - fp_image_device_enroll_maybe_await_finger_on (self); - } -} - -/** - * fpi_image_device_image_captured: - * @self: a #FpImageDevice imaging fingerprint device - * @image: whether the finger is present on the sensor - * - * Reports an image capture. Only use this function if the image was - * captured successfully. If there was an issue where the user should - * retry, use fpi_image_device_retry_scan() to report the retry condition. - * - * In the event of a fatal error for the operation use - * fpi_image_device_session_error(). This will abort the entire operation - * including e.g. an enroll operation which captures multiple images during - * one session. - */ -void -fpi_image_device_image_captured (FpImageDevice *self, FpImage *image) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - FpDeviceAction action; - - action = fpi_device_get_current_action (FP_DEVICE (self)); - - g_return_if_fail (image != NULL); - g_return_if_fail (priv->state == FP_IMAGE_DEVICE_STATE_CAPTURE); - g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL || - action == FP_DEVICE_ACTION_VERIFY || - action == FP_DEVICE_ACTION_IDENTIFY || - action == FP_DEVICE_ACTION_CAPTURE); - - fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF); - - g_debug ("Image device captured an image"); - - /* XXX: We also detect minutiae in capture mode, we solely do this - * to normalize the image which will happen as a by-product. */ - fp_image_detect_minutiae (image, - fpi_device_get_cancellable (FP_DEVICE (self)), - fpi_image_device_minutiae_detected, - self); -} - -/** - * fpi_image_device_retry_scan: - * @self: a #FpImageDevice imaging fingerprint device - * @retry: The #FpDeviceRetry error code to report - * - * Reports a scan failure to the user. This may or may not abort the - * current session. It is the equivalent of fpi_image_device_image_captured() - * in the case of a retryable error condition (e.g. short swipe). - */ -void -fpi_image_device_retry_scan (FpImageDevice *self, FpDeviceRetry retry) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - FpDeviceAction action; - GError *error; - - action = fpi_device_get_current_action (FP_DEVICE (self)); - - /* We might be waiting for a finger at this point, so just accept - * all but INACTIVE */ - g_return_if_fail (priv->state != FP_IMAGE_DEVICE_STATE_INACTIVE); - g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL || - action == FP_DEVICE_ACTION_VERIFY || - action == FP_DEVICE_ACTION_IDENTIFY || - action == FP_DEVICE_ACTION_CAPTURE); - - error = fpi_device_retry_new (retry); - - if (action == FP_DEVICE_ACTION_ENROLL) - { - g_debug ("Reporting retry during enroll"); - fpi_device_enroll_progress (FP_DEVICE (self), priv->enroll_stage, NULL, error); - } - else - { - /* We abort the operation and let the surrounding code retry in the - * non-enroll case (this is identical to a session error). */ - g_debug ("Abort current operation due to retry (non-enroll case)"); - fp_image_device_deactivate (FP_DEVICE (self)); - fpi_device_action_error (FP_DEVICE (self), error); - } -} - -/** - * fpi_image_device_session_error: - * @self: a #FpImageDevice imaging fingerprint device - * @error: The #GError to report - * - * Report an error while interacting with the device. This effectively - * aborts the current ongoing action. - */ -void -fpi_image_device_session_error (FpImageDevice *self, GError *error) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - - g_return_if_fail (self); - - if (!error) - { - g_warning ("Driver did not provide an error, generating a generic one"); - error = g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, "Driver reported session error without an error"); - } - - if (!priv->active) - { - FpDeviceAction action = fpi_device_get_current_action (FP_DEVICE (self)); - g_warning ("Driver reported session error, but device is inactive."); - - if (action != FP_DEVICE_ACTION_NONE) - { - g_warning ("Translating to activation failure!"); - fpi_image_device_activate_complete (self, error); - return; - } - } - else if (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE) - { - g_warning ("Driver reported session error; translating to deactivation failure."); - fpi_image_device_deactivate_complete (self, error); - return; - } - - if (error->domain == FP_DEVICE_RETRY) - g_warning ("Driver should report retries using fpi_image_device_retry_scan!"); - - fp_image_device_deactivate (FP_DEVICE (self)); - fpi_device_action_error (FP_DEVICE (self), error); -} - -/** - * fpi_image_device_activate_complete: - * @self: a #FpImageDevice imaging fingerprint device - * @error: A #GError or %NULL on success - * - * Reports completion of device activation. - */ -void -fpi_image_device_activate_complete (FpImageDevice *self, GError *error) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - FpDeviceAction action; - - action = fpi_device_get_current_action (FP_DEVICE (self)); - - g_return_if_fail (priv->active == FALSE); - g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL || - action == FP_DEVICE_ACTION_VERIFY || - action == FP_DEVICE_ACTION_IDENTIFY || - action == FP_DEVICE_ACTION_CAPTURE); - - if (error) - { - g_debug ("Image device activation failed"); - fpi_device_action_error (FP_DEVICE (self), error); - return; - } - - g_debug ("Image device activation completed"); - - priv->active = TRUE; - - /* We always want to capture at this point, move to AWAIT_FINGER - * state. */ - fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON); -} - -/** - * fpi_image_device_deactivate_complete: - * @self: a #FpImageDevice imaging fingerprint device - * @error: A #GError or %NULL on success - * - * Reports completion of device deactivation. - */ -void -fpi_image_device_deactivate_complete (FpImageDevice *self, GError *error) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self); - FpDeviceAction action; - - g_return_if_fail (priv->active == TRUE); - g_return_if_fail (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE); - - g_debug ("Image device deactivation completed"); - - priv->active = FALSE; - - /* Deactivation completed. As we deactivate in the background - * there may already be a new task pending. Check whether we - * need to do anything. */ - action = fpi_device_get_current_action (FP_DEVICE (self)); - - /* Special case, if we should be closing, but didn't due to a running - * deactivation, then do so now. */ - if (action == FP_DEVICE_ACTION_CLOSE) - { - cls->img_close (self); - return; - } - - /* We might be waiting to be able to activate again. */ - if (priv->pending_activation_timeout_id) - { - g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove); - priv->pending_activation_timeout_id = - g_idle_add ((GSourceFunc) fp_image_device_activate, self); - } -} - -/** - * fpi_image_device_open_complete: - * @self: a #FpImageDevice imaging fingerprint device - * @error: A #GError or %NULL on success - * - * Reports completion of open operation. - */ -void -fpi_image_device_open_complete (FpImageDevice *self, GError *error) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - FpDeviceAction action; - - action = fpi_device_get_current_action (FP_DEVICE (self)); - - g_return_if_fail (priv->active == FALSE); - g_return_if_fail (action == FP_DEVICE_ACTION_OPEN); - - g_debug ("Image device open completed"); - - priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]); - - fpi_device_open_complete (FP_DEVICE (self), error); -} - -/** - * fpi_image_device_close_complete: - * @self: a #FpImageDevice imaging fingerprint device - * @error: A #GError or %NULL on success - * - * Reports completion of close operation. - */ -void -fpi_image_device_close_complete (FpImageDevice *self, GError *error) -{ - FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); - FpDeviceAction action; - - action = fpi_device_get_current_action (FP_DEVICE (self)); - - g_debug ("Image device close completed"); - - g_return_if_fail (priv->active == FALSE); - g_return_if_fail (action == FP_DEVICE_ACTION_CLOSE); - - priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]); - - fpi_device_close_complete (FP_DEVICE (self), error); } diff --git a/libfprint/fpi-image-device.c b/libfprint/fpi-image-device.c new file mode 100644 index 0000000..6e5802e --- /dev/null +++ b/libfprint/fpi-image-device.c @@ -0,0 +1,595 @@ +/* + * FpImageDevice - An image based fingerprint reader device - Private APIs + * Copyright (C) 2019 Benjamin Berg + * + * 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 "image_device" +#include "fpi-log.h" + +#include "fp-image-device-private.h" +#include "fp-image-device.h" + +/** + * SECTION: fpi-image + * @title: Internal FpImage + * @short_description: Internal image handling routines + * + * Internal image handling routines. Also see the public FpImage routines. + */ + +/* Manually redefine what G_DEFINE_* macro does */ +static inline gpointer +fp_image_device_get_instance_private (FpImageDevice *self) +{ + FpImageDeviceClass *img_class = g_type_class_peek_static (FP_TYPE_IMAGE_DEVICE); + + return G_STRUCT_MEMBER_P (self, + g_type_class_get_instance_private_offset (img_class)); +} + +/* Private shared functions */ + +void +fpi_image_device_activate (FpImageDevice *self) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self); + + g_assert (!priv->active); + + /* We don't have a neutral ACTIVE state, but we always will + * go into WAIT_FINGER_ON afterwards. */ + priv->state = FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON; + g_object_notify (G_OBJECT (self), "fp-image-device-state"); + + /* We might have been waiting for deactivation to finish before + * starting the next operation. */ + g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove); + + fp_dbg ("Activating image device\n"); + cls->activate (self); +} + +void +fpi_image_device_deactivate (FpImageDevice *self) +{ + FpDevice *device = FP_DEVICE (self); + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (device); + + if (!priv->active) + { + /* XXX: We currently deactivate both from minutiae scan result + * and finger off report. */ + fp_dbg ("Already deactivated, ignoring request."); + return; + } + if (!priv->cancelling && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) + g_warning ("Deactivating image device while waiting for finger, this should not happen."); + + priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE; + g_object_notify (G_OBJECT (self), "fp-image-device-state"); + + fp_dbg ("Deactivating image device\n"); + cls->deactivate (self); +} + +/* Static helper functions */ + +static void +fp_image_device_change_state (FpImageDevice *self, FpImageDeviceState state) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + + /* Cannot change to inactive using this function. */ + g_assert (state != FP_IMAGE_DEVICE_STATE_INACTIVE); + + /* We might have been waiting for the finger to go OFF to start the + * next operation. */ + g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove); + + fp_dbg ("Image device internal state change from %d to %d\n", priv->state, state); + + priv->state = state; + g_object_notify (G_OBJECT (self), "fp-image-device-state"); + g_signal_emit_by_name (self, "fp-image-device-state-changed", priv->state); +} + +static void +fp_image_device_enroll_maybe_await_finger_on (FpImageDevice *self) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + + if (priv->enroll_await_on_pending) + { + priv->enroll_await_on_pending = FALSE; + fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON); + } + else + { + priv->enroll_await_on_pending = TRUE; + } +} + +static void +fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(FpImage) image = FP_IMAGE (source_object); + g_autoptr(FpPrint) print = NULL; + GError *error = NULL; + FpImageDevice *self = FP_IMAGE_DEVICE (user_data); + FpDevice *device = FP_DEVICE (self); + FpImageDevicePrivate *priv; + FpDeviceAction action; + + /* Note: We rely on the device to not disappear during an operation. */ + + if (!fp_image_detect_minutiae_finish (image, res, &error)) + { + /* Cancel operation . */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + fpi_device_action_error (device, g_steal_pointer (&error)); + fpi_image_device_deactivate (self); + return; + } + + /* Replace error with a retry condition. */ + g_warning ("Failed to detect minutiae: %s", error->message); + g_clear_pointer (&error, g_error_free); + + error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_GENERAL, "Minutiae detection failed, please retry"); + } + + priv = fp_image_device_get_instance_private (FP_IMAGE_DEVICE (device)); + action = fpi_device_get_current_action (device); + + if (action == FP_DEVICE_ACTION_CAPTURE) + { + fpi_device_capture_complete (device, g_steal_pointer (&image), error); + fpi_image_device_deactivate (self); + return; + } + + if (!error) + { + print = fp_print_new (device); + fpi_print_set_type (print, FP_PRINT_NBIS); + if (!fpi_print_add_from_image (print, image, &error)) + g_clear_object (&print); + } + + if (action == FP_DEVICE_ACTION_ENROLL) + { + FpPrint *enroll_print; + fpi_device_get_enroll_data (device, &enroll_print); + + if (print) + { + fpi_print_add_print (enroll_print, print); + priv->enroll_stage += 1; + } + + fpi_device_enroll_progress (device, priv->enroll_stage, + g_steal_pointer (&print), error); + + /* Start another scan or deactivate. */ + if (priv->enroll_stage == IMG_ENROLL_STAGES) + { + fpi_device_enroll_complete (device, g_object_ref (enroll_print), NULL); + fpi_image_device_deactivate (self); + } + else + { + fp_image_device_enroll_maybe_await_finger_on (FP_IMAGE_DEVICE (device)); + } + } + else if (action == FP_DEVICE_ACTION_VERIFY) + { + FpPrint *template; + FpiMatchResult result; + + fpi_device_get_verify_data (device, &template); + if (print) + result = fpi_print_bz3_match (template, print, priv->bz3_threshold, &error); + else + result = FPI_MATCH_ERROR; + + fpi_device_verify_complete (device, result, g_steal_pointer (&print), error); + fpi_image_device_deactivate (self); + } + else if (action == FP_DEVICE_ACTION_IDENTIFY) + { + gint i; + GPtrArray *templates; + FpPrint *result = NULL; + + fpi_device_get_identify_data (device, &templates); + for (i = 0; !error && i < templates->len; i++) + { + FpPrint *template = g_ptr_array_index (templates, i); + + if (fpi_print_bz3_match (template, print, priv->bz3_threshold, &error) == FPI_MATCH_SUCCESS) + { + result = g_object_ref (template); + break; + } + } + + fpi_device_identify_complete (device, result, g_steal_pointer (&print), error); + fpi_image_device_deactivate (self); + } + else + { + /* XXX: This can be hit currently due to a race condition in the enroll code! + * In that case we scan a further image even though the minutiae for the previous + * one have not yet been detected. + * We need to keep track on the pending minutiae detection and the fact that + * it will finish eventually (or we may need to retry on error and activate the + * device again). */ + g_assert_not_reached (); + } +} + +/*********************************************************/ +/* Private API */ + +/** + * fpi_image_device_set_bz3_threshold: + * @self: a #FpImageDevice imaging fingerprint device + * @bz3_threshold: BZ3 threshold to use + * + * Dynamically adjust the bz3 threshold. This is only needed for drivers + * that support devices with different properties. It should generally be + * called from the probe callback, but is acceptable to call from the open + * callback. + */ +void +fpi_image_device_set_bz3_threshold (FpImageDevice *self, + gint bz3_threshold) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + + g_return_if_fail (FP_IS_IMAGE_DEVICE (self)); + g_return_if_fail (bz3_threshold > 0); + + priv->bz3_threshold = bz3_threshold; +} + +/** + * fpi_image_device_report_finger_status: + * @self: a #FpImageDevice imaging fingerprint device + * @present: whether the finger is present on the sensor + * + * Reports from the driver whether the user's finger is on + * the sensor. + */ +void +fpi_image_device_report_finger_status (FpImageDevice *self, + gboolean present) +{ + FpDevice *device = FP_DEVICE (self); + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + FpDeviceAction action; + + if (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE) + { + /* Do we really want to always ignore such reports? We could + * also track the state in case the user had the finger on + * the device at initialisation time and the driver reports + * this early. + */ + g_debug ("Ignoring finger presence report as the device is not active!"); + return; + } + + action = fpi_device_get_current_action (device); + + g_assert (action != FP_DEVICE_ACTION_OPEN); + g_assert (action != FP_DEVICE_ACTION_CLOSE); + + g_debug ("Image device reported finger status: %s", present ? "on" : "off"); + + if (present && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) + { + fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_CAPTURE); + } + else if (!present && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF) + { + /* We need to deactivate or continue to await finger */ + + /* There are three possible situations: + * 1. We are deactivating the device and the action is still in progress + * (minutiae detection). + * 2. We are still deactivating the device after an action completed + * 3. We were waiting for finger removal to start the new action + * Either way, we always end up deactivating except for the enroll case. + * + * The enroll case is special as AWAIT_FINGER_ON should only happen after + * minutiae detection to prevent deactivation (without cancellation) + * from the AWAIT_FINGER_ON state. + */ + if (action != FP_DEVICE_ACTION_ENROLL) + fpi_image_device_deactivate (self); + else + fp_image_device_enroll_maybe_await_finger_on (self); + } +} + +/** + * fpi_image_device_image_captured: + * @self: a #FpImageDevice imaging fingerprint device + * @image: whether the finger is present on the sensor + * + * Reports an image capture. Only use this function if the image was + * captured successfully. If there was an issue where the user should + * retry, use fpi_image_device_retry_scan() to report the retry condition. + * + * In the event of a fatal error for the operation use + * fpi_image_device_session_error(). This will abort the entire operation + * including e.g. an enroll operation which captures multiple images during + * one session. + */ +void +fpi_image_device_image_captured (FpImageDevice *self, FpImage *image) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + FpDeviceAction action; + + action = fpi_device_get_current_action (FP_DEVICE (self)); + + g_return_if_fail (image != NULL); + g_return_if_fail (priv->state == FP_IMAGE_DEVICE_STATE_CAPTURE); + g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL || + action == FP_DEVICE_ACTION_VERIFY || + action == FP_DEVICE_ACTION_IDENTIFY || + action == FP_DEVICE_ACTION_CAPTURE); + + fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF); + + g_debug ("Image device captured an image"); + + /* XXX: We also detect minutiae in capture mode, we solely do this + * to normalize the image which will happen as a by-product. */ + fp_image_detect_minutiae (image, + fpi_device_get_cancellable (FP_DEVICE (self)), + fpi_image_device_minutiae_detected, + self); +} + +/** + * fpi_image_device_retry_scan: + * @self: a #FpImageDevice imaging fingerprint device + * @retry: The #FpDeviceRetry error code to report + * + * Reports a scan failure to the user. This may or may not abort the + * current session. It is the equivalent of fpi_image_device_image_captured() + * in the case of a retryable error condition (e.g. short swipe). + */ +void +fpi_image_device_retry_scan (FpImageDevice *self, FpDeviceRetry retry) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + FpDeviceAction action; + GError *error; + + action = fpi_device_get_current_action (FP_DEVICE (self)); + + /* We might be waiting for a finger at this point, so just accept + * all but INACTIVE */ + g_return_if_fail (priv->state != FP_IMAGE_DEVICE_STATE_INACTIVE); + g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL || + action == FP_DEVICE_ACTION_VERIFY || + action == FP_DEVICE_ACTION_IDENTIFY || + action == FP_DEVICE_ACTION_CAPTURE); + + error = fpi_device_retry_new (retry); + + if (action == FP_DEVICE_ACTION_ENROLL) + { + g_debug ("Reporting retry during enroll"); + fpi_device_enroll_progress (FP_DEVICE (self), priv->enroll_stage, NULL, error); + } + else + { + /* We abort the operation and let the surrounding code retry in the + * non-enroll case (this is identical to a session error). */ + g_debug ("Abort current operation due to retry (non-enroll case)"); + fpi_image_device_deactivate (self); + fpi_device_action_error (FP_DEVICE (self), error); + } +} + +/** + * fpi_image_device_session_error: + * @self: a #FpImageDevice imaging fingerprint device + * @error: The #GError to report + * + * Report an error while interacting with the device. This effectively + * aborts the current ongoing action. + */ +void +fpi_image_device_session_error (FpImageDevice *self, GError *error) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + + g_return_if_fail (self); + + if (!error) + { + g_warning ("Driver did not provide an error, generating a generic one"); + error = g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, "Driver reported session error without an error"); + } + + if (!priv->active) + { + FpDeviceAction action = fpi_device_get_current_action (FP_DEVICE (self)); + g_warning ("Driver reported session error, but device is inactive."); + + if (action != FP_DEVICE_ACTION_NONE) + { + g_warning ("Translating to activation failure!"); + fpi_image_device_activate_complete (self, error); + return; + } + } + else if (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE) + { + g_warning ("Driver reported session error; translating to deactivation failure."); + fpi_image_device_deactivate_complete (self, error); + return; + } + + if (error->domain == FP_DEVICE_RETRY) + g_warning ("Driver should report retries using fpi_image_device_retry_scan!"); + + fpi_image_device_deactivate (self); + fpi_device_action_error (FP_DEVICE (self), error); +} + +/** + * fpi_image_device_activate_complete: + * @self: a #FpImageDevice imaging fingerprint device + * @error: A #GError or %NULL on success + * + * Reports completion of device activation. + */ +void +fpi_image_device_activate_complete (FpImageDevice *self, GError *error) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + FpDeviceAction action; + + action = fpi_device_get_current_action (FP_DEVICE (self)); + + g_return_if_fail (priv->active == FALSE); + g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL || + action == FP_DEVICE_ACTION_VERIFY || + action == FP_DEVICE_ACTION_IDENTIFY || + action == FP_DEVICE_ACTION_CAPTURE); + + if (error) + { + g_debug ("Image device activation failed"); + fpi_device_action_error (FP_DEVICE (self), error); + return; + } + + g_debug ("Image device activation completed"); + + priv->active = TRUE; + + /* We always want to capture at this point, move to AWAIT_FINGER + * state. */ + fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON); +} + +/** + * fpi_image_device_deactivate_complete: + * @self: a #FpImageDevice imaging fingerprint device + * @error: A #GError or %NULL on success + * + * Reports completion of device deactivation. + */ +void +fpi_image_device_deactivate_complete (FpImageDevice *self, GError *error) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self); + FpDeviceAction action; + + g_return_if_fail (priv->active == TRUE); + g_return_if_fail (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE); + + g_debug ("Image device deactivation completed"); + + priv->active = FALSE; + + /* Deactivation completed. As we deactivate in the background + * there may already be a new task pending. Check whether we + * need to do anything. */ + action = fpi_device_get_current_action (FP_DEVICE (self)); + + /* Special case, if we should be closing, but didn't due to a running + * deactivation, then do so now. */ + if (action == FP_DEVICE_ACTION_CLOSE) + { + cls->img_close (self); + return; + } + + /* We might be waiting to be able to activate again. */ + if (priv->pending_activation_timeout_id) + { + g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove); + priv->pending_activation_timeout_id = + g_idle_add ((GSourceFunc) fpi_image_device_activate, self); + } +} + +/** + * fpi_image_device_open_complete: + * @self: a #FpImageDevice imaging fingerprint device + * @error: A #GError or %NULL on success + * + * Reports completion of open operation. + */ +void +fpi_image_device_open_complete (FpImageDevice *self, GError *error) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + FpDeviceAction action; + + action = fpi_device_get_current_action (FP_DEVICE (self)); + + g_return_if_fail (priv->active == FALSE); + g_return_if_fail (action == FP_DEVICE_ACTION_OPEN); + + g_debug ("Image device open completed"); + + priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE; + g_object_notify (G_OBJECT (self), "fp-image-device-state"); + + fpi_device_open_complete (FP_DEVICE (self), error); +} + +/** + * fpi_image_device_close_complete: + * @self: a #FpImageDevice imaging fingerprint device + * @error: A #GError or %NULL on success + * + * Reports completion of close operation. + */ +void +fpi_image_device_close_complete (FpImageDevice *self, GError *error) +{ + FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); + FpDeviceAction action; + + action = fpi_device_get_current_action (FP_DEVICE (self)); + + g_debug ("Image device close completed"); + + g_return_if_fail (priv->active == FALSE); + g_return_if_fail (action == FP_DEVICE_ACTION_CLOSE); + + priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE; + g_object_notify (G_OBJECT (self), "fp-image-device-state"); + + fpi_device_close_complete (FP_DEVICE (self), error); +} diff --git a/libfprint/meson.build b/libfprint/meson.build index 4a34cbd..9eb4849 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -9,6 +9,7 @@ libfprint_sources = [ libfprint_private_sources = [ 'fpi-assembling.c', 'fpi-device.c', + 'fpi-image-device.c', 'fpi-ssm.c', 'fpi-usb-transfer.c', 'fpi-byte-reader.c', -- 2.24.1