Blob Blame History Raw
From 3b368f67de150199ba2dfb083ab22e7fd2e4204f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
Date: Tue, 10 Dec 2019 20:24:04 +0100
Subject: [PATCH 105/181] fp-device: Move fpi code into its own unit that can
 be compiled a part

In order to be able to test the private device code (used by drivers) we
need to have that split a part in a different .c file so that we can compile
it alone and link with it both the shared library and the test executables.

Redefine fp_device_get_instance_private for private usage, not to move
the private struct as part of FpDevice.
---
 libfprint/fp-device-private.h |   65 ++
 libfprint/fp-device.c         | 1186 +--------------------------------
 libfprint/fpi-device.c        | 1177 ++++++++++++++++++++++++++++++++
 libfprint/meson.build         |    1 +
 4 files changed, 1245 insertions(+), 1184 deletions(-)
 create mode 100644 libfprint/fp-device-private.h
 create mode 100644 libfprint/fpi-device.c

diff --git a/libfprint/fp-device-private.h b/libfprint/fp-device-private.h
new file mode 100644
index 0000000..65fb1cb
--- /dev/null
+++ b/libfprint/fp-device-private.h
@@ -0,0 +1,65 @@
+/*
+ * FpDevice - A fingerprint reader device
+ * Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
+ * Copyright (C) 2019 Marco Trevisan <marco.trevisan@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "fpi-device.h"
+
+typedef struct
+{
+  FpDeviceType type;
+
+  GUsbDevice  *usb_device;
+  const gchar *virtual_env;
+
+  gboolean     is_open;
+
+  gchar       *device_id;
+  gchar       *device_name;
+  FpScanType   scan_type;
+
+  guint64      driver_data;
+
+  gint         nr_enroll_stages;
+  GSList      *sources;
+
+  /* We always make sure that only one task is run at a time. */
+  FpDeviceAction      current_action;
+  GTask              *current_task;
+  GAsyncReadyCallback current_user_cb;
+  gulong              current_cancellable_id;
+  GSource            *current_idle_cancel_source;
+  GSource            *current_task_idle_return_source;
+
+  /* State for tasks */
+  gboolean wait_for_finger;
+} FpDevicePrivate;
+
+
+typedef struct
+{
+  FpPrint         *print;
+
+  FpEnrollProgress enroll_progress_cb;
+  gpointer         enroll_progress_data;
+  GDestroyNotify   enroll_progress_destroy;
+} FpEnrollData;
+
+void enroll_data_free (FpEnrollData *enroll_data);
diff --git a/libfprint/fp-device.c b/libfprint/fp-device.c
index 91a3aae..c49e5a9 100644
--- a/libfprint/fp-device.c
+++ b/libfprint/fp-device.c
@@ -21,17 +21,7 @@
 #define FP_COMPONENT "device"
 #include "fpi-log.h"
 
-#include "fpi-device.h"
-
-/**
- * SECTION: fp-device
- * @title: FpDevice
- * @short_description: Fingerprint device handling
- *
- * The #FpDevice object allows you to interact with fingerprint readers.
- * Befor doing any other operation you need to fp_device_open() the device
- * and after you are done you need to fp_device_close() it again.
- */
+#include "fp-device-private.h"
 
 /**
  * SECTION: fpi-device
@@ -46,36 +36,6 @@
  * Also see the public #FpDevice routines.
  */
 
