libfprint/libfprint/fp-context.c
Marco Trevisan (Treviño) 5dc3edf07c fp-context: Run dispose on the usb context to deal with circular refs
Ensure that we dispose the USB context before unreffing it, so that it will
release any reference it has and destroy the internal libusb context.
2019-11-27 21:40:44 +01:00

367 lines
11 KiB
C

/*
* FpContext - A FPrint context
* Copyright (C) 2019 Benjamin Berg <bberg@redhat.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 "context"
#include <fpi-log.h>
#include "fpi-context.h"
#include "fpi-device.h"
#include <gusb.h>
/**
* SECTION: fp-context
* @title: FpContext
* @short_description: Discover fingerprint devices
*
* The #FpContext allows you to discover fingerprint scanning hardware. This
* is the starting point when integrating libfprint into your software.
*
* The <link linkend="device-added">device-added</link> and device-removed signals allow you to handle devices
* that may be hotplugged at runtime.
*/
typedef struct
{
GUsbContext *usb_ctx;
GCancellable *cancellable;
gint pending_devices;
gboolean enumerated;
GArray *drivers;
GPtrArray *devices;
} FpContextPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (FpContext, fp_context, G_TYPE_OBJECT)
enum {
DEVICE_ADDED_SIGNAL,
DEVICE_REMOVED_SIGNAL,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
async_device_init_done_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
g_autoptr(GError) error = NULL;
FpDevice *device;
FpContext *context;
FpContextPrivate *priv;
device = (FpDevice *) g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, &error);
if (!device)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
context = FP_CONTEXT (user_data);
priv = fp_context_get_instance_private (context);
priv->pending_devices--;
g_message ("Ignoring device due to initialization error: %s", error->message);
return;
}
context = FP_CONTEXT (user_data);
priv = fp_context_get_instance_private (context);
priv->pending_devices--;
g_ptr_array_add (priv->devices, device);
g_signal_emit (context, signals[DEVICE_ADDED_SIGNAL], 0, device);
}
static void
usb_device_added_cb (FpContext *self, GUsbDevice *device, GUsbContext *usb_ctx)
{
FpContextPrivate *priv = fp_context_get_instance_private (self);
GType found_driver = G_TYPE_NONE;
const FpIdEntry *found_entry = NULL;
gint found_score = 0;
gint i;
guint16 pid, vid;
pid = g_usb_device_get_pid (device);
vid = g_usb_device_get_vid (device);
/* Find the best driver to handle this USB device. */
for (i = 0; i < priv->drivers->len; i++)
{
GType driver = g_array_index (priv->drivers, GType, i);
FpDeviceClass *cls = FP_DEVICE_CLASS (g_type_class_ref (driver));
const FpIdEntry *entry;
if (cls->type != FP_DEVICE_TYPE_USB)
{
g_type_class_unref (cls);
continue;
}
for (entry = cls->id_table; entry->pid; entry++)
{
gint driver_score = 50;
if (entry->pid != pid || entry->vid != vid)
continue;
if (cls->usb_discover)
driver_score = cls->usb_discover (device);
/* Is this driver better than the one we had? */
if (driver_score <= found_score)
continue;
found_score = driver_score;
found_driver = driver;
found_entry = entry;
}
g_type_class_unref (cls);
}
if (found_driver == G_TYPE_NONE)
{
g_debug ("No driver found for USB device %04X:%04X", pid, vid);
return;
}
priv->pending_devices++;
g_async_initable_new_async (found_driver,
G_PRIORITY_LOW,
priv->cancellable,
async_device_init_done_cb,
self,
"fp-usb-device", device,
"fp-driver-data", found_entry->driver_data,
NULL);
}
static void
usb_device_removed_cb (FpContext *self, GUsbDevice *device, GUsbContext *usb_ctx)
{
FpContextPrivate *priv = fp_context_get_instance_private (self);
gint i;
/* Do the lazy way and just look at each device. */
for (i = 0; i < priv->devices->len; i++)
{
FpDevice *dev = g_ptr_array_index (priv->devices, i);
FpDeviceClass *cls = FP_DEVICE_GET_CLASS (dev);
if (cls->type != FP_DEVICE_TYPE_USB)
continue;
if (fpi_device_get_usb_device (dev) == device)
{
g_signal_emit (self, signals[DEVICE_REMOVED_SIGNAL], 0, dev);
g_ptr_array_remove_index_fast (priv->devices, i);
return;
}
}
}
static void
fp_context_finalize (GObject *object)
{
FpContext *self = (FpContext *) object;
FpContextPrivate *priv = fp_context_get_instance_private (self);
g_clear_pointer (&priv->devices, g_ptr_array_unref);
g_cancellable_cancel (priv->cancellable);
g_clear_object (&priv->cancellable);
g_clear_pointer (&priv->drivers, g_array_unref);
g_object_run_dispose (G_OBJECT (priv->usb_ctx));
g_clear_object (&priv->usb_ctx);
G_OBJECT_CLASS (fp_context_parent_class)->finalize (object);
}
static void
fp_context_class_init (FpContextClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fp_context_finalize;
/**
* FpContext::device-added:
* @context: the #FpContext instance that emitted the signal
* @device: A #FpDevice
*
* This signal is emitted when a fingerprint reader is added.
**/
signals[DEVICE_ADDED_SIGNAL] = g_signal_new ("device-added",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (FpContextClass, device_added),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
FP_TYPE_DEVICE);
/**
* FpContext::device-removed:
* @context: the #FpContext instance that emitted the signal
* @device: A #FpDevice
*
* This signal is emitted when a fingerprint reader is removed.
**/
signals[DEVICE_REMOVED_SIGNAL] = g_signal_new ("device-removed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (FpContextClass, device_removed),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
FP_TYPE_DEVICE);
}
static void
fp_context_init (FpContext *self)
{
g_autoptr(GError) error = NULL;
FpContextPrivate *priv = fp_context_get_instance_private (self);
priv->drivers = g_array_new (TRUE, FALSE, sizeof (GType));
fpi_get_driver_types (priv->drivers);
priv->devices = g_ptr_array_new_with_free_func (g_object_unref);
priv->cancellable = g_cancellable_new ();
priv->usb_ctx = g_usb_context_new (&error);
if (!priv->usb_ctx)
{
fp_warn ("Could not initialise USB Subsystem: %s", error->message);
}
else
{
g_usb_context_set_debug (priv->usb_ctx, G_LOG_LEVEL_INFO);
g_signal_connect_object (priv->usb_ctx,
"device-added",
G_CALLBACK (usb_device_added_cb),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->usb_ctx,
"device-removed",
G_CALLBACK (usb_device_removed_cb),
self,
G_CONNECT_SWAPPED);
}
}
/**
* fp_context_new:
*
* Create a new #FpContext.
*
* Returns: (transfer full): a newly created #FpContext
*/
FpContext *
fp_context_new (void)
{
return g_object_new (FP_TYPE_CONTEXT, NULL);
}
/**
* fp_context_enumerate:
* @context: a #FpContext
*
* Enumerate all devices. You should call this function exactly once
* at startup. Please note that it iterates the mainloop until all
* devices are enumerated.
*/
void
fp_context_enumerate (FpContext *context)
{
FpContextPrivate *priv = fp_context_get_instance_private (context);
gint i;
g_return_if_fail (FP_IS_CONTEXT (context));
if (priv->enumerated)
return;
priv->enumerated = TRUE;
/* USB devices are handled from callbacks */
g_usb_context_enumerate (priv->usb_ctx);
/* Handle Virtual devices based on environment variables */
for (i = 0; i < priv->drivers->len; i++)
{
GType driver = g_array_index (priv->drivers, GType, i);
FpDeviceClass *cls = FP_DEVICE_CLASS (g_type_class_ref (driver));
const FpIdEntry *entry;
if (cls->type != FP_DEVICE_TYPE_VIRTUAL)
continue;
for (entry = cls->id_table; entry->pid; entry++)
{
const gchar *val;
val = g_getenv (entry->virtual_envvar);
if (!val || val[0] == '\0')
continue;
g_debug ("Found virtual environment device: %s, %s", entry->virtual_envvar, val);
priv->pending_devices++;
g_async_initable_new_async (driver,
G_PRIORITY_LOW,
priv->cancellable,
async_device_init_done_cb,
context,
"fp-environ", val,
"fp-driver-data", entry->driver_data,
NULL);
g_debug ("created");
}
g_type_class_unref (cls);
}
while (priv->pending_devices)
g_main_context_iteration (NULL, TRUE);
}
/**
* fp_context_get_devices:
* @context: a #FpContext
*
* Get all devices. fp_context_enumerate() will be called as needed.
*
* Returns: (transfer none) (element-type FpDevice): a new #GPtrArray of #GUsbDevice's.
*/
GPtrArray *
fp_context_get_devices (FpContext *context)
{
FpContextPrivate *priv = fp_context_get_instance_private (context);
g_return_val_if_fail (FP_IS_CONTEXT (context), NULL);
fp_context_enumerate (context);
return priv->devices;
}