/* * 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 #include #include #include "fpi-log.h" #include "fp-device-private.h" /** * SECTION: fpi-device * @title: Internal FpDevice * @short_description: Internal device routines * * The methods that are available for drivers to manipulate a device. See * #FpDeviceClass for more information. Also note that most of these are * not relevant for image based devices, see #FpImageDeviceClass in that * case. * * Also see the public #FpDevice routines. */ /* Manually redefine what G_DEFINE_* macro does */ static inline gpointer fp_device_get_instance_private (FpDevice *self) { FpDeviceClass *dev_class = g_type_class_peek_static (FP_TYPE_DEVICE); return G_STRUCT_MEMBER_P (self, g_type_class_get_instance_private_offset (dev_class)); } /** * fpi_device_class_auto_initialize_features: * * Initializes the #FpDeviceClass @features flags checking what device vfuncs * are implemented. * Drivers should call this at the end of the class initialization. */ void fpi_device_class_auto_initialize_features (FpDeviceClass *device_class) { g_return_if_fail (FP_IS_DEVICE_CLASS (device_class)); if (device_class->capture) device_class->features |= FP_DEVICE_FEATURE_CAPTURE; if (device_class->verify) device_class->features |= FP_DEVICE_FEATURE_VERIFY; if (device_class->identify) device_class->features |= FP_DEVICE_FEATURE_IDENTIFY; if (device_class->list) device_class->features |= FP_DEVICE_FEATURE_STORAGE_LIST; if (device_class->delete) device_class->features |= FP_DEVICE_FEATURE_STORAGE_DELETE; if (device_class->clear_storage) device_class->features |= FP_DEVICE_FEATURE_STORAGE_CLEAR; if (device_class->delete && (device_class->list || device_class->clear_storage)) device_class->features |= FP_DEVICE_FEATURE_STORAGE; if (device_class->temp_hot_seconds < 0) device_class->features |= FP_DEVICE_FEATURE_ALWAYS_ON; } /** * fpi_device_retry_new: * @error: The #FpDeviceRetry error value describing the issue * * Create a new retry error code for use with fpi_device_verify_complete() * and similar calls. */ GError * fpi_device_retry_new (FpDeviceRetry error) { const gchar *msg; switch (error) { case FP_DEVICE_RETRY_GENERAL: msg = "Please try again."; break; case FP_DEVICE_RETRY_TOO_SHORT: msg = "The swipe was too short, please try again."; break; case FP_DEVICE_RETRY_CENTER_FINGER: msg = "The finger was not centered properly, please try again."; break; case FP_DEVICE_RETRY_REMOVE_FINGER: msg = "Please try again after removing the finger first."; break; default: g_warning ("Unsupported error, returning general error instead!"); error = FP_DEVICE_RETRY_GENERAL; msg = "Please try again."; } return g_error_new_literal (FP_DEVICE_RETRY, error, msg); } /** * fpi_device_error_new: * @error: The #FpDeviceRetry error value describing the issue * * Create a new error code for use with fpi_device_verify_complete() and * similar calls. */ GError * fpi_device_error_new (FpDeviceError error) { const gchar *msg; switch (error) { case FP_DEVICE_ERROR_GENERAL: msg = "An unspecified error occurred!"; break; case FP_DEVICE_ERROR_NOT_SUPPORTED: msg = "The operation is not supported on this device!"; break; case FP_DEVICE_ERROR_NOT_OPEN: msg = "The device needs to be opened first!"; break; case FP_DEVICE_ERROR_ALREADY_OPEN: msg = "The device has already been opened!"; break; case FP_DEVICE_ERROR_BUSY: msg = "The device is still busy with another operation, please try again later."; break; case FP_DEVICE_ERROR_PROTO: msg = "The driver encountered a protocol error with the device."; break; case FP_DEVICE_ERROR_DATA_INVALID: msg = "Passed (print) data is not valid."; break; case FP_DEVICE_ERROR_DATA_FULL: msg = "On device storage space is full."; break; case FP_DEVICE_ERROR_DATA_NOT_FOUND: msg = "Print was not found on the devices storage."; break; case FP_DEVICE_ERROR_DATA_DUPLICATE: msg = "This finger has already enrolled, please try a different finger"; break; case FP_DEVICE_ERROR_REMOVED: msg = "This device has been removed from the system."; break; case FP_DEVICE_ERROR_TOO_HOT: msg = "Device disabled to prevent overheating."; break; default: g_warning ("Unsupported error, returning general error instead!"); error = FP_DEVICE_ERROR_GENERAL; msg = "An unspecified error occurred!"; } return g_error_new_literal (FP_DEVICE_ERROR, error, msg); } /** * fpi_device_retry_new_msg: * @error: The #FpDeviceRetry error value describing the issue * @msg: Custom message to use with printf-style formatting * @...: args for @msg * * Create a new retry error code for use with fpi_device_verify_complete() * and similar calls. */ GError * fpi_device_retry_new_msg (FpDeviceRetry device_error, const gchar *msg, ...) { GError *error; va_list args; va_start (args, msg); error = g_error_new_valist (FP_DEVICE_RETRY, device_error, msg, args); va_end (args); return error; } /** * fpi_device_error_new_msg: * @error: The #FpDeviceRetry error value describing the issue * @msg: Custom message to use with printf-style formatting * @...: args for @msg * * Create a new error code for use with fpi_device_verify_complete() * and similar calls. */ GError * fpi_device_error_new_msg (FpDeviceError device_error, const gchar *msg, ...) { GError *error; va_list args; va_start (args, msg); error = g_error_new_valist (FP_DEVICE_ERROR, device_error, msg, args); va_end (args); return error; } /** * fpi_device_set_nr_enroll_stages: * @device: The #FpDevice * @enroll_stages: The number of enroll stages * * Updates the reported number of enroll stages that the device needs. * If all supported devices have the same number of stages, then the * value can simply be set in the class. */ void fpi_device_set_nr_enroll_stages (FpDevice *device, gint enroll_stages) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (enroll_stages > 0); priv->nr_enroll_stages = enroll_stages; g_object_notify (G_OBJECT (device), "nr-enroll-stages"); } /** * fpi_device_set_scan_type: * @device: The #FpDevice * @scan_type: The scan type of the device * * Updates the the scan type of the device from the default. * If all supported devices have the same scan type, then the * value can simply be set in the class. */ void fpi_device_set_scan_type (FpDevice *device, FpScanType scan_type) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); priv->scan_type = scan_type; g_object_notify (G_OBJECT (device), "scan-type"); } /** * fpi_device_update_features: * @device: The #FpDevice * @update: The feature flags to update * @value: The value to set the flags to * * Updates the feature flags for the device. This can be used * to runtime detect features that are supported by the device. */ void fpi_device_update_features (FpDevice *device, FpDeviceFeature update, FpDeviceFeature value) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_PROBE); g_return_if_fail ((value & update) == value); priv->features = (priv->features & ~update) | (value & update); } 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; GMainContext *context; source = (FpDeviceTimeoutSource *) g_source_new (&timeout_funcs, sizeof (FpDeviceTimeoutSource)); source->device = device; if (priv->current_task) context = g_task_get_context (priv->current_task); else context = g_main_context_get_thread_default (); g_source_attach (&source->source, context); g_source_set_callback (&source->source, (GSourceFunc) func, user_data, destroy_notify); g_source_set_ready_time (&source->source, g_source_get_time (&source->source) + interval * (guint64) 1000); priv->sources = g_slist_prepend (priv->sources, source); g_source_unref (&source->source); return &source->source; } /** * fpi_device_get_usb_device: * @device: The #FpDevice * * Get the #GUsbDevice for this #FpDevice. Only permissible to call if the * #FpDevice is of type %FP_DEVICE_TYPE_USB. * * Returns: The #GUsbDevice */ GUsbDevice * fpi_device_get_usb_device (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_val_if_fail (FP_IS_DEVICE (device), NULL); g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_USB, NULL); return priv->usb_device; } /** * fpi_device_get_udev_data: * @device: The #FpDevice * @subtype: Which subtype to get information about * * Get a subtype-specific hardware resource for this #FpDevice. Only permissible to call if the * #FpDevice is of type %FP_DEVICE_TYPE_UDEV. * * Returns: Depends on @subtype; for SPIDEV/HIDRAW returns a path to the relevant device. */ gpointer fpi_device_get_udev_data (FpDevice *device, FpiDeviceUdevSubtypeFlags subtype) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_val_if_fail (FP_IS_DEVICE (device), NULL); g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_UDEV, NULL); switch (subtype) { case FPI_DEVICE_UDEV_SUBTYPE_HIDRAW: return priv->udev_data.hidraw_path; case FPI_DEVICE_UDEV_SUBTYPE_SPIDEV: return priv->udev_data.spidev_path; default: g_return_val_if_reached (NULL); return NULL; } } /** * fpi_device_get_virtual_env: * @device: The #FpDevice * * Get the value of the environment variable that caused the virtual #FpDevice to be * generated. Only permissible to call if the #FpDevice is of type %FP_DEVICE_TYPE_VIRTUAL. * * Returns: The value of the environment variable */ const gchar * fpi_device_get_virtual_env (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_val_if_fail (FP_IS_DEVICE (device), NULL); g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_VIRTUAL, NULL); return priv->virtual_env; } /** * fpi_device_get_current_action: * @device: The #FpDevice * * Get the currently ongoing action or %FPI_DEVICE_ACTION_NONE if there * is no operation at this time. * * This is useful for drivers that might share code paths between different * actions (e.g. verify and identify) and want to find out again later which * action was started in the beginning. * * Returns: The ongoing #FpiDeviceAction */ FpiDeviceAction fpi_device_get_current_action (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_val_if_fail (FP_IS_DEVICE (device), FPI_DEVICE_ACTION_NONE); return priv->current_action; } /** * fpi_device_action_is_cancelled: * @device: The #FpDevice * * Checks whether the current action has been cancelled by the user. * This is equivalent to first getting the cancellable using * fpi_device_get_cancellable() and then checking whether it has been * cancelled (if it is non-NULL). * * Returns: %TRUE if action should be cancelled */ gboolean fpi_device_action_is_cancelled (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_val_if_fail (FP_IS_DEVICE (device), TRUE); g_return_val_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE, TRUE); return g_cancellable_is_cancelled (priv->current_cancellable); } /** * fpi_device_get_driver_data: * @device: The #FpDevice * * Returns: The driver data from the #FpIdEntry table entry */ guint64 fpi_device_get_driver_data (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_val_if_fail (FP_IS_DEVICE (device), 0); return priv->driver_data; } /** * fpi_device_get_enroll_data: * @device: The #FpDevice * @print: (out) (transfer none): The user provided template print * * Get data for enrollment. */ void fpi_device_get_enroll_data (FpDevice *device, FpPrint **print) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpEnrollData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_ENROLL); data = g_task_get_task_data (priv->current_task); g_assert (data); if (print) *print = data->print; } /** * fpi_device_get_capture_data: * @device: The #FpDevice * @wait_for_finger: (out): Whether to wait for finger or not * * Get data for capture. */ void fpi_device_get_capture_data (FpDevice *device, gboolean *wait_for_finger) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_CAPTURE); if (wait_for_finger) *wait_for_finger = priv->wait_for_finger; } /** * fpi_device_get_verify_data: * @device: The #FpDevice * @print: (out) (transfer none): The enrolled print * * Get data for verify. */ void fpi_device_get_verify_data (FpDevice *device, FpPrint **print) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpMatchData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_VERIFY); data = g_task_get_task_data (priv->current_task); g_assert (data); if (print) *print = data->enrolled_print; } /** * fpi_device_get_identify_data: * @device: The #FpDevice * @prints: (out) (transfer none) (element-type FpPrint): The gallery of prints * * Get prints gallery for identification. * * The @prints array is always non-%NULL and may contain a list of #FpPrint's * that the device should match against. * * Note that @prints can be an empty array, in such case the device is expected * to report the scanned print matching the one in its internal storage, if any. * */ void fpi_device_get_identify_data (FpDevice *device, GPtrArray **prints) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpMatchData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_IDENTIFY); data = g_task_get_task_data (priv->current_task); g_assert (data); if (prints) *prints = data->gallery; } /** * fpi_device_get_delete_data: * @device: The #FpDevice * @print: (out) (transfer none): The print to delete * * Get data for delete. */ void fpi_device_get_delete_data (FpDevice *device, FpPrint **print) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_DELETE); if (print) *print = g_task_get_task_data (priv->current_task); } /** * fpi_device_get_cancellable: * @device: The #FpDevice * * Retrieve the #GCancellable that may cancel the currently ongoing operation. This * is primarily useful to pass directly to e.g. fpi_usb_transfer_submit() for cancellable * transfers. * In many cases the cancel vfunc may be more convenient to react to cancellation in some * way. * * Returns: (transfer none): The #GCancellable for the current action. */ GCancellable * fpi_device_get_cancellable (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_val_if_fail (FP_IS_DEVICE (device), NULL); g_return_val_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE, NULL); return priv->current_cancellable; } static void emit_removed_on_task_completed (FpDevice *device) { g_signal_emit_by_name (device, "removed"); } /** * fpi_device_remove: * @device: The #FpDevice * * Called to signal to the #FpDevice that it has been unplugged (physically * removed from the system). * * For USB devices, this API is called automatically by #FpContext. */ void fpi_device_remove (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (!priv->is_removed); priv->is_removed = TRUE; g_object_notify (G_OBJECT (device), "removed"); /* If there is a pending action, we wait for it to fail, otherwise we * immediately emit the "removed" signal. */ if (priv->current_task) { g_signal_connect_object (priv->current_task, "notify::completed", (GCallback) emit_removed_on_task_completed, device, G_CONNECT_SWAPPED); } else { g_signal_emit_by_name (device, "removed"); } } /** * fpi_device_action_error: * @device: The #FpDevice * @error: The #GError to return * * Finish an ongoing action with an error. This is the same as calling * the corresponding complete function such as fpi_device_open_complete() * with an error set. If possible, use the correct complete function as * that results in improved error detection. */ void fpi_device_action_error (FpDevice *device, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE); if (error != NULL) { g_autofree char *action_str = NULL; action_str = g_enum_to_string (FPI_TYPE_DEVICE_ACTION, priv->current_action); g_debug ("Device reported generic error (%s) during action; action was: %s", error->message, action_str); } else { g_warning ("Device failed to pass an error to generic action error function"); error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Device reported error but did not provide an error condition"); } switch (priv->current_action) { case FPI_DEVICE_ACTION_PROBE: fpi_device_probe_complete (device, NULL, NULL, error); break; case FPI_DEVICE_ACTION_OPEN: fpi_device_open_complete (device, error); break; case FPI_DEVICE_ACTION_CLOSE: fpi_device_close_complete (device, error); break; case FPI_DEVICE_ACTION_ENROLL: fpi_device_enroll_complete (device, NULL, error); break; case FPI_DEVICE_ACTION_VERIFY: fpi_device_verify_complete (device, error); break; case FPI_DEVICE_ACTION_IDENTIFY: fpi_device_identify_complete (device, error); break; case FPI_DEVICE_ACTION_CAPTURE: fpi_device_capture_complete (device, NULL, error); break; case FPI_DEVICE_ACTION_DELETE: fpi_device_delete_complete (device, error); break; case FPI_DEVICE_ACTION_LIST: fpi_device_list_complete (device, NULL, error); break; case FPI_DEVICE_ACTION_CLEAR_STORAGE: fpi_device_clear_storage_complete (device, error); break; default: case FPI_DEVICE_ACTION_NONE: g_return_if_reached (); break; } } /** * fpi_device_critical_enter: * @device: The #FpDevice * * Enter a critical section in the driver code where no outside calls from * libfprint should happen. Drivers can already assume that everything * happens from the same thread, however, that still allows e.g. the cancel * vfunc to be called at any point in time. * * Using this kind of critical section, the driver can assume that libfprint * will not forward any external requests to the driver for the time being. * This is for example useful to prevent cancellation while the device is being * set up. Or, said differently, using this feature means that the cancel * handler is able to make more assumptions about the current state. * * Please note that the driver is not shielded from all external changes. For * example the cancellable as returned by fpi_device_get_cancellable() will * still change immediately. * * The driver may call this function multiple times, but must also ensure that * fpi_device_critical_leave() is called an equal amount of times and that all * critical sections are left before command completion. */ void fpi_device_critical_enter (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE); priv->critical_section += 1; /* Stop flushing events if that was previously queued. */ if (priv->critical_section_flush_source) g_source_destroy (priv->critical_section_flush_source); priv->critical_section_flush_source = NULL; } static gboolean fpi_device_critical_section_flush_idle_cb (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpDeviceClass *cls = FP_DEVICE_GET_CLASS (device); if (priv->cancel_queued) { /* Cancellation must only happen if the driver is busy. */ if (priv->current_action != FPI_DEVICE_ACTION_NONE && priv->current_task_idle_return_source == NULL) cls->cancel (device); priv->cancel_queued = FALSE; return G_SOURCE_CONTINUE; } if (priv->suspend_queued) { priv->suspend_queued = FALSE; fpi_device_suspend (device); return G_SOURCE_CONTINUE; } if (priv->resume_queued) { priv->resume_queued = FALSE; fpi_device_resume (device); return G_SOURCE_CONTINUE; } priv->critical_section_flush_source = NULL; return G_SOURCE_REMOVE; } /** * fpi_device_critical_leave: * @device: The #FpDevice * * Leave a critical section started by fpi_device_critical_enter(). * * Once all critical sections have been left, libfprint will start flushing * out the queued up requests. This is done from the mainloop and the driver * is protected from reentrency issues. */ void fpi_device_critical_leave (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE); g_return_if_fail (priv->critical_section); priv->critical_section -= 1; if (priv->critical_section) return; /* We left the critical section, make sure a flush is queued. */ if (priv->critical_section_flush_source) return; priv->critical_section_flush_source = g_idle_source_new (); g_source_set_priority (priv->critical_section_flush_source, G_PRIORITY_HIGH); g_source_set_callback (priv->critical_section_flush_source, (GSourceFunc) fpi_device_critical_section_flush_idle_cb, device, NULL); g_source_set_name (priv->critical_section_flush_source, "Flush libfprint driver critical section"); g_source_attach (priv->critical_section_flush_source, g_task_get_context (priv->current_task)); g_source_unref (priv->critical_section_flush_source); } 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 (priv->current_cancellable, priv->current_cancellable_id); priv->current_cancellable_id = 0; } if (priv->current_task_cancellable_id) { g_cancellable_disconnect (g_task_get_cancellable (priv->current_task), priv->current_task_cancellable_id); priv->current_task_cancellable_id = 0; } } typedef enum _FpDeviceTaskReturnType { FP_DEVICE_TASK_RETURN_INT, FP_DEVICE_TASK_RETURN_BOOL, FP_DEVICE_TASK_RETURN_OBJECT, FP_DEVICE_TASK_RETURN_PTR_ARRAY, FP_DEVICE_TASK_RETURN_ERROR, } FpDeviceTaskReturnType; typedef struct _FpDeviceTaskReturnData { FpDevice *device; FpDeviceTaskReturnType type; gpointer result; } FpDeviceTaskReturnData; static gboolean fp_device_task_return_in_idle_cb (gpointer user_data) { FpDeviceTaskReturnData *data = user_data; FpDevicePrivate *priv = fp_device_get_instance_private (data->device); g_autofree char *action_str = NULL; FpiDeviceAction action; g_autoptr(GTask) task = NULL; g_autoptr(GError) cancellation_reason = NULL; action_str = g_enum_to_string (FPI_TYPE_DEVICE_ACTION, priv->current_action); g_debug ("Completing action %s in idle!", action_str); task = g_steal_pointer (&priv->current_task); action = priv->current_action; priv->current_action = FPI_DEVICE_ACTION_NONE; priv->current_task_idle_return_source = NULL; g_clear_object (&priv->current_cancellable); cancellation_reason = g_steal_pointer (&priv->current_cancellation_reason); fpi_device_update_temp (data->device, FALSE); if (action == FPI_DEVICE_ACTION_OPEN && data->type != FP_DEVICE_TASK_RETURN_ERROR) { priv->is_open = TRUE; g_object_notify (G_OBJECT (data->device), "open"); } else if (action == FPI_DEVICE_ACTION_CLOSE) { /* Always consider the device closed. Drivers should try hard to close the * device. Generally, e.g. cancellations should be ignored. */ priv->is_open = FALSE; g_object_notify (G_OBJECT (data->device), "open"); } /* TODO: Port/use the cancellation mechanism for device removal! */ /* Return FP_DEVICE_ERROR_REMOVED if the device is removed, * with the exception of a successful open, which is an odd corner case. */ if (priv->is_removed && ((action != FPI_DEVICE_ACTION_OPEN) || (action == FPI_DEVICE_ACTION_OPEN && data->type == FP_DEVICE_TASK_RETURN_ERROR))) { g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_REMOVED)); /* NOTE: The removed signal will be emitted from the GTask * notify::completed if that is necessary. */ return G_SOURCE_REMOVE; } switch (data->type) { case FP_DEVICE_TASK_RETURN_INT: g_task_return_int (task, GPOINTER_TO_INT (data->result)); break; case FP_DEVICE_TASK_RETURN_BOOL: g_task_return_boolean (task, GPOINTER_TO_UINT (data->result)); break; case FP_DEVICE_TASK_RETURN_OBJECT: g_task_return_pointer (task, g_steal_pointer (&data->result), g_object_unref); break; case FP_DEVICE_TASK_RETURN_PTR_ARRAY: g_task_return_pointer (task, g_steal_pointer (&data->result), (GDestroyNotify) g_ptr_array_unref); break; case FP_DEVICE_TASK_RETURN_ERROR: /* Return internal cancellation reason instead if we have one. * Note that an external cancellation always returns G_IO_ERROR_CANCELLED */ if (cancellation_reason) { g_task_set_task_data (task, NULL, NULL); g_task_return_error (task, g_steal_pointer (&cancellation_reason)); } else { g_task_return_error (task, g_steal_pointer (&data->result)); } break; default: g_assert_not_reached (); } return G_SOURCE_REMOVE; } static void fpi_device_task_return_data_free (FpDeviceTaskReturnData *data) { switch (data->type) { case FP_DEVICE_TASK_RETURN_INT: case FP_DEVICE_TASK_RETURN_BOOL: break; case FP_DEVICE_TASK_RETURN_OBJECT: g_clear_object ((GObject **) &data->result); break; case FP_DEVICE_TASK_RETURN_PTR_ARRAY: g_clear_pointer ((GPtrArray **) &data->result, g_ptr_array_unref); break; case FP_DEVICE_TASK_RETURN_ERROR: g_clear_error ((GError **) &data->result); break; default: g_assert_not_reached (); } g_object_unref (data->device); g_free (data); } /** * fpi_device_return_task_in_idle: * @device: The #FpDevice * @return_type: The #FpDeviceTaskReturnType of @return_data * @return_data: (nullable) (transfer full): The data to return. * * Completes a #FpDevice task in an idle source, stealing the ownership of * the passed @returned_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, g_task_get_context (priv->current_task)); 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: (nullable) (transfer full): The #GError or %NULL on success * * Finish an ongoing probe operation. If error is %NULL success is assumed. */ void fpi_device_probe_complete (FpDevice *device, const gchar *device_id, const gchar *device_name, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_PROBE); g_debug ("Device reported probe completion"); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (!error) { if (device_id) { g_clear_pointer (&priv->device_id, g_free); priv->device_id = g_strdup (device_id); g_object_notify (G_OBJECT (device), "device-id"); } if (device_name) { g_clear_pointer (&priv->device_name, g_free); priv->device_name = g_strdup (device_name); g_object_notify (G_OBJECT (device), "name"); } fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, GUINT_TO_POINTER (TRUE)); } else { fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } } /** * fpi_device_open_complete: * @device: The #FpDevice * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish an ongoing open operation. If error is %NULL success is assumed. */ void fpi_device_open_complete (FpDevice *device, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_OPEN); g_debug ("Device reported open completion"); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (!error) fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, GUINT_TO_POINTER (TRUE)); else fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } /** * fpi_device_close_complete: * @device: The #FpDevice * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish an ongoing close operation. If error is %NULL success is assumed. */ void fpi_device_close_complete (FpDevice *device, GError *error) { GError *nested_error = NULL; FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_CLOSE); g_debug ("Device reported close completion"); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); switch (priv->type) { case FP_DEVICE_TYPE_USB: if (!g_usb_device_close (priv->usb_device, &nested_error)) { if (error == NULL) error = nested_error; fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); return; } break; case FP_DEVICE_TYPE_VIRTUAL: case FP_DEVICE_TYPE_UDEV: break; default: g_assert_not_reached (); fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); return; } if (!error) fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, GUINT_TO_POINTER (TRUE)); else fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } /** * fpi_device_enroll_complete: * @device: The #FpDevice * @print: (nullable) (transfer full): The #FpPrint or %NULL on failure * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish an ongoing enroll operation. The #FpPrint can be stored by the * caller for later verification. */ void fpi_device_enroll_complete (FpDevice *device, FpPrint *print, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_ENROLL); g_debug ("Device reported enroll completion"); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (!error) { if (FP_IS_PRINT (print)) { FpiPrintType print_type; g_autofree char *finger_str = NULL; g_object_get (print, "fpi-type", &print_type, NULL); if (print_type == FPI_PRINT_UNDEFINED) { g_warning ("Driver did not set the type on the returned print!"); g_clear_object (&print); error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Driver provided incorrect print data!"); fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); return; } finger_str = g_enum_to_string (FP_TYPE_FINGER, fp_print_get_finger (print)); g_debug ("Print for finger %s enrolled", finger_str); fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_OBJECT, print); } else { g_warning ("Driver did not provide a valid print and failed to provide an error!"); error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Driver failed to provide print data!"); fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } } else { fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); if (FP_IS_PRINT (print)) { g_warning ("Driver passed an error but also provided a print, returning error!"); g_object_unref (print); } } } /** * fpi_device_verify_complete: * @device: The #FpDevice * @error: A #GError if result is %FPI_MATCH_ERROR * * Finish an ongoing verify operation. * * Note that @error should only be set for actual errors. In the case * of retry errors, report these using fpi_device_verify_report() * and then call this function without any error argument. * * If @error is not set, we expect that a result (and print, in case) * have been already reported via fpi_device_verify_report(). */ void fpi_device_verify_complete (FpDevice *device, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpMatchData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_VERIFY); g_debug ("Device reported verify completion"); data = g_task_get_task_data (priv->current_task); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (!error) { if (!data->result_reported) { g_warning ("Driver reported successful verify complete but did not report the result earlier. Reporting error instead"); fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); } else if (data->error) { fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, g_steal_pointer (&data->error)); } else { fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_INT, GINT_TO_POINTER (data->match != NULL ? FPI_MATCH_SUCCESS : FPI_MATCH_FAIL)); } } else { /* Replace a retry error with a general error, this is a driver bug. */ if (error->domain == FP_DEVICE_RETRY) { g_warning ("Driver reported a retry error to fpi_device_verify_complete. " "This is not permissible and needs to be reported using " "fpi_device_verify_report, reporting general verification " "failure instead."); g_clear_error (&error); error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); } fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } } /** * fpi_device_identify_complete: * @device: The #FpDevice * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish an ongoing identify operation. * * Note that @error should only be set for actual errors. In the case * of retry errors, report these using fpi_device_identify_report() * and then call this function without any error argument. * * If @error is not set, we expect that a match and / or a print have been * already reported via fpi_device_identify_report() */ void fpi_device_identify_complete (FpDevice *device, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpMatchData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_IDENTIFY); g_debug ("Device reported identify completion"); data = g_task_get_task_data (priv->current_task); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (!error) { if (!data->result_reported) { g_warning ("Driver reported successful identify complete but did not report the result earlier. Reporting error instead"); fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); } else if (data->error) { fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, g_steal_pointer (&data->error)); } else { fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, GUINT_TO_POINTER (TRUE)); } } else { /* Replace a retry error with a general error, this is a driver bug. */ if (error->domain == FP_DEVICE_RETRY) { g_warning ("Driver reported a retry error to fpi_device_identify_complete. " "This is not permissible and needs to be reported using " "fpi_device_identify_report, reporting general identification " "failure instead."); g_clear_error (&error); error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); } fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } } /** * fpi_device_capture_complete: * @device: The #FpDevice * @image: The #FpImage, or %NULL on error * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish an ongoing capture operation. */ void fpi_device_capture_complete (FpDevice *device, FpImage *image, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_CAPTURE); g_debug ("Device reported capture completion"); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (!error) { if (image) { fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_OBJECT, image); } else { g_warning ("Driver did not provide an error for a failed capture operation!"); error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Driver failed to provide an error!"); fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } } else { fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); if (image) { g_warning ("Driver passed an error but also provided an image, returning error!"); g_clear_object (&image); } } } /** * fpi_device_delete_complete: * @device: The #FpDevice * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish an ongoing delete operation. */ void fpi_device_delete_complete (FpDevice *device, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_DELETE); g_debug ("Device reported deletion completion"); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (!error) fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, GUINT_TO_POINTER (TRUE)); else fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } /** * fpi_device_list_complete: * @device: The #FpDevice * @prints: (element-type FpPrint) (transfer container): Possibly empty array of prints or %NULL on error * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish an ongoing list operation. * * Please note that the @prints array will be free'ed using * g_ptr_array_unref() and the elements are destroyed automatically. * As such, you must use g_ptr_array_new_with_free_func() with * g_object_unref() as free func to create the array. */ void fpi_device_list_complete (FpDevice *device, GPtrArray *prints, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_LIST); g_debug ("Device reported listing completion"); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (prints && error) { g_warning ("Driver reported back prints and error, ignoring prints"); g_clear_pointer (&prints, g_ptr_array_unref); } else if (!prints && !error) { g_warning ("Driver did not pass array but failed to provide an error"); error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Driver failed to provide a list of prints"); } if (!error) fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_PTR_ARRAY, prints); else fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } static int update_attr (const char *attr, const char *value) { int fd, err; gssize r; char buf[50] = { 0 }; fd = open (attr, O_RDONLY); err = -errno; if (fd < 0) return -err; r = read (fd, buf, sizeof (buf) - 1); err = errno; close (fd); if (r < 0) return -err; g_strchomp (buf); if (g_strcmp0 (buf, value) == 0) return 0; /* O_TRUNC makes things work in the umockdev environment */ fd = open (attr, O_WRONLY | O_TRUNC); err = errno; if (fd < 0) return -err; r = write (fd, value, strlen (value)); err = -errno; close (fd); if (r < 0) { /* Write failures are weird, and are worth a warning */ g_warning ("Could not write %s to %s", value, attr); return -err; } return 0; } static void complete_suspend_resume_task (FpDevice *device) { g_autoptr(GTask) task = NULL; FpDevicePrivate *priv = fp_device_get_instance_private (device); g_assert (priv->suspend_resume_task); task = g_steal_pointer (&priv->suspend_resume_task); g_task_return_boolean (task, TRUE); } void fpi_device_suspend (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); /* If the device is currently idle, just complete immediately. * For long running tasks, call the driver handler right away, for short * tasks, wait for completion and then return the task. */ switch (priv->current_action) { case FPI_DEVICE_ACTION_NONE: fpi_device_suspend_complete (device, NULL); break; case FPI_DEVICE_ACTION_ENROLL: case FPI_DEVICE_ACTION_VERIFY: case FPI_DEVICE_ACTION_IDENTIFY: case FPI_DEVICE_ACTION_CAPTURE: if (FP_DEVICE_GET_CLASS (device)->suspend) { if (priv->critical_section) priv->suspend_queued = TRUE; else FP_DEVICE_GET_CLASS (device)->suspend (device); } else { fpi_device_suspend_complete (device, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); } break; default: case FPI_DEVICE_ACTION_PROBE: case FPI_DEVICE_ACTION_OPEN: case FPI_DEVICE_ACTION_CLOSE: case FPI_DEVICE_ACTION_DELETE: case FPI_DEVICE_ACTION_LIST: case FPI_DEVICE_ACTION_CLEAR_STORAGE: g_signal_connect_object (priv->current_task, "notify::completed", G_CALLBACK (complete_suspend_resume_task), device, G_CONNECT_SWAPPED); break; } } void fpi_device_resume (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); switch (priv->current_action) { case FPI_DEVICE_ACTION_NONE: fpi_device_resume_complete (device, NULL); break; case FPI_DEVICE_ACTION_ENROLL: case FPI_DEVICE_ACTION_VERIFY: case FPI_DEVICE_ACTION_IDENTIFY: case FPI_DEVICE_ACTION_CAPTURE: if (FP_DEVICE_GET_CLASS (device)->resume) { if (priv->critical_section) priv->resume_queued = TRUE; else FP_DEVICE_GET_CLASS (device)->resume (device); } else { fpi_device_resume_complete (device, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); } break; default: case FPI_DEVICE_ACTION_PROBE: case FPI_DEVICE_ACTION_OPEN: case FPI_DEVICE_ACTION_CLOSE: case FPI_DEVICE_ACTION_DELETE: case FPI_DEVICE_ACTION_LIST: case FPI_DEVICE_ACTION_CLEAR_STORAGE: /* cannot happen as we make sure these tasks complete before suspend */ g_assert_not_reached (); complete_suspend_resume_task (device); break; } } void fpi_device_configure_wakeup (FpDevice *device, gboolean enabled) { FpDevicePrivate *priv = fp_device_get_instance_private (device); switch (priv->type) { case FP_DEVICE_TYPE_USB: { g_autoptr(GString) ports = NULL; g_autoptr(GUsbDevice) dev = NULL; const char *wakeup_command = enabled ? "enabled" : "disabled"; guint8 bus; g_autofree gchar *sysfs_wakeup = NULL; g_autofree gchar *sysfs_persist = NULL; int res; ports = g_string_new (NULL); bus = g_usb_device_get_bus (priv->usb_device); /* Walk up, skipping the root hub. */ g_set_object (&dev, priv->usb_device); while (TRUE) { g_autoptr(GUsbDevice) parent = g_usb_device_get_parent (dev); g_autofree gchar *port_str = NULL; guint8 port; if (!parent) break; port = g_usb_device_get_port_number (dev); port_str = g_strdup_printf ("%d.", port); g_string_prepend (ports, port_str); g_set_object (&dev, parent); } g_string_set_size (ports, ports->len - 1); sysfs_wakeup = g_strdup_printf ("/sys/bus/usb/devices/%d-%s/power/wakeup", bus, ports->str); res = update_attr (sysfs_wakeup, wakeup_command); if (res < 0) g_debug ("Failed to set %s to %s", sysfs_wakeup, wakeup_command); /* Persist means that the kernel tries to keep the USB device open * in case it is "replugged" due to suspend. * This is not helpful, as it will receive a reset and will be in a bad * state. Instead, seeing an unplug and a new device makes more sense. */ sysfs_persist = g_strdup_printf ("/sys/bus/usb/devices/%d-%s/power/persist", bus, ports->str); res = update_attr (sysfs_persist, "0"); if (res < 0) g_warning ("Failed to disable USB persist by writing to %s", sysfs_persist); break; } case FP_DEVICE_TYPE_VIRTUAL: case FP_DEVICE_TYPE_UDEV: break; default: g_assert_not_reached (); fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); return; } } static void fpi_device_suspend_completed (FpDevice *device) { g_autoptr(GTask) task = NULL; FpDevicePrivate *priv = fp_device_get_instance_private (device); /* We have an ongoing operation, allow the device to wake up the machine. */ if (priv->current_action != FPI_DEVICE_ACTION_NONE) fpi_device_configure_wakeup (device, TRUE); if (priv->critical_section) g_warning ("Driver was in a critical section at suspend time. It likely deadlocked!"); task = g_steal_pointer (&priv->suspend_resume_task); if (priv->suspend_error) g_task_return_error (task, g_steal_pointer (&priv->suspend_error)); else g_task_return_boolean (task, TRUE); } /** * fpi_device_suspend_complete: * @device: The #FpDevice * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish a suspend request. Only return a %NULL error if suspend has been * correctly configured and the current action as returned by * fpi_device_get_current_action() will continue to run after resume. * * In all other cases an error must be returned. Should this happen, the * current action will be cancelled before the error is forwarded to the * application. * * It is recommended to set @error to #FP_ERROR_NOT_IMPLEMENTED. */ void fpi_device_suspend_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->suspend_resume_task); g_return_if_fail (priv->suspend_error == NULL); priv->suspend_error = g_steal_pointer (&error); priv->is_suspended = TRUE; /* If there is no error, we have no running task, return immediately. */ if (!priv->suspend_error || !priv->current_task || g_task_get_completed (priv->current_task)) { fpi_device_suspend_completed (device); return; } /* Wait for completion of the current task. */ g_signal_connect_object (priv->current_task, "notify::completed", G_CALLBACK (fpi_device_suspend_completed), device, G_CONNECT_SWAPPED); /* And cancel any action that might be long-running. */ if (!priv->current_cancellation_reason) priv->current_cancellation_reason = fpi_device_error_new_msg (FP_DEVICE_ERROR_BUSY, "Cannot run while suspended."); g_cancellable_cancel (priv->current_cancellable); } /** * fpi_device_resume_complete: * @device: The #FpDevice * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish a resume request. */ void fpi_device_resume_complete (FpDevice *device, GError *error) { g_autoptr(GTask) task = NULL; FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->suspend_resume_task); priv->is_suspended = FALSE; fpi_device_configure_wakeup (device, FALSE); task = g_steal_pointer (&priv->suspend_resume_task); if (error) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); } /** * fpi_device_clear_storage_complete: * @device: The #FpDevice * @error: (nullable) (transfer full): The #GError or %NULL on success * * Finish an ongoing clear_storage operation. */ void fpi_device_clear_storage_complete (FpDevice *device, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_CLEAR_STORAGE); g_debug ("Device reported deletion completion"); clear_device_cancel_action (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (!error) fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL, GUINT_TO_POINTER (TRUE)); else fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } /** * fpi_device_enroll_progress: * @device: The #FpDevice * @completed_stages: The number of stages that are completed at this point * @print: (transfer floating): The #FpPrint for the newly completed stage or %NULL on failure * @error: (nullable) (transfer full): The #GError or %NULL on success * * Notify about the progress of the enroll operation. This is important for UI interaction. * The passed error may be used if a scan needs to be retried, use fpi_device_retry_new(). */ void fpi_device_enroll_progress (FpDevice *device, gint completed_stages, FpPrint *print, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpEnrollData *data; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_ENROLL); g_return_if_fail (error == NULL || error->domain == FP_DEVICE_RETRY); g_debug ("Device reported enroll progress, reported %i of %i have been completed", completed_stages, priv->nr_enroll_stages); if (print) g_object_ref_sink (print); if (error && print) { g_warning ("Driver passed an error and also provided a print, returning error!"); g_clear_object (&print); } data = g_task_get_task_data (priv->current_task); if (data->enroll_progress_cb) { data->enroll_progress_cb (device, completed_stages, print, data->enroll_progress_data, error); } g_clear_error (&error); g_clear_object (&print); } /** * fpi_device_verify_report: * @device: The #FpDevice * @result: The #FpiMatchResult of the operation * @print: (transfer floating) The scanned #FpPrint * @error: A #GError if result is %FPI_MATCH_ERROR * * Report the result of a verify operation. Note that the passed @error must be * a retry error with the %FP_DEVICE_RETRY domain. For all other error cases, * the error should passed to fpi_device_verify_complete(). */ void fpi_device_verify_report (FpDevice *device, FpiMatchResult result, FpPrint *print, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpMatchData *data = g_task_get_task_data (priv->current_task); gboolean call_cb = TRUE; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_VERIFY); g_return_if_fail (data->result_reported == FALSE); data->result_reported = TRUE; g_debug ("Device reported verify result"); if (print) print = g_object_ref_sink (print); if (error || result == FPI_MATCH_ERROR) { if (result != FPI_MATCH_ERROR) g_warning ("Driver reported an error code without setting match result to error!"); if (error == NULL) { g_warning ("Driver reported an error without specifying a retry code, assuming general retry error!"); error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); } if (print) { g_warning ("Driver reported a print together with an error!"); g_clear_object (&print); } data->error = error; if (error->domain != FP_DEVICE_RETRY) { g_warning ("Driver reported a verify error that was not in the retry domain, delaying report!"); call_cb = FALSE; } } else { if (result == FPI_MATCH_SUCCESS) { fpi_device_get_verify_data (device, &data->match); g_object_ref (data->match); } data->print = g_steal_pointer (&print); } if (call_cb && data->match_cb) data->match_cb (device, data->match, data->print, data->match_data, data->error); } /** * fpi_device_identify_report: * @device: The #FpDevice * @match: (transfer none): The #FpPrint from the gallery that matched * @print: (transfer floating): The scanned #FpPrint, set in the absence * of an error. * @error: A #GError of %FP_DEVICE_RETRY type if @match and @print are unset. * * Report the results of an identify operation. * * In case of successful identification @match is expected to be set to a * #FpPrint that matches one from the provided gallery, while @print * represents the scanned print and will be different. * * If there are no errors, it's expected that the device always reports the * recognized @print even if there is no @match with the provided gallery (that * can be potentially empty). This is required for application logic further * up in the stack, such as for enroll-duplicate checking. @print needs to be * sufficiently filled to do a comparison. * * In case of error, both @match and @print are expected to be %NULL. * Note that the passed @error must be a retry error from the %FP_DEVICE_RETRY * domain. For all other error cases, the error should passed to * fpi_device_identify_complete(). */ void fpi_device_identify_report (FpDevice *device, FpPrint *match, FpPrint *print, GError *error) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpMatchData *data = g_task_get_task_data (priv->current_task); gboolean call_cb = TRUE; g_return_if_fail (FP_IS_DEVICE (device)); g_return_if_fail (priv->current_action == FPI_DEVICE_ACTION_IDENTIFY); g_return_if_fail (data->result_reported == FALSE); data->result_reported = TRUE; if (match) g_object_ref (match); if (print) print = g_object_ref_sink (print); if (match && !g_ptr_array_find (data->gallery, match, NULL)) { g_warning ("Driver reported a match to a print that was not in the gallery, ignoring match."); g_clear_object (&match); } g_debug ("Device reported identify result"); if (error) { if (match != NULL) { g_warning ("Driver reported an error code but also provided a match!"); g_clear_object (&match); } if (print) { g_warning ("Driver reported a print together with an error!"); g_clear_object (&print); } data->error = error; if (error->domain != FP_DEVICE_RETRY) { g_warning ("Driver reported a verify error that was not in the retry domain, delaying report!"); call_cb = FALSE; } } else { if (match) data->match = g_steal_pointer (&match); if (print) data->print = g_steal_pointer (&print); } if (call_cb && data->match_cb) data->match_cb (device, data->match, data->print, data->match_data, data->error); } /** * fpi_device_report_finger_status: * @device: The #FpDevice * @finger_status: The current #FpFingerStatusFlags to report * * Report the finger status for the @device. * This can be used by UI to give a feedback * * Returns: %TRUE if changed */ gboolean fpi_device_report_finger_status (FpDevice *device, FpFingerStatusFlags finger_status) { FpDevicePrivate *priv = fp_device_get_instance_private (device); g_autofree char *status_string = NULL; if (priv->finger_status == finger_status) return FALSE; status_string = g_flags_to_string (FP_TYPE_FINGER_STATUS_FLAGS, finger_status); fp_dbg ("Device reported finger status change: %s", status_string); priv->finger_status = finger_status; g_object_notify (G_OBJECT (device), "finger-status"); return TRUE; } /** * fpi_device_report_finger_status_changes: * @device: The #FpDevice * @added_status: The #FpFingerStatusFlags to add * @removed_status: The #FpFingerStatusFlags to remove * * Report the finger status for the @device adding the @added_status flags * and removing the @removed_status flags. * * This can be used by UI to give a feedback * * Returns: %TRUE if changed */ gboolean fpi_device_report_finger_status_changes (FpDevice *device, FpFingerStatusFlags added_status, FpFingerStatusFlags removed_status) { FpDevicePrivate *priv = fp_device_get_instance_private (device); FpFingerStatusFlags finger_status = priv->finger_status; finger_status |= added_status; finger_status &= ~removed_status; return fpi_device_report_finger_status (device, finger_status); } static void update_temp_timeout (FpDevice *device, gpointer user_data) { FpDevicePrivate *priv = fp_device_get_instance_private (device); fpi_device_update_temp (device, priv->temp_last_active); } /** * fpi_device_update_temp: * @device: The #FpDevice * @is_active: Whether the device is now active * * Purely internal function to update the temperature. Also ensure that the * state is updated once a threshold is reached. */ void fpi_device_update_temp (FpDevice *device, gboolean is_active) { FpDevicePrivate *priv = fp_device_get_instance_private (device); gint64 now = g_get_monotonic_time (); gdouble passed_seconds; gdouble alpha; gdouble next_threshold; gdouble old_ratio; FpTemperature old_temp; g_autofree char *old_temp_str = NULL; g_autofree char *new_temp_str = NULL; if (priv->temp_hot_seconds < 0) { g_debug ("Not updating temperature model, device can run continuously!"); return; } passed_seconds = (now - priv->temp_last_update) / 1e6; old_ratio = priv->temp_current_ratio; if (priv->temp_last_active) { alpha = exp (-passed_seconds / priv->temp_hot_seconds); priv->temp_current_ratio = alpha * priv->temp_current_ratio + 1 - alpha; } else { alpha = exp (-passed_seconds / priv->temp_cold_seconds); priv->temp_current_ratio = alpha * priv->temp_current_ratio; } priv->temp_last_active = is_active; priv->temp_last_update = now; old_temp = priv->temp_current; if (priv->temp_current_ratio < TEMP_COLD_THRESH) { priv->temp_current = FP_TEMPERATURE_COLD; next_threshold = is_active ? TEMP_COLD_THRESH : -1.0; } else if (priv->temp_current_ratio < TEMP_HOT_WARM_THRESH) { priv->temp_current = FP_TEMPERATURE_WARM; next_threshold = is_active ? TEMP_WARM_HOT_THRESH : TEMP_COLD_THRESH; } else if (priv->temp_current_ratio < TEMP_WARM_HOT_THRESH) { /* Keep HOT until we reach TEMP_HOT_WARM_THRESH */ if (priv->temp_current != FP_TEMPERATURE_HOT) priv->temp_current = FP_TEMPERATURE_WARM; next_threshold = is_active ? TEMP_WARM_HOT_THRESH : TEMP_HOT_WARM_THRESH; } else { priv->temp_current = FP_TEMPERATURE_HOT; next_threshold = is_active ? -1.0 : TEMP_HOT_WARM_THRESH; } old_temp_str = g_enum_to_string (FP_TYPE_TEMPERATURE, old_temp); new_temp_str = g_enum_to_string (FP_TYPE_TEMPERATURE, priv->temp_current); g_debug ("Updated temperature model after %0.2f seconds, ratio %0.2f -> %0.2f, active %d -> %d, %s -> %s", passed_seconds, old_ratio, priv->temp_current_ratio, priv->temp_last_active, is_active, old_temp_str, new_temp_str); if (priv->temp_current != old_temp) g_object_notify (G_OBJECT (device), "temperature"); /* If the device is HOT, then do an internal cancellation of long running tasks. */ if (priv->temp_current == FP_TEMPERATURE_HOT) { if (priv->current_action == FPI_DEVICE_ACTION_ENROLL || priv->current_action == FPI_DEVICE_ACTION_VERIFY || priv->current_action == FPI_DEVICE_ACTION_IDENTIFY || priv->current_action == FPI_DEVICE_ACTION_CAPTURE) { if (!priv->current_cancellation_reason) priv->current_cancellation_reason = fpi_device_error_new (FP_DEVICE_ERROR_TOO_HOT); g_cancellable_cancel (priv->current_cancellable); } } g_clear_pointer (&priv->temp_timeout, g_source_destroy); if (next_threshold < 0) return; /* Set passed_seconds to the time until the next update is needed */ if (is_active) passed_seconds = -priv->temp_hot_seconds * log ((next_threshold - 1.0) / (priv->temp_current_ratio - 1.0)); else passed_seconds = -priv->temp_cold_seconds * log (next_threshold / priv->temp_current_ratio); passed_seconds += TEMP_DELAY_SECONDS; priv->temp_timeout = fpi_device_add_timeout (device, passed_seconds * 1000, update_temp_timeout, NULL, NULL); }