-typedef struct
-{
-  FpDeviceType type;
-
-  GUsbDevice  *usb_device;
-  const gchar *virtual_env;
-
-  gboolean   is_open;
-
-  gchar     *device_id;
-  gchar     *device_name;
-  FpScanType scan_type;
-
-  guint64    driver_data;
-
-  gint       nr_enroll_stages;
-  GSList    *sources;
-
-  /* We always make sure that only one task is run at a time. */
-  FpDeviceAction      current_action;
-  GTask              *current_task;
-  GAsyncReadyCallback current_user_cb;
-  gulong              current_cancellable_id;
-  GSource            *current_idle_cancel_source;
-  GSource            *current_task_idle_return_source;
-
-  /* State for tasks */
-  gboolean wait_for_finger;
-} FpDevicePrivate;
-
 static void fp_device_async_initable_iface_init (GAsyncInitableIface *iface);
 
 G_DEFINE_TYPE_EXTENDED (FpDevice, fp_device, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT,
@@ -99,27 +59,6 @@ enum {
 
 static GParamSpec *properties[N_PROPS];
 
-typedef struct
-{
-  FpPrint         *print;
-
-  FpEnrollProgress enroll_progress_cb;
-  gpointer         enroll_progress_data;
-  GDestroyNotify   enroll_progress_destroy;
-} FpEnrollData;
-
-static void
-enroll_data_free (gpointer free_data)
-{
-  FpEnrollData *data = free_data;
-
-  if (data->enroll_progress_destroy)
-    data->enroll_progress_destroy (data->enroll_progress_data);
-  data->enroll_progress_data = NULL;
-  g_clear_object (&data->print);
-  g_free (data);
-}
-
 /**
  * fp_device_retry_quark:
  *
@@ -134,150 +73,6 @@ G_DEFINE_QUARK (fp - device - retry - quark, fp_device_retry)
  **/
 G_DEFINE_QUARK (fp - device - error - quark, fp_device_error)
 
-/**
- * fpi_device_retry_new:
- * @error: The #FpDeviceRetry error value describing the issue
- *
- * Create a new retry error code for use with fpi_device_verify_complete()
- * and similar calls.
- */
-GError *
-fpi_device_retry_new (FpDeviceRetry error)
-{
-  const gchar *msg;
-
-  switch (error)
-    {
-    case FP_DEVICE_RETRY_GENERAL:
-      msg = "Please try again.";
-      break;
-
-    case FP_DEVICE_RETRY_TOO_SHORT:
-      msg = "The swipe was too short, please try again.";
-      break;
-
-    case FP_DEVICE_RETRY_CENTER_FINGER:
-      msg = "The finger was not centered properly, please try again.";
-      break;
-
-    case FP_DEVICE_RETRY_REMOVE_FINGER:
-      msg = "Please try again after removing the finger first.";
-      break;
-
-    default:
-      g_warning ("Unsupported error, returning general error instead!");
-      error = FP_DEVICE_RETRY_GENERAL;
-      msg = "Please try again.";
-    }
-
-  return g_error_new_literal (FP_DEVICE_RETRY, error, msg);
-}
-
-/**
- * fpi_device_error_new:
- * @error: The #FpDeviceRetry error value describing the issue
- *
- * Create a new error code for use with fpi_device_verify_complete() and
- * similar calls.
- */
-GError *
-fpi_device_error_new (FpDeviceError error)
-{
-  const gchar *msg;
-
-  switch (error)
-    {
-    case FP_DEVICE_ERROR_GENERAL:
-      msg = "An unspecified error occured!";
-      break;
-
-    case FP_DEVICE_ERROR_NOT_SUPPORTED:
-      msg = "The operation is not supported on this device!";
-      break;
-
-    case FP_DEVICE_ERROR_NOT_OPEN:
-      msg = "The device needs to be opened first!";
-      break;
-
-    case FP_DEVICE_ERROR_ALREADY_OPEN:
-      msg = "The device has already been opened!";
-      break;
-
-    case FP_DEVICE_ERROR_BUSY:
-      msg = "The device is still busy with another operation, please try again later.";
-      break;
-
-    case FP_DEVICE_ERROR_PROTO:
-      msg = "The driver encountered a protocol error with the device.";
-      break;
-
-    case FP_DEVICE_ERROR_DATA_INVALID:
-      msg = "Passed (print) data is not valid.";
-      break;
-
-    case FP_DEVICE_ERROR_DATA_FULL:
-      msg = "On device storage space is full.";
-      break;
-
-    case FP_DEVICE_ERROR_DATA_NOT_FOUND:
-      msg = "Print was not found on the devices storage.";
-      break;
-
-    default:
-      g_warning ("Unsupported error, returning general error instead!");
-      error = FP_DEVICE_ERROR_GENERAL;
-      msg = "An unspecified error occured!";
-    }
-
-  return g_error_new_literal (FP_DEVICE_ERROR, error, msg);
-}
-
-/**
- * fpi_device_retry_new_msg:
- * @error: The #FpDeviceRetry error value describing the issue
- * @msg: Custom message to use
- *
- * Create a new retry error code for use with fpi_device_verify_complete()
- * and similar calls.
- */
-GError *
-fpi_device_retry_new_msg (FpDeviceRetry device_error,
-                          const gchar  *msg,
-                          ...)
-{
-  GError *error;
-  va_list args;
-
-  va_start (args, msg);
-  error = g_error_new_valist (FP_DEVICE_RETRY, device_error, msg, args);
-  va_end (args);
-
-  return error;
-}
-
-/**
- * fpi_device_error_new_msg:
- * @error: The #FpDeviceRetry error value describing the issue
- * @msg: Custom message to use
- *
- * Create a new error code for use with fpi_device_verify_complete()
- * and similar calls.
- */
-GError *
-fpi_device_error_new_msg (FpDeviceError device_error,
-                          const gchar  *msg,
-                          ...)
-{
-  GError *error;
-  va_list args;
-
-  va_start (args, msg);
-  error = g_error_new_valist (FP_DEVICE_ERROR, device_error, msg, args);
-  va_end (args);
-
-  return error;
-}
-
 static gboolean
 fp_device_cancel_in_idle_cb (gpointer user_data)
 {
@@ -330,21 +125,6 @@ maybe_cancel_on_cancelled (FpDevice     *device,
                                                         NULL);
 }
 
-static void
-clear_device_cancel_action (FpDevice *device)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_clear_pointer (&priv->current_idle_cancel_source, g_source_destroy);
-
-  if (priv->current_cancellable_id)
-    {
-      g_cancellable_disconnect (g_task_get_cancellable (priv->current_task),
-                                priv->current_cancellable_id);
-      priv->current_cancellable_id = 0;
-    }
-}
-
 static void
 fp_device_constructed (GObject *object)
 {
@@ -976,7 +756,7 @@ fp_device_enroll (FpDevice           *device,
   data->enroll_progress_data = progress_data;
 
   // Attach the progress data as task data so that it is destroyed
-  g_task_set_task_data (priv->current_task, data, enroll_data_free);
+  g_task_set_task_data (priv->current_task, data, (GDestroyNotify) enroll_data_free);
 
   FP_DEVICE_GET_CLASS (device)->enroll (device);
 }
@@ -1406,968 +1186,6 @@ fp_device_list_prints_finish (FpDevice     *device,
   return g_task_propagate_pointer (G_TASK (result), error);
 }
 
-typedef struct
-{
-  GSource   source;
-  FpDevice *device;
-} FpDeviceTimeoutSource;
-
-static void
-timeout_finalize (GSource *source)
-{
-  FpDeviceTimeoutSource *timeout_source = (FpDeviceTimeoutSource *) source;
-  FpDevicePrivate *priv;
-
-  priv = fp_device_get_instance_private (timeout_source->device);
-  priv->sources = g_slist_remove (priv->sources, source);
-}
-
-static gboolean
-timeout_dispatch (GSource *source, GSourceFunc gsource_func, gpointer user_data)
-{
-  FpDeviceTimeoutSource *timeout_source = (FpDeviceTimeoutSource *) source;
-  FpTimeoutFunc callback = (FpTimeoutFunc) gsource_func;
-
-  callback (timeout_source->device, user_data);
-
-  return G_SOURCE_REMOVE;
-}
-
-static GSourceFuncs timeout_funcs = {
-  NULL, /* prepare */
-  NULL, /* check */
-  timeout_dispatch,
-  timeout_finalize,
-  NULL, NULL
-};
-
-/* Private API functions */
-
-/**
- * fpi_device_set_nr_enroll_stages:
- * @device: The #FpDevice
- * @enroll_stages: The number of enroll stages
- *
- * Updates the reported number of enroll stages that the device needs.
- * If all supported devices have the same number of stages, then the
- * value can simply be set in the class.
- */
-void
-fpi_device_set_nr_enroll_stages (FpDevice *device,
-                                 gint      enroll_stages)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-
-  priv->nr_enroll_stages = enroll_stages;
-  g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_NR_ENROLL_STAGES]);
-}
-
-/**
- * fpi_device_set_scan_type:
- * @device: The #FpDevice
- * @scan_type: The scan type of the device
- *
- * Updates the the scan type of the device from the default.
- * If all supported devices have the same scan type, then the
- * value can simply be set in the class.
- */
-void
-fpi_device_set_scan_type (FpDevice  *device,
-                          FpScanType scan_type)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-
-  priv->scan_type = scan_type;
-  g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_SCAN_TYPE]);
-}
-
-/**
- * fpi_device_add_timeout:
- * @device: The #FpDevice
- * @interval: The interval in milliseconds
- * @func: The #FpTimeoutFunc to call on timeout
- * @user_data: (nullable): User data to pass to the callback
- * @destroy_notify: (nullable): #GDestroyNotify for @user_data
- *
- * Register a timeout to run. Drivers should always make sure that timers are
- * cancelled when appropriate.
- *
- * Returns: (transfer none): A newly created and attached #GSource
- */
-GSource *
-fpi_device_add_timeout (FpDevice      *device,
-                        gint           interval,
-                        FpTimeoutFunc  func,
-                        gpointer       user_data,
-                        GDestroyNotify destroy_notify)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-  FpDeviceTimeoutSource *source;
-
-  source = (FpDeviceTimeoutSource *) g_source_new (&timeout_funcs,
-                                                   sizeof (FpDeviceTimeoutSource));
-  source->device = device;
-
-  g_source_attach (&source->source, NULL);
-  g_source_set_callback (&source->source, (GSourceFunc) func, user_data, destroy_notify);
-  g_source_set_ready_time (&source->source,
-                           g_source_get_time (&source->source) + interval * (guint64) 1000);
-  priv->sources = g_slist_prepend (priv->sources, source);
-  g_source_unref (&source->source);
-
-  return &source->source;
-}
-
-/**
- * fpi_device_get_usb_device:
- * @device: The #FpDevice
- *
- * Get the #GUsbDevice for this #FpDevice. Only permissible to call if the
- * #FpDevice is of type %FP_DEVICE_TYPE_USB.
- *
- * Returns: The #GUsbDevice
- */
-GUsbDevice *
-fpi_device_get_usb_device (FpDevice *device)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
-  g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_USB, NULL);
-
-  return priv->usb_device;
-}
-
-/**
- * fpi_device_get_virtual_env:
- * @device: The #FpDevice
- *
- * Get the value of the environment variable that caused the virtual #FpDevice to be
- * generated. Only permissible to call if the #FpDevice is of type %FP_DEVICE_TYPE_VIRTUAL.
- *
- * Returns: The value of the environment variable
- */
-const gchar *
-fpi_device_get_virtual_env (FpDevice *device)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
-  g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_VIRTUAL, NULL);
-
-  return priv->virtual_env;
-}
-
-/**
- * fpi_device_get_current_action:
- * @device: The #FpDevice
- *
- * Get the currently ongoing action or %FP_DEVICE_ACTION_NONE if there
- * is no operation at this time.
- *
- * This is useful for drivers that might share code paths between different
- * actions (e.g. verify and identify) and want to find out again later which
- * action was started in the beginning.
- *
- * Returns: The ongoing #FpDeviceAction
- */
-FpDeviceAction
-fpi_device_get_current_action (FpDevice *device)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_val_if_fail (FP_IS_DEVICE (device), FP_DEVICE_ACTION_NONE);
-
-  return priv->current_action;
-}
-
-/**
- * fpi_device_action_is_cancelled:
- * @device: The #FpDevice
- *
- * Checks whether the current action has been cancelled by the user.
- * This is equivalent to first getting the cancellable using
- * fpi_device_get_cancellable() and then checking whether it has been
- * cancelled (if it is non-NULL).
- *
- * Returns: %TRUE if action should be cancelled
- */
-gboolean
-fpi_device_action_is_cancelled (FpDevice *device)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-  GCancellable *cancellable;
-
-  g_return_val_if_fail (FP_IS_DEVICE (device), TRUE);
-  g_return_val_if_fail (priv->current_action != FP_DEVICE_ACTION_NONE, TRUE);
-
-  cancellable = g_task_get_cancellable (priv->current_task);
-
-  return cancellable ? g_cancellable_is_cancelled (cancellable) : FALSE;
-}
-
-/**
- * fpi_device_get_driver_data:
- * @device: The #FpDevice
- *
- * Returns: The driver data from the #FpIdEntry table entry
- */
-guint64
-fpi_device_get_driver_data (FpDevice *device)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_val_if_fail (FP_IS_DEVICE (device), 0);
-
-  return priv->driver_data;
-}
-
-/**
- * fpi_device_get_enroll_data:
- * @device: The #FpDevice
- * @print: (out) (transfer none): The user provided template print
- *
- * Get data for enrollment.
- */
-void
-fpi_device_get_enroll_data (FpDevice *device,
-                            FpPrint **print)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-  FpEnrollData *data;
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_ENROLL);
-
-  data = g_task_get_task_data (priv->current_task);
-  g_assert (data);
-
-  if (print)
-    *print = data->print;
-}
-
-/**
- * fpi_device_get_capture_data:
- * @device: The #FpDevice
- * @wait_for_finger: (out): Whether to wait for finger or not
- *
- * Get data for capture.
- */
-void
-fpi_device_get_capture_data (FpDevice *device,
-                             gboolean *wait_for_finger)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_CAPTURE);
-
-  if (wait_for_finger)
-    *wait_for_finger = priv->wait_for_finger;
-}
-
-/**
- * fpi_device_get_verify_data:
- * @device: The #FpDevice
- * @print: (out) (transfer none): The enrolled print
- *
- * Get data for verify.
- */
-void
-fpi_device_get_verify_data (FpDevice *device,
-                            FpPrint **print)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_VERIFY);
-
-  if (print)
-    *print = g_task_get_task_data (priv->current_task);
-}
-
-/**
- * fpi_device_get_identify_data:
- * @device: The #FpDevice
- * @prints: (out) (transfer none) (element-type FpPrint): The gallery of prints
- *
- * Get data for identify.
- */
-void
-fpi_device_get_identify_data (FpDevice   *device,
-                              GPtrArray **prints)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_IDENTIFY);
-
-  if (prints)
-    *prints = g_task_get_task_data (priv->current_task);
-}
-
-/**
- * fpi_device_get_delete_data:
- * @device: The #FpDevice
- * @print: (out) (transfer none): The print to delete
- *
- * Get data for delete.
- */
-void
-fpi_device_get_delete_data (FpDevice *device,
-                            FpPrint **print)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_DELETE);
-
-  if (print)
-    *print = g_task_get_task_data (priv->current_task);
-}
-
-/**
- * fpi_device_get_cancellable:
- * @device: The #FpDevice
- *
- * Retrieve the #GCancellable that may cancel the currently ongoing operation. This
- * is primarily useful to pass directly to e.g. fpi_usb_transfer_submit() for cancellable
- * transfers.
- * In many cases the cancel vfunc may be more convenient to react to cancellation in some
- * way.
- *
- * Returns: (transfer none): The #GCancellable for the current action.
- */
-GCancellable *
-fpi_device_get_cancellable (FpDevice *device)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
-  g_return_val_if_fail (priv->current_action != FP_DEVICE_ACTION_NONE, NULL);
-
-  return g_task_get_cancellable (priv->current_task);
-}
-
-/**
- * fpi_device_action_error:
- * @device: The #FpDevice
- * @error: The #GError to return
- *
- * Finish an ongoing action with an error. This is the same as calling
- * the corresponding complete function such as fpi_device_open_complete()
- * with an error set. If possible, use the correct complete function as
- * that results in improved error detection.
- */
-void
-fpi_device_action_error (FpDevice *device,
-                         GError   *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action != FP_DEVICE_ACTION_NONE);
-
-  if (error != NULL)
-    {
-      g_debug ("Device reported generic error during action; action was: %i", priv->current_action);
-    }
-  else
-    {
-      g_warning ("Device failed to pass an error to generic action error function");
-      error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Device reported error but did not provide an error condition");
-    }
-
-
-  switch (priv->current_action)
-    {
-    case FP_DEVICE_ACTION_PROBE:
-      fpi_device_probe_complete (device, NULL, NULL, error);
-      break;
-
-    case FP_DEVICE_ACTION_OPEN:
-      fpi_device_open_complete (device, error);
-      break;
-
-    case FP_DEVICE_ACTION_CLOSE:
-      fpi_device_close_complete (device, error);
-      break;
-
-    case FP_DEVICE_ACTION_ENROLL:
-      fpi_device_enroll_complete (device, NULL, error);
-      break;
-
-    case FP_DEVICE_ACTION_VERIFY:
-      fpi_device_verify_complete (device, FPI_MATCH_ERROR, NULL, error);
-      break;
-
-    case FP_DEVICE_ACTION_IDENTIFY:
-      fpi_device_identify_complete (device, NULL, NULL, error);
-      break;
-
-    case FP_DEVICE_ACTION_CAPTURE:
-      fpi_device_capture_complete (device, NULL, error);
-      break;
-
-    case FP_DEVICE_ACTION_DELETE:
-      fpi_device_delete_complete (device, error);
-      break;
-
-    case FP_DEVICE_ACTION_LIST:
-      fpi_device_list_complete (device, NULL, error);
-      break;
-
-    default:
-    case FP_DEVICE_ACTION_NONE:
-      g_return_if_reached ();
-      break;
-    }
-}
-
-typedef enum _FpDeviceTaskReturnType {
-  FP_DEVICE_TASK_RETURN_INT,
-  FP_DEVICE_TASK_RETURN_BOOL,
-  FP_DEVICE_TASK_RETURN_OBJECT,
-  FP_DEVICE_TASK_RETURN_PTR_ARRAY,
-  FP_DEVICE_TASK_RETURN_ERROR,
-} FpDeviceTaskReturnType;
-
-typedef struct _FpDeviceTaskReturnData
-{
-  FpDevice              *device;
-  FpDeviceTaskReturnType type;
-  gpointer               result;
-} FpDeviceTaskReturnData;
-
-static gboolean
-fp_device_task_return_in_idle_cb (gpointer user_data)
-{
-  FpDeviceTaskReturnData *data = user_data;
-  FpDevicePrivate *priv = fp_device_get_instance_private (data->device);
-
-  g_autoptr(GTask) task = NULL;
-
-  g_debug ("Completing action %d in idle!", priv->current_action);
-
-  task = g_steal_pointer (&priv->current_task);
-  priv->current_action = FP_DEVICE_ACTION_NONE;
-  priv->current_task_idle_return_source = NULL;
-
-  switch (data->type)
-    {
-    case FP_DEVICE_TASK_RETURN_INT:
-      g_task_return_int (task, GPOINTER_TO_INT (data->result));
-      break;
-
-    case FP_DEVICE_TASK_RETURN_BOOL:
-      g_task_return_boolean (task, GPOINTER_TO_UINT (data->result));
-      break;
-
-    case FP_DEVICE_TASK_RETURN_OBJECT:
-      g_task_return_pointer (task, data->result, g_object_unref);
-      break;
-
-    case FP_DEVICE_TASK_RETURN_PTR_ARRAY:
-      g_task_return_pointer (task, data->result,
-                             (GDestroyNotify) g_ptr_array_unref);
-      break;
-
-    case FP_DEVICE_TASK_RETURN_ERROR:
-      g_task_return_error (task, data->result);
-      break;
-
-    default:
-      g_assert_not_reached ();
-    }
-
-  return G_SOURCE_REMOVE;
-}
-
-static void
-fp_device_task_return_data_free (FpDeviceTaskReturnData *data)
-{
-  g_object_unref (data->device);
-  g_free (data);
-}
-
-static void
-fp_device_return_task_in_idle (FpDevice              *device,
-                               FpDeviceTaskReturnType return_type,
-                               gpointer               return_data)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-  FpDeviceTaskReturnData *data;
-
-  data = g_new0 (FpDeviceTaskReturnData, 1);
-  data->device = g_object_ref (device);
-  data->type = return_type;
-  data->result = return_data;
-
-  priv->current_task_idle_return_source = g_idle_source_new ();
-  g_source_set_priority (priv->current_task_idle_return_source,
-                         g_task_get_priority (priv->current_task));
-  g_source_set_callback (priv->current_task_idle_return_source,
-                         fp_device_task_return_in_idle_cb,
-                         data,
-                         (GDestroyNotify) fp_device_task_return_data_free);
-
-  g_source_attach (priv->current_task_idle_return_source, NULL);
-  g_source_unref (priv->current_task_idle_return_source);
-}
-
-/**
- * fpi_device_probe_complete:
- * @device: The #FpDevice
- * @device_id: Unique ID for the device or %NULL
- * @device_name: Human readable name or %NULL for driver name
- * @error: The #GError or %NULL on success
- *
- * Finish an ongoing probe operation. If error is %NULL success is assumed.
- */
-void
-fpi_device_probe_complete (FpDevice    *device,
-                           const gchar *device_id,
-                           const gchar *device_name,
-                           GError      *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_PROBE);
-
-  g_debug ("Device reported probe completion");
-
-  clear_device_cancel_action (device);
-
-  if (!error)
-    {
-      if (device_id)
-        {
-          g_clear_pointer (&priv->device_id, g_free);
-          priv->device_id = g_strdup (device_id);
-          g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_DEVICE_ID]);
-        }
-      if (device_name)
-        {
-          g_clear_pointer (&priv->device_name, g_free);
-          priv->device_name = g_strdup (device_name);
-          g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_NAME]);
-        }
-      fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
-                                     GUINT_TO_POINTER (TRUE));
-    }
-  else
-    {
-      fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-    }
-}
-
-/**
- * fpi_device_open_complete:
- * @device: The #FpDevice
- * @error: The #GError or %NULL on success
- *
- * Finish an ongoing open operation. If error is %NULL success is assumed.
- */
-void
-fpi_device_open_complete (FpDevice *device, GError *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_OPEN);
-
-  g_debug ("Device reported open completion");
-
-  clear_device_cancel_action (device);
-
-  if (!error)
-    {
-      priv->is_open = TRUE;
-      g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_OPEN]);
-    }
-
-  if (!error)
-    fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
-                                   GUINT_TO_POINTER (TRUE));
-  else
-    fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-}
-
-/**
- * fpi_device_close_complete:
- * @device: The #FpDevice
- * @error: The #GError or %NULL on success
- *
- * Finish an ongoing close operation. If error is %NULL success is assumed.
- */
-void
-fpi_device_close_complete (FpDevice *device, GError *error)
-{
-  GError *nested_error = NULL;
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_CLOSE);
-
-  g_debug ("Device reported close completion");
-
-  clear_device_cancel_action (device);
-  priv->is_open = FALSE;
-  g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_OPEN]);
-
-  switch (priv->type)
-    {
-    case FP_DEVICE_TYPE_USB:
-      if (!g_usb_device_close (priv->usb_device, &nested_error))
-        {
-          if (error == NULL)
-            error = nested_error;
-          fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-          return;
-        }
-      break;
-
-    case FP_DEVICE_TYPE_VIRTUAL:
-      break;
-
-    default:
-      g_assert_not_reached ();
-      fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR,
-                                     fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
-      return;
-    }
-
-  if (!error)
-    fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
-                                   GUINT_TO_POINTER (TRUE));
-  else
-    fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-}
-
-/**
- * fpi_device_enroll_complete:
- * @device: The #FpDevice
- * @print: (nullable) (transfer full): The #FpPrint or %NULL on failure
- * @error: The #GError or %NULL on success
- *
- * Finish an ongoing enroll operation. The #FpPrint can be stored by the
- * caller for later verification.
- */
-void
-fpi_device_enroll_complete (FpDevice *device, FpPrint *print, GError *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_ENROLL);
-
-  g_debug ("Device reported enroll completion");
-
-  clear_device_cancel_action (device);
-
-  if (!error)
-    {
-      if (FP_IS_PRINT (print))
-        {
-          fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_OBJECT, print);
-        }
-      else
-        {
-          g_warning ("Driver did not provide a valid print and failed to provide an error!");
-          error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
-                                            "Driver failed to provide print data!");
-          fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-        }
-    }
-  else
-    {
-      fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-      if (FP_IS_PRINT (print))
-        {
-          g_warning ("Driver passed an error but also provided a print, returning error!");
-          g_object_unref (print);
-        }
-    }
-}
-
-/**
- * fpi_device_verify_complete:
- * @device: The #FpDevice
- * @result: The #FpiMatchResult of the operation
- * @print: The scanned #FpPrint
- * @error: A #GError if result is %FPI_MATCH_ERROR
- *
- * Finish an ongoing verify operation. The returned print should be
- * representing the new scan and not the one passed for verification.
- */
-void
-fpi_device_verify_complete (FpDevice      *device,
-                            FpiMatchResult result,
-                            FpPrint       *print,
-                            GError        *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_VERIFY);
-
-  g_debug ("Device reported verify completion");
-
-  clear_device_cancel_action (device);
-
-  g_object_set_data_full (G_OBJECT (priv->current_task),
-                          "print",
-                          print,
-                          g_object_unref);
-
-  if (!error)
-    {
-      if (result != FPI_MATCH_ERROR)
-        {
-          fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_INT,
-                                         GINT_TO_POINTER (result));
-        }
-      else
-        {
-          g_warning ("Driver did not provide an error for a failed verify operation!");
-          error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
-                                            "Driver failed to provide an error!");
-          fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-        }
-    }
-  else
-    {
-      fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-      if (result != FPI_MATCH_ERROR)
-        {
-          g_warning ("Driver passed an error but also provided a match result, returning error!");
-          g_object_unref (print);
-        }
-    }
-}
-
-/**
- * fpi_device_identify_complete:
- * @device: The #FpDevice
- * @match: The matching #FpPrint from the passed gallery, or %NULL if none matched
- * @print: The scanned #FpPrint, may be %NULL
- * @error: The #GError or %NULL on success
- *
- * Finish an ongoing identify operation. The match that was identified is
- * returned in @match. The @print parameter returns the newly created scan
- * that was used for matching.
- */
-void
-fpi_device_identify_complete (FpDevice *device,
-                              FpPrint  *match,
-                              FpPrint  *print,
-                              GError   *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_IDENTIFY);
-
-  g_debug ("Device reported identify completion");
-
-  clear_device_cancel_action (device);
-
-  g_object_set_data_full (G_OBJECT (priv->current_task),
-                          "print",
-                          print,
-                          g_object_unref);
-  g_object_set_data_full (G_OBJECT (priv->current_task),
-                          "match",
-                          match,
-                          g_object_unref);
-  if (!error)
-    {
-      fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
-                                     GUINT_TO_POINTER (TRUE));
-    }
-  else
-    {
-      fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-      if (match)
-        {
-          g_warning ("Driver passed an error but also provided a match result, returning error!");
-          g_clear_object (&match);
-        }
-    }
-}
-
-
-/**
- * fpi_device_capture_complete:
- * @device: The #FpDevice
- * @image: The #FpImage, or %NULL on error
- * @error: The #GError or %NULL on success
- *
- * Finish an ongoing capture operation.
- */
-void
-fpi_device_capture_complete (FpDevice *device,
-                             FpImage  *image,
-                             GError   *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_CAPTURE);
-
-  g_debug ("Device reported capture completion");
-
-  clear_device_cancel_action (device);
-
-  if (!error)
-    {
-      if (image)
-        {
-          fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_OBJECT, image);
-        }
-      else
-        {
-          g_warning ("Driver did not provide an error for a failed capture operation!");
-          error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
-                                            "Driver failed to provide an error!");
-          fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-        }
-    }
-  else
-    {
-      fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-      if (image)
-        {
-          g_warning ("Driver passed an error but also provided an image, returning error!");
-          g_clear_object (&image);
-        }
-    }
-}
-
-/**
- * fpi_device_delete_complete:
- * @device: The #FpDevice
- * @error: The #GError or %NULL on success
- *
- * Finish an ongoing delete operation.
- */
-void
-fpi_device_delete_complete (FpDevice *device,
-                            GError   *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_DELETE);
-
-  g_debug ("Device reported deletion completion");
-
-  clear_device_cancel_action (device);
-
-  if (!error)
-    fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
-                                   GUINT_TO_POINTER (TRUE));
-  else
-    fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-}
-
-/**
- * fpi_device_list_complete:
- * @device: The #FpDevice
- * @prints: (element-type FpPrint) (transfer container): Possibly empty array of prints or %NULL on error
- * @error: The #GError or %NULL on success
- *
- * Finish an ongoing list operation.
- *
- * Please note that the @prints array will be free'ed using
- * g_ptr_array_unref() and the elements are destroyed automatically.
- * As such, you must use g_ptr_array_new_with_free_func() with
- * g_object_unref() as free func to create the array.
- */
-void
-fpi_device_list_complete (FpDevice  *device,
-                          GPtrArray *prints,
-                          GError    *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_LIST);
-
-  g_debug ("Device reported listing completion");
-
-  clear_device_cancel_action (device);
-
-  if (prints && error)
-    {
-      g_warning ("Driver reported back prints and error, ignoring prints");
-      g_clear_pointer (&prints, g_ptr_array_unref);
-    }
-  else if (!prints && !error)
-    {
-      g_warning ("Driver did not pass array but failed to provide an error");
-      error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
-                                        "Driver failed to provide a list of prints");
-    }
-
-  if (!error)
-    fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_PTR_ARRAY, prints);
-  else
-    fp_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
-}
-
-/**
- * fpi_device_enroll_progress:
- * @device: The #FpDevice
- * @completed_stages: The number of stages that are completed at this point
- * @print: The #FpPrint for the newly completed stage or %NULL on failure
- * @error: The #GError or %NULL on success
- *
- * Notify about the progress of the enroll operation. This is important for UI interaction.
- * The passed error may be used if a scan needs to be retried, use fpi_device_retry_new().
- */
-void
-fpi_device_enroll_progress (FpDevice *device,
-                            gint      completed_stages,
-                            FpPrint  *print,
-                            GError   *error)
-{
-  FpDevicePrivate *priv = fp_device_get_instance_private (device);
-  FpEnrollData *data;
-
-  g_return_if_fail (FP_IS_DEVICE (device));
-  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_ENROLL);
-  g_return_if_fail (error == NULL || error->domain == FP_DEVICE_RETRY);
-
-  g_debug ("Device reported enroll progress, reported %i of %i have been completed", completed_stages, priv->nr_enroll_stages);
-
-  if (error && print)
-    {
-      g_warning ("Driver passed an error and also provided a print, returning error!");
-      g_clear_object (&print);
-    }
-
-  data = g_task_get_task_data (priv->current_task);
-
-  if (data->enroll_progress_cb)
-    {
-      data->enroll_progress_cb (device,
-                                completed_stages,
-                                print,
-                                data->enroll_progress_data,
-                                error);
-    }
-
-  g_clear_error (&error);
-  g_clear_object (&print);
-}
-
-
 static void
 async_result_ready (GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
diff --git a/libfprint/fpi-device.c b/libfprint/fpi-device.c
new file mode 100644
index 0000000..3eee062
--- /dev/null
+++ b/libfprint/fpi-device.c
@@ -0,0 +1,1177 @@
+/*
+ * FpDevice - A fingerprint reader device - Private APIs
+ * Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
+ * Copyright (C) 2019 Marco Trevisan <marco.trevisan@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define FP_COMPONENT "device"
+#include "fpi-log.h"
+
+#include "fp-device-private.h"
+
+/**
+ * SECTION: fpi-device
+ * @title: Internal FpDevice
+ * @short_description: Internal device routines
+ *
+ * The methods that are availabe for drivers to manipulate a device. See
+ * #FpDeviceClass for more information. Also note that most of these are
+ * not relevant for image based devices, see #FpImageDeviceClass in that
+ * case.
+ *
+ * Also see the public #FpDevice routines.
+ */
+
+/* Manually redefine what G_DEFINE_* macro does */
+static inline gpointer
+fp_device_get_instance_private (FpDevice *self)
+{
+  FpDeviceClass *dev_class = g_type_class_peek_static (FP_TYPE_DEVICE);
+
+  return G_STRUCT_MEMBER_P (self,
+                            g_type_class_get_instance_private_offset (dev_class));
+}
+
+/**
+ * fpi_device_retry_new:
+ * @error: The #FpDeviceRetry error value describing the issue
+ *
+ * Create a new retry error code for use with fpi_device_verify_complete()
+ * and similar calls.
+ */
+GError *
+fpi_device_retry_new (FpDeviceRetry error)
+{
+  const gchar *msg;
+
+  switch (error)
+    {
+    case FP_DEVICE_RETRY_GENERAL:
+      msg = "Please try again.";
+      break;
+
+    case FP_DEVICE_RETRY_TOO_SHORT:
+      msg = "The swipe was too short, please try again.";
+      break;
+
+    case FP_DEVICE_RETRY_CENTER_FINGER:
+      msg = "The finger was not centered properly, please try again.";
+      break;
+
+    case FP_DEVICE_RETRY_REMOVE_FINGER:
+      msg = "Please try again after removing the finger first.";
+      break;
+
+    default:
+      g_warning ("Unsupported error, returning general error instead!");
+      error = FP_DEVICE_RETRY_GENERAL;
+      msg = "Please try again.";
+    }
+
+  return g_error_new_literal (FP_DEVICE_RETRY, error, msg);
+}
+
+/**
+ * fpi_device_error_new:
+ * @error: The #FpDeviceRetry error value describing the issue
+ *
+ * Create a new error code for use with fpi_device_verify_complete() and
+ * similar calls.
+ */
+GError *
+fpi_device_error_new (FpDeviceError error)
+{
+  const gchar *msg;
+
+  switch (error)
+    {
+    case FP_DEVICE_ERROR_GENERAL:
+      msg = "An unspecified error occured!";
+      break;
+
+    case FP_DEVICE_ERROR_NOT_SUPPORTED:
+      msg = "The operation is not supported on this device!";
+      break;
+
+    case FP_DEVICE_ERROR_NOT_OPEN:
+      msg = "The device needs to be opened first!";
+      break;
+
+    case FP_DEVICE_ERROR_ALREADY_OPEN:
+      msg = "The device has already been opened!";
+      break;
+
+    case FP_DEVICE_ERROR_BUSY:
+      msg = "The device is still busy with another operation, please try again later.";
+      break;
+
+    case FP_DEVICE_ERROR_PROTO:
+      msg = "The driver encountered a protocol error with the device.";
+      break;
+
+    case FP_DEVICE_ERROR_DATA_INVALID:
+      msg = "Passed (print) data is not valid.";
+      break;
+
+    case FP_DEVICE_ERROR_DATA_FULL:
+      msg = "On device storage space is full.";
+      break;
+
+    case FP_DEVICE_ERROR_DATA_NOT_FOUND:
+      msg = "Print was not found on the devices storage.";
+      break;
+
+    default:
+      g_warning ("Unsupported error, returning general error instead!");
+      error = FP_DEVICE_ERROR_GENERAL;
+      msg = "An unspecified error occured!";
+    }
+
+  return g_error_new_literal (FP_DEVICE_ERROR, error, msg);
+}
+
+/**
+ * fpi_device_retry_new_msg:
+ * @error: The #FpDeviceRetry error value describing the issue
+ * @msg: Custom message to use with printf-style formatting
+ * @...: args for @msg
+ *
+ * Create a new retry error code for use with fpi_device_verify_complete()
+ * and similar calls.
+ */
+GError *
+fpi_device_retry_new_msg (FpDeviceRetry device_error,
+                          const gchar  *msg,
+                          ...)
+{
+  GError *error;
+  va_list args;
+
+  va_start (args, msg);
+  error = g_error_new_valist (FP_DEVICE_RETRY, device_error, msg, args);
+  va_end (args);
+
+  return error;
+}
+
+/**
+ * fpi_device_error_new_msg:
+ * @error: The #FpDeviceRetry error value describing the issue
+ * @msg: Custom message to use with printf-style formatting
+ * @...: args for @msg
+ *
+ * Create a new error code for use with fpi_device_verify_complete()
+ * and similar calls.
+ */
+GError *
+fpi_device_error_new_msg (FpDeviceError device_error,
+                          const gchar  *msg,
+                          ...)
+{
+  GError *error;
+  va_list args;
+
+  va_start (args, msg);
+  error = g_error_new_valist (FP_DEVICE_ERROR, device_error, msg, args);
+  va_end (args);
+
+  return error;
+}
+
+/**
+ * fpi_device_set_nr_enroll_stages:
+ * @device: The #FpDevice
+ * @enroll_stages: The number of enroll stages
+ *
+ * Updates the reported number of enroll stages that the device needs.
+ * If all supported devices have the same number of stages, then the
+ * value can simply be set in the class.
+ */
+void
+fpi_device_set_nr_enroll_stages (FpDevice *device,
+                                 gint      enroll_stages)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+
+  priv->nr_enroll_stages = enroll_stages;
+  g_object_notify (G_OBJECT (device), "nr-enroll-stages");
+}
+
+/**
+ * fpi_device_set_scan_type:
+ * @device: The #FpDevice
+ * @scan_type: The scan type of the device
+ *
+ * Updates the the scan type of the device from the default.
+ * If all supported devices have the same scan type, then the
+ * value can simply be set in the class.
+ */
+void
+fpi_device_set_scan_type (FpDevice  *device,
+                          FpScanType scan_type)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+
+  priv->scan_type = scan_type;
+  g_object_notify (G_OBJECT (device), "scan-type");
+}
+
+typedef struct
+{
+  GSource   source;
+  FpDevice *device;
+} FpDeviceTimeoutSource;
+
+static void
+timeout_finalize (GSource *source)
+{
+  FpDeviceTimeoutSource *timeout_source = (FpDeviceTimeoutSource *) source;
+  FpDevicePrivate *priv;
+
+  priv = fp_device_get_instance_private (timeout_source->device);
+  priv->sources = g_slist_remove (priv->sources, source);
+}
+
+static gboolean
+timeout_dispatch (GSource *source, GSourceFunc gsource_func, gpointer user_data)
+{
+  FpDeviceTimeoutSource *timeout_source = (FpDeviceTimeoutSource *) source;
+  FpTimeoutFunc callback = (FpTimeoutFunc) gsource_func;
+
+  callback (timeout_source->device, user_data);
+
+  return G_SOURCE_REMOVE;
+}
+
+static GSourceFuncs timeout_funcs = {
+  NULL, /* prepare */
+  NULL, /* check */
+  timeout_dispatch,
+  timeout_finalize,
+  NULL, NULL
+};
+
+/**
+ * fpi_device_add_timeout:
+ * @device: The #FpDevice
+ * @interval: The interval in milliseconds
+ * @func: The #FpTimeoutFunc to call on timeout
+ * @user_data: (nullable): User data to pass to the callback
+ * @destroy_notify: (nullable): #GDestroyNotify for @user_data
+ *
+ * Register a timeout to run. Drivers should always make sure that timers are
+ * cancelled when appropriate.
+ *
+ * Returns: (transfer none): A newly created and attached #GSource
+ */
+GSource *
+fpi_device_add_timeout (FpDevice      *device,
+                        gint           interval,
+                        FpTimeoutFunc  func,
+                        gpointer       user_data,
+                        GDestroyNotify destroy_notify)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+  FpDeviceTimeoutSource *source;
+
+  source = (FpDeviceTimeoutSource *) g_source_new (&timeout_funcs,
+                                                   sizeof (FpDeviceTimeoutSource));
+  source->device = device;
+
+  g_source_attach (&source->source, NULL);
+  g_source_set_callback (&source->source, (GSourceFunc) func, user_data, destroy_notify);
+  g_source_set_ready_time (&source->source,
+                           g_source_get_time (&source->source) + interval * (guint64) 1000);
+  priv->sources = g_slist_prepend (priv->sources, source);
+  g_source_unref (&source->source);
+
+  return &source->source;
+}
+
+/**
+ * fpi_device_get_usb_device:
+ * @device: The #FpDevice
+ *
+ * Get the #GUsbDevice for this #FpDevice. Only permissible to call if the
+ * #FpDevice is of type %FP_DEVICE_TYPE_USB.
+ *
+ * Returns: The #GUsbDevice
+ */
+GUsbDevice *
+fpi_device_get_usb_device (FpDevice *device)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_USB, NULL);
+
+  return priv->usb_device;
+}
+
+/**
+ * fpi_device_get_virtual_env:
+ * @device: The #FpDevice
+ *
+ * Get the value of the environment variable that caused the virtual #FpDevice to be
+ * generated. Only permissible to call if the #FpDevice is of type %FP_DEVICE_TYPE_VIRTUAL.
+ *
+ * Returns: The value of the environment variable
+ */
+const gchar *
+fpi_device_get_virtual_env (FpDevice *device)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_VIRTUAL, NULL);
+
+  return priv->virtual_env;
+}
+
+/**
+ * fpi_device_get_current_action:
+ * @device: The #FpDevice
+ *
+ * Get the currently ongoing action or %FP_DEVICE_ACTION_NONE if there
+ * is no operation at this time.
+ *
+ * This is useful for drivers that might share code paths between different
+ * actions (e.g. verify and identify) and want to find out again later which
+ * action was started in the beginning.
+ *
+ * Returns: The ongoing #FpDeviceAction
+ */
+FpDeviceAction
+fpi_device_get_current_action (FpDevice *device)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_val_if_fail (FP_IS_DEVICE (device), FP_DEVICE_ACTION_NONE);
+
+  return priv->current_action;
+}
+
+/**
+ * fpi_device_action_is_cancelled:
+ * @device: The #FpDevice
+ *
+ * Checks whether the current action has been cancelled by the user.
+ * This is equivalent to first getting the cancellable using
+ * fpi_device_get_cancellable() and then checking whether it has been
+ * cancelled (if it is non-NULL).
+ *
+ * Returns: %TRUE if action should be cancelled
+ */
+gboolean
+fpi_device_action_is_cancelled (FpDevice *device)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+  GCancellable *cancellable;
+
+  g_return_val_if_fail (FP_IS_DEVICE (device), TRUE);
+  g_return_val_if_fail (priv->current_action != FP_DEVICE_ACTION_NONE, TRUE);
+
+  cancellable = g_task_get_cancellable (priv->current_task);
+
+  return cancellable ? g_cancellable_is_cancelled (cancellable) : FALSE;
+}
+
+/**
+ * fpi_device_get_driver_data:
+ * @device: The #FpDevice
+ *
+ * Returns: The driver data from the #FpIdEntry table entry
+ */
+guint64
+fpi_device_get_driver_data (FpDevice *device)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_val_if_fail (FP_IS_DEVICE (device), 0);
+
+  return priv->driver_data;
+}
+
+void
+enroll_data_free (FpEnrollData *data)
+{
+  if (data->enroll_progress_destroy)
+    data->enroll_progress_destroy (data->enroll_progress_data);
+  data->enroll_progress_data = NULL;
+  g_clear_object (&data->print);
+  g_free (data);
+}
+
+/**
+ * fpi_device_get_enroll_data:
+ * @device: The #FpDevice
+ * @print: (out) (transfer none): The user provided template print
+ *
+ * Get data for enrollment.
+ */
+void
+fpi_device_get_enroll_data (FpDevice *device,
+                            FpPrint **print)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+  FpEnrollData *data;
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_ENROLL);
+
+  data = g_task_get_task_data (priv->current_task);
+  g_assert (data);
+
+  if (print)
+    *print = data->print;
+}
+
+/**
+ * fpi_device_get_capture_data:
+ * @device: The #FpDevice
+ * @wait_for_finger: (out): Whether to wait for finger or not
+ *
+ * Get data for capture.
+ */
+void
+fpi_device_get_capture_data (FpDevice *device,
+                             gboolean *wait_for_finger)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_CAPTURE);
+
+  if (wait_for_finger)
+    *wait_for_finger = priv->wait_for_finger;
+}
+
+/**
+ * fpi_device_get_verify_data:
+ * @device: The #FpDevice
+ * @print: (out) (transfer none): The enrolled print
+ *
+ * Get data for verify.
+ */
+void
+fpi_device_get_verify_data (FpDevice *device,
+                            FpPrint **print)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_VERIFY);
+
+  if (print)
+    *print = g_task_get_task_data (priv->current_task);
+}
+
+/**
+ * fpi_device_get_identify_data:
+ * @device: The #FpDevice
+ * @prints: (out) (transfer none) (element-type FpPrint): The gallery of prints
+ *
+ * Get data for identify.
+ */
+void
+fpi_device_get_identify_data (FpDevice   *device,
+                              GPtrArray **prints)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_IDENTIFY);
+
+  if (prints)
+    *prints = g_task_get_task_data (priv->current_task);
+}
+
+/**
+ * fpi_device_get_delete_data:
+ * @device: The #FpDevice
+ * @print: (out) (transfer none): The print to delete
+ *
+ * Get data for delete.
+ */
+void
+fpi_device_get_delete_data (FpDevice *device,
+                            FpPrint **print)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_DELETE);
+
+  if (print)
+    *print = g_task_get_task_data (priv->current_task);
+}
+
+/**
+ * fpi_device_get_cancellable:
+ * @device: The #FpDevice
+ *
+ * Retrieve the #GCancellable that may cancel the currently ongoing operation. This
+ * is primarily useful to pass directly to e.g. fpi_usb_transfer_submit() for cancellable
+ * transfers.
+ * In many cases the cancel vfunc may be more convenient to react to cancellation in some
+ * way.
+ *
+ * Returns: (transfer none): The #GCancellable for the current action.
+ */
+GCancellable *
+fpi_device_get_cancellable (FpDevice *device)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_val_if_fail (FP_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (priv->current_action != FP_DEVICE_ACTION_NONE, NULL);
+
+  return g_task_get_cancellable (priv->current_task);
+}
+
+/**
+ * fpi_device_action_error:
+ * @device: The #FpDevice
+ * @error: The #GError to return
+ *
+ * Finish an ongoing action with an error. This is the same as calling
+ * the corresponding complete function such as fpi_device_open_complete()
+ * with an error set. If possible, use the correct complete function as
+ * that results in improved error detection.
+ */
+void
+fpi_device_action_error (FpDevice *device,
+                         GError   *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action != FP_DEVICE_ACTION_NONE);
+
+  if (error != NULL)
+    {
+      g_debug ("Device reported generic error during action; action was: %i", priv->current_action);
+    }
+  else
+    {
+      g_warning ("Device failed to pass an error to generic action error function");
+      error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Device reported error but did not provide an error condition");
+    }
+
+
+  switch (priv->current_action)
+    {
+    case FP_DEVICE_ACTION_PROBE:
+      fpi_device_probe_complete (device, NULL, NULL, error);
+      break;
+
+    case FP_DEVICE_ACTION_OPEN:
+      fpi_device_open_complete (device, error);
+      break;
+
+    case FP_DEVICE_ACTION_CLOSE:
+      fpi_device_close_complete (device, error);
+      break;
+
+    case FP_DEVICE_ACTION_ENROLL:
+      fpi_device_enroll_complete (device, NULL, error);
+      break;
+
+    case FP_DEVICE_ACTION_VERIFY:
+      fpi_device_verify_complete (device, FPI_MATCH_ERROR, NULL, error);
+      break;
+
+    case FP_DEVICE_ACTION_IDENTIFY:
+      fpi_device_identify_complete (device, NULL, NULL, error);
+      break;
+
+    case FP_DEVICE_ACTION_CAPTURE:
+      fpi_device_capture_complete (device, NULL, error);
+      break;
+
+    case FP_DEVICE_ACTION_DELETE:
+      fpi_device_delete_complete (device, error);
+      break;
+
+    case FP_DEVICE_ACTION_LIST:
+      fpi_device_list_complete (device, NULL, error);
+      break;
+
+    default:
+    case FP_DEVICE_ACTION_NONE:
+      g_return_if_reached ();
+      break;
+    }
+}
+
+static void
+clear_device_cancel_action (FpDevice *device)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_clear_pointer (&priv->current_idle_cancel_source, g_source_destroy);
+
+  if (priv->current_cancellable_id)
+    {
+      g_cancellable_disconnect (g_task_get_cancellable (priv->current_task),
+                                priv->current_cancellable_id);
+      priv->current_cancellable_id = 0;
+    }
+}
+
+typedef enum _FpDeviceTaskReturnType {
+  FP_DEVICE_TASK_RETURN_INT,
+  FP_DEVICE_TASK_RETURN_BOOL,
+  FP_DEVICE_TASK_RETURN_OBJECT,
+  FP_DEVICE_TASK_RETURN_PTR_ARRAY,
+  FP_DEVICE_TASK_RETURN_ERROR,
+} FpDeviceTaskReturnType;
+
+typedef struct _FpDeviceTaskReturnData
+{
+  FpDevice              *device;
+  FpDeviceTaskReturnType type;
+  gpointer               result;
+} FpDeviceTaskReturnData;
+
+static gboolean
+fp_device_task_return_in_idle_cb (gpointer user_data)
+{
+  FpDeviceTaskReturnData *data = user_data;
+  FpDevicePrivate *priv = fp_device_get_instance_private (data->device);
+
+  g_autoptr(GTask) task = NULL;
+
+  g_debug ("Completing action %d in idle!", priv->current_action);
+
+  task = g_steal_pointer (&priv->current_task);
+  priv->current_action = FP_DEVICE_ACTION_NONE;
+  priv->current_task_idle_return_source = NULL;
+
+  switch (data->type)
+    {
+    case FP_DEVICE_TASK_RETURN_INT:
+      g_task_return_int (task, GPOINTER_TO_INT (data->result));
+      break;
+
+    case FP_DEVICE_TASK_RETURN_BOOL:
+      g_task_return_boolean (task, GPOINTER_TO_UINT (data->result));
+      break;
+
+    case FP_DEVICE_TASK_RETURN_OBJECT:
+      g_task_return_pointer (task, data->result, g_object_unref);
+      break;
+
+    case FP_DEVICE_TASK_RETURN_PTR_ARRAY:
+      g_task_return_pointer (task, data->result,
+                             (GDestroyNotify) g_ptr_array_unref);
+      break;
+
+    case FP_DEVICE_TASK_RETURN_ERROR:
+      g_task_return_error (task, data->result);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+fpi_device_task_return_data_free (FpDeviceTaskReturnData *data)
+{
+  g_object_unref (data->device);
+  g_free (data);
+}
+
+static void
+fpi_device_return_task_in_idle (FpDevice              *device,
+                                FpDeviceTaskReturnType return_type,
+                                gpointer               return_data)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+  FpDeviceTaskReturnData *data;
+
+  data = g_new0 (FpDeviceTaskReturnData, 1);
+  data->device = g_object_ref (device);
+  data->type = return_type;
+  data->result = return_data;
+
+  priv->current_task_idle_return_source = g_idle_source_new ();
+  g_source_set_priority (priv->current_task_idle_return_source,
+                         g_task_get_priority (priv->current_task));
+  g_source_set_callback (priv->current_task_idle_return_source,
+                         fp_device_task_return_in_idle_cb,
+                         data,
+                         (GDestroyNotify) fpi_device_task_return_data_free);
+
+  g_source_attach (priv->current_task_idle_return_source, NULL);
+  g_source_unref (priv->current_task_idle_return_source);
+}
+
+/**
+ * fpi_device_probe_complete:
+ * @device: The #FpDevice
+ * @device_id: Unique ID for the device or %NULL
+ * @device_name: Human readable name or %NULL for driver name
+ * @error: The #GError or %NULL on success
+ *
+ * Finish an ongoing probe operation. If error is %NULL success is assumed.
+ */
+void
+fpi_device_probe_complete (FpDevice    *device,
+                           const gchar *device_id,
+                           const gchar *device_name,
+                           GError      *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_PROBE);
+
+  g_debug ("Device reported probe completion");
+
+  clear_device_cancel_action (device);
+
+  if (!error)
+    {
+      if (device_id)
+        {
+          g_clear_pointer (&priv->device_id, g_free);
+          priv->device_id = g_strdup (device_id);
+          g_object_notify (G_OBJECT (device), "device-id");
+        }
+      if (device_name)
+        {
+          g_clear_pointer (&priv->device_name, g_free);
+          priv->device_name = g_strdup (device_name);
+          g_object_notify (G_OBJECT (device), "name");
+        }
+      fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
+                                      GUINT_TO_POINTER (TRUE));
+    }
+  else
+    {
+      fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+    }
+}
+
+/**
+ * fpi_device_open_complete:
+ * @device: The #FpDevice
+ * @error: The #GError or %NULL on success
+ *
+ * Finish an ongoing open operation. If error is %NULL success is assumed.
+ */
+void
+fpi_device_open_complete (FpDevice *device, GError *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_OPEN);
+
+  g_debug ("Device reported open completion");
+
+  clear_device_cancel_action (device);
+
+  if (!error)
+    {
+      priv->is_open = TRUE;
+      g_object_notify (G_OBJECT (device), "open");
+    }
+
+  if (!error)
+    fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
+                                    GUINT_TO_POINTER (TRUE));
+  else
+    fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+}
+
+/**
+ * fpi_device_close_complete:
+ * @device: The #FpDevice
+ * @error: The #GError or %NULL on success
+ *
+ * Finish an ongoing close operation. If error is %NULL success is assumed.
+ */
+void
+fpi_device_close_complete (FpDevice *device, GError *error)
+{
+  GError *nested_error = NULL;
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_CLOSE);
+
+  g_debug ("Device reported close completion");
+
+  clear_device_cancel_action (device);
+  priv->is_open = FALSE;
+  g_object_notify (G_OBJECT (device), "open");
+
+  switch (priv->type)
+    {
+    case FP_DEVICE_TYPE_USB:
+      if (!g_usb_device_close (priv->usb_device, &nested_error))
+        {
+          if (error == NULL)
+            error = nested_error;
+          fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+          return;
+        }
+      break;
+
+    case FP_DEVICE_TYPE_VIRTUAL:
+      break;
+
+    default:
+      g_assert_not_reached ();
+      fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR,
+                                      fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
+      return;
+    }
+
+  if (!error)
+    fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
+                                    GUINT_TO_POINTER (TRUE));
+  else
+    fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+}
+
+/**
+ * fpi_device_enroll_complete:
+ * @device: The #FpDevice
+ * @print: (nullable) (transfer full): The #FpPrint or %NULL on failure
+ * @error: The #GError or %NULL on success
+ *
+ * Finish an ongoing enroll operation. The #FpPrint can be stored by the
+ * caller for later verification.
+ */
+void
+fpi_device_enroll_complete (FpDevice *device, FpPrint *print, GError *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_ENROLL);
+
+  g_debug ("Device reported enroll completion");
+
+  clear_device_cancel_action (device);
+
+  if (!error)
+    {
+      if (FP_IS_PRINT (print))
+        {
+          fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_OBJECT, print);
+        }
+      else
+        {
+          g_warning ("Driver did not provide a valid print and failed to provide an error!");
+          error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+                                            "Driver failed to provide print data!");
+          fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+        }
+    }
+  else
+    {
+      fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+      if (FP_IS_PRINT (print))
+        {
+          g_warning ("Driver passed an error but also provided a print, returning error!");
+          g_object_unref (print);
+        }
+    }
+}
+
+/**
+ * fpi_device_verify_complete:
+ * @device: The #FpDevice
+ * @result: The #FpiMatchResult of the operation
+ * @print: The scanned #FpPrint
+ * @error: A #GError if result is %FPI_MATCH_ERROR
+ *
+ * Finish an ongoing verify operation. The returned print should be
+ * representing the new scan and not the one passed for verification.
+ */
+void
+fpi_device_verify_complete (FpDevice      *device,
+                            FpiMatchResult result,
+                            FpPrint       *print,
+                            GError        *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_VERIFY);
+
+  g_debug ("Device reported verify completion");
+
+  clear_device_cancel_action (device);
+
+  g_object_set_data_full (G_OBJECT (priv->current_task),
+                          "print",
+                          print,
+                          g_object_unref);
+
+  if (!error)
+    {
+      if (result != FPI_MATCH_ERROR)
+        {
+          fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_INT,
+                                          GINT_TO_POINTER (result));
+        }
+      else
+        {
+          g_warning ("Driver did not provide an error for a failed verify operation!");
+          error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+                                            "Driver failed to provide an error!");
+          fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+        }
+    }
+  else
+    {
+      fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+      if (result != FPI_MATCH_ERROR)
+        {
+          g_warning ("Driver passed an error but also provided a match result, returning error!");
+          g_object_unref (print);
+        }
+    }
+}
+
+/**
+ * fpi_device_identify_complete:
+ * @device: The #FpDevice
+ * @match: The matching #FpPrint from the passed gallery, or %NULL if none matched
+ * @print: The scanned #FpPrint, may be %NULL
+ * @error: The #GError or %NULL on success
+ *
+ * Finish an ongoing identify operation. The match that was identified is
+ * returned in @match. The @print parameter returns the newly created scan
+ * that was used for matching.
+ */
+void
+fpi_device_identify_complete (FpDevice *device,
+                              FpPrint  *match,
+                              FpPrint  *print,
+                              GError   *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_IDENTIFY);
+
+  g_debug ("Device reported identify completion");
+
+  clear_device_cancel_action (device);
+
+  g_object_set_data_full (G_OBJECT (priv->current_task),
+                          "print",
+                          print,
+                          g_object_unref);
+  g_object_set_data_full (G_OBJECT (priv->current_task),
+                          "match",
+                          match,
+                          g_object_unref);
+  if (!error)
+    {
+      fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
+                                      GUINT_TO_POINTER (TRUE));
+    }
+  else
+    {
+      fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+      if (match)
+        {
+          g_warning ("Driver passed an error but also provided a match result, returning error!");
+          g_clear_object (&match);
+        }
+    }
+}
+
+
+/**
+ * fpi_device_capture_complete:
+ * @device: The #FpDevice
+ * @image: The #FpImage, or %NULL on error
+ * @error: The #GError or %NULL on success
+ *
+ * Finish an ongoing capture operation.
+ */
+void
+fpi_device_capture_complete (FpDevice *device,
+                             FpImage  *image,
+                             GError   *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_CAPTURE);
+
+  g_debug ("Device reported capture completion");
+
+  clear_device_cancel_action (device);
+
+  if (!error)
+    {
+      if (image)
+        {
+          fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_OBJECT, image);
+        }
+      else
+        {
+          g_warning ("Driver did not provide an error for a failed capture operation!");
+          error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+                                            "Driver failed to provide an error!");
+          fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+        }
+    }
+  else
+    {
+      fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+      if (image)
+        {
+          g_warning ("Driver passed an error but also provided an image, returning error!");
+          g_clear_object (&image);
+        }
+    }
+}
+
+/**
+ * fpi_device_delete_complete:
+ * @device: The #FpDevice
+ * @error: The #GError or %NULL on success
+ *
+ * Finish an ongoing delete operation.
+ */
+void
+fpi_device_delete_complete (FpDevice *device,
+                            GError   *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_DELETE);
+
+  g_debug ("Device reported deletion completion");
+
+  clear_device_cancel_action (device);
+
+  if (!error)
+    fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_BOOL,
+                                    GUINT_TO_POINTER (TRUE));
+  else
+    fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+}
+
+/**
+ * fpi_device_list_complete:
+ * @device: The #FpDevice
+ * @prints: (element-type FpPrint) (transfer container): Possibly empty array of prints or %NULL on error
+ * @error: The #GError or %NULL on success
+ *
+ * Finish an ongoing list operation.
+ *
+ * Please note that the @prints array will be free'ed using
+ * g_ptr_array_unref() and the elements are destroyed automatically.
+ * As such, you must use g_ptr_array_new_with_free_func() with
+ * g_object_unref() as free func to create the array.
+ */
+void
+fpi_device_list_complete (FpDevice  *device,
+                          GPtrArray *prints,
+                          GError    *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_LIST);
+
+  g_debug ("Device reported listing completion");
+
+  clear_device_cancel_action (device);
+
+  if (prints && error)
+    {
+      g_warning ("Driver reported back prints and error, ignoring prints");
+      g_clear_pointer (&prints, g_ptr_array_unref);
+    }
+  else if (!prints && !error)
+    {
+      g_warning ("Driver did not pass array but failed to provide an error");
+      error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+                                        "Driver failed to provide a list of prints");
+    }
+
+  if (!error)
+    fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_PTR_ARRAY, prints);
+  else
+    fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error);
+}
+
+/**
+ * fpi_device_enroll_progress:
+ * @device: The #FpDevice
+ * @completed_stages: The number of stages that are completed at this point
+ * @print: The #FpPrint for the newly completed stage or %NULL on failure
+ * @error: The #GError or %NULL on success
+ *
+ * Notify about the progress of the enroll operation. This is important for UI interaction.
+ * The passed error may be used if a scan needs to be retried, use fpi_device_retry_new().
+ */
+void
+fpi_device_enroll_progress (FpDevice *device,
+                            gint      completed_stages,
+                            FpPrint  *print,
+                            GError   *error)
+{
+  FpDevicePrivate *priv = fp_device_get_instance_private (device);
+  FpEnrollData *data;
+
+  g_return_if_fail (FP_IS_DEVICE (device));
+  g_return_if_fail (priv->current_action == FP_DEVICE_ACTION_ENROLL);
+  g_return_if_fail (error == NULL || error->domain == FP_DEVICE_RETRY);
+
+  g_debug ("Device reported enroll progress, reported %i of %i have been completed", completed_stages, priv->nr_enroll_stages);
+
+  if (error && print)
+    {
+      g_warning ("Driver passed an error and also provided a print, returning error!");
+      g_clear_object (&print);
+    }
+
+  data = g_task_get_task_data (priv->current_task);
+
+  if (data->enroll_progress_cb)
+    {
+      data->enroll_progress_cb (device,
+                                completed_stages,
+                                print,
+                                data->enroll_progress_data,
+                                error);
+    }
+
+  g_clear_error (&error);
+  g_clear_object (&print);
+}
diff --git a/libfprint/meson.build b/libfprint/meson.build
index 1e98e2d..4a34cbd 100644
--- a/libfprint/meson.build
+++ b/libfprint/meson.build
@@ -8,6 +8,7 @@ libfprint_sources = [
 
 libfprint_private_sources = [
     'fpi-assembling.c',
+    'fpi-device.c',
     'fpi-ssm.c',
     'fpi-usb-transfer.c',
     'fpi-byte-reader.c',
-- 
2.24.1