GCC Code Coverage Report


Directory: ./
File: libfprint/fp-context.c
Date: 2024-09-16 14:36:32
Exec Total Coverage
Lines: 201 220 91.4%
Functions: 17 18 94.4%
Branches: 83 131 63.4%

Line Branch Exec Source
1 /*
2 * FpContext - A FPrint context
3 * Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #define FP_COMPONENT "context"
21 #include <fpi-log.h>
22
23 #include "fpi-context.h"
24 #include "fpi-device.h"
25 #include <gusb.h>
26 #include <stdio.h>
27
28 #include <config.h>
29
30 #ifdef HAVE_UDEV
31 #include <gudev/gudev.h>
32 #endif
33
34 /**
35 * SECTION: fp-context
36 * @title: FpContext
37 * @short_description: Discover fingerprint devices
38 *
39 * The #FpContext allows you to discover fingerprint scanning hardware. This
40 * is the starting point when integrating libfprint into your software.
41 *
42 * The <link linkend="device-added">device-added</link> and device-removed signals allow you to handle devices
43 * that may be hotplugged at runtime.
44 */
45
46 typedef struct
47 {
48 GUsbContext *usb_ctx;
49 GCancellable *cancellable;
50
51 GSList *sources;
52
53 gint pending_devices;
54 gboolean enumerated;
55
56 GArray *drivers;
57 GPtrArray *devices;
58 } FpContextPrivate;
59
60
3/5
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 608 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 120 times.
✗ Branch 4 not taken.
3565 G_DEFINE_TYPE_WITH_PRIVATE (FpContext, fp_context, G_TYPE_OBJECT)
61
62 enum {
63 DEVICE_ADDED_SIGNAL,
64 DEVICE_REMOVED_SIGNAL,
65 LAST_SIGNAL
66 };
67 static guint signals[LAST_SIGNAL] = { 0 };
68
69 static const char *
70 4862 get_drivers_allowlist_env (void)
71 {
72 4862 return g_getenv ("FP_DRIVERS_ALLOWLIST");
73 }
74
75 static gboolean
76 4719 is_driver_allowed (const gchar *driver)
77 {
78 9438 g_auto(GStrv) allowlisted_drivers = NULL;
79 4719 const char *fp_drivers_allowlist_env;
80
81
1/2
✓ Branch 0 taken 4719 times.
✗ Branch 1 not taken.
4719 g_return_val_if_fail (driver, TRUE);
82
83 4719 fp_drivers_allowlist_env = get_drivers_allowlist_env ();
84
85
1/2
✓ Branch 0 taken 4719 times.
✗ Branch 1 not taken.
4719 if (!fp_drivers_allowlist_env)
86 return TRUE;
87
88 4719 allowlisted_drivers = g_strsplit (fp_drivers_allowlist_env, ":", -1);
89 4719 return g_strv_contains ((const gchar * const *) allowlisted_drivers, driver);
90 }
91
92 typedef struct
93 {
94 FpContext *context;
95 FpDevice *device;
96 GSource *source;
97 } RemoveDeviceData;
98
99 static gboolean
100 7 remove_device_idle_cb (RemoveDeviceData *data)
101 {
102 7 FpContextPrivate *priv = fp_context_get_instance_private (data->context);
103 7 guint idx = 0;
104
105
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 g_return_val_if_fail (g_ptr_array_find (priv->devices, data->device, &idx), G_SOURCE_REMOVE);
106
107 7 g_signal_emit (data->context, signals[DEVICE_REMOVED_SIGNAL], 0, data->device);
108 7 g_ptr_array_remove_index_fast (priv->devices, idx);
109
110 7 return G_SOURCE_REMOVE;
111 }
112
113 static void
114 7 remove_device_data_free (RemoveDeviceData *data)
115 {
116 7 FpContextPrivate *priv = fp_context_get_instance_private (data->context);
117
118 7 priv->sources = g_slist_remove (priv->sources, data->source);
119 7 g_free (data);
120 7 }
121
122 static void
123 7 remove_device (FpContext *context, FpDevice *device)
124 {
125 14 g_autoptr(GSource) source = NULL;
126 7 FpContextPrivate *priv = fp_context_get_instance_private (context);
127 7 RemoveDeviceData *data;
128
129 7 data = g_new (RemoveDeviceData, 1);
130 7 data->context = context;
131 7 data->device = device;
132
133 7 source = data->source = g_idle_source_new ();
134 7 g_source_set_callback (source,
135 G_SOURCE_FUNC (remove_device_idle_cb), data,
136 (GDestroyNotify) remove_device_data_free);
137 7 g_source_attach (source, g_main_context_get_thread_default ());
138
139
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 priv->sources = g_slist_prepend (priv->sources, source);
140 7 }
141
142 static void
143 5 device_remove_on_notify_open_cb (FpContext *context, GParamSpec *pspec, FpDevice *device)
144 {
145 5 remove_device (context, device);
146 5 }
147
148 static void
149 7 device_removed_cb (FpContext *context, FpDevice *device)
150 {
151 7 gboolean open = FALSE;
152
153 7 g_object_get (device, "open", &open, NULL);
154
155 /* Wait for device close if the device is currently still open. */
156
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
7 if (open)
157 {
158 5 g_signal_connect_object (device, "notify::open",
159 (GCallback) device_remove_on_notify_open_cb,
160 context,
161 G_CONNECT_SWAPPED);
162 }
163 else
164 {
165 2 remove_device (context, device);
166 }
167 7 }
168
169 static void
170 141 async_device_init_done_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
171 {
172 141 g_autoptr(GError) error = NULL;
173 141 FpDevice *device;
174 141 FpContext *context;
175 141 FpContextPrivate *priv;
176
177 141 device = FP_DEVICE (g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
178 res, &error));
179
1/2
✓ Branch 2 taken 141 times.
✗ Branch 3 not taken.
141 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
180 return;
181
182 141 context = FP_CONTEXT (user_data);
183 141 priv = fp_context_get_instance_private (context);
184 141 priv->pending_devices--;
185
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 141 times.
141 if (error)
187 {
188 g_message ("Ignoring device due to initialization error: %s", error->message);
189 return;
190 }
191
192 141 g_ptr_array_add (priv->devices, device);
193
194 141 g_signal_connect_object (device, "removed",
195 (GCallback) device_removed_cb,
196 context,
197 G_CONNECT_SWAPPED);
198
199
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 141 times.
141 g_signal_emit (context, signals[DEVICE_ADDED_SIGNAL], 0, device);
200 }
201
202 static void
203 1100 usb_device_added_cb (FpContext *self, GUsbDevice *device, GUsbContext *usb_ctx)
204 {
205 1100 FpContextPrivate *priv = fp_context_get_instance_private (self);
206 1100 GType found_driver = G_TYPE_NONE;
207 1100 const FpIdEntry *found_entry = NULL;
208 1100 gint found_score = 0;
209 1100 gint i;
210 1100 guint16 pid, vid;
211
212 1100 pid = g_usb_device_get_pid (device);
213 1100 vid = g_usb_device_get_vid (device);
214
215 /* Find the best driver to handle this USB device. */
216
2/2
✓ Branch 1 taken 3188 times.
✓ Branch 2 taken 1100 times.
5388 for (i = 0; i < priv->drivers->len; i++)
217 {
218 3188 GType driver = g_array_index (priv->drivers, GType, i);
219 6376 g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver);
220 3188 const FpIdEntry *entry;
221
222
2/2
✓ Branch 0 taken 3132 times.
✓ Branch 1 taken 56 times.
3188 if (cls->type != FP_DEVICE_TYPE_USB)
223 3132 continue;
224
225
2/2
✓ Branch 0 taken 566 times.
✓ Branch 1 taken 56 times.
622 for (entry = cls->id_table; entry->pid; entry++)
226 {
227 566 gint driver_score = 50;
228
229
3/4
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 541 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
566 if (entry->pid != pid || entry->vid != vid)
230 541 continue;
231
232
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 23 times.
25 if (cls->usb_discover)
233 2 driver_score = cls->usb_discover (device);
234
235 /* Is this driver better than the one we had? */
236
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 if (driver_score <= found_score)
237 continue;
238
239 found_score = driver_score;
240 found_driver = driver;
241 found_entry = entry;
242 }
243 }
244
245
2/2
✓ Branch 0 taken 1075 times.
✓ Branch 1 taken 25 times.
1100 if (found_driver == G_TYPE_NONE)
246 {
247 1075 g_debug ("No driver found for USB device %04X:%04X", vid, pid);
248 1075 return;
249 }
250
251 25 priv->pending_devices++;
252 25 g_async_initable_new_async (found_driver,
253 G_PRIORITY_LOW,
254 priv->cancellable,
255 async_device_init_done_cb,
256 self,
257 "fpi-usb-device", device,
258 25 "fpi-driver-data", found_entry->driver_data,
259 NULL);
260 }
261
262 static void
263 usb_device_removed_cb (FpContext *self, GUsbDevice *device, GUsbContext *usb_ctx)
264 {
265 FpContextPrivate *priv = fp_context_get_instance_private (self);
266 gint i;
267
268 /* Do the lazy way and just look at each device. */
269 for (i = 0; i < priv->devices->len; i++)
270 {
271 FpDevice *dev = g_ptr_array_index (priv->devices, i);
272 FpDeviceClass *cls = FP_DEVICE_GET_CLASS (dev);
273
274 if (cls->type != FP_DEVICE_TYPE_USB)
275 continue;
276
277 if (fpi_device_get_usb_device (dev) == device)
278 fpi_device_remove (dev);
279 }
280 }
281
282 static void
283 143 fp_context_finalize (GObject *object)
284 {
285 143 FpContext *self = (FpContext *) object;
286 143 FpContextPrivate *priv = fp_context_get_instance_private (self);
287
288 143 g_cancellable_cancel (priv->cancellable);
289
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 g_clear_object (&priv->cancellable);
290
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 g_clear_pointer (&priv->drivers, g_array_unref);
291
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 g_clear_pointer (&priv->devices, g_ptr_array_unref);
292
293 143 g_slist_free_full (g_steal_pointer (&priv->sources), (GDestroyNotify) g_source_destroy);
294
295
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 if (priv->usb_ctx)
296 143 g_object_run_dispose (G_OBJECT (priv->usb_ctx));
297
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 g_clear_object (&priv->usb_ctx);
298
299 143 G_OBJECT_CLASS (fp_context_parent_class)->finalize (object);
300 143 }
301
302 static void
303 120 fp_context_class_init (FpContextClass *klass)
304 {
305 120 GObjectClass *object_class = G_OBJECT_CLASS (klass);
306
307 120 object_class->finalize = fp_context_finalize;
308
309 /**
310 * FpContext::device-added:
311 * @context: the #FpContext instance that emitted the signal
312 * @device: A #FpDevice
313 *
314 * This signal is emitted when a fingerprint reader is added.
315 **/
316 120 signals[DEVICE_ADDED_SIGNAL] = g_signal_new ("device-added",
317 G_TYPE_FROM_CLASS (klass),
318 G_SIGNAL_RUN_LAST,
319 G_STRUCT_OFFSET (FpContextClass, device_added),
320 NULL,
321 NULL,
322 g_cclosure_marshal_VOID__OBJECT,
323 G_TYPE_NONE,
324 1,
325 FP_TYPE_DEVICE);
326
327 /**
328 * FpContext::device-removed:
329 * @context: the #FpContext instance that emitted the signal
330 * @device: A #FpDevice
331 *
332 * This signal is emitted when a fingerprint reader is removed.
333 *
334 * It is guaranteed that the device has been closed before this signal
335 * is emitted. See the #FpDevice removed signal documentation for more
336 * information.
337 **/
338 120 signals[DEVICE_REMOVED_SIGNAL] = g_signal_new ("device-removed",
339 G_TYPE_FROM_CLASS (klass),
340 G_SIGNAL_RUN_LAST,
341 G_STRUCT_OFFSET (FpContextClass, device_removed),
342 NULL,
343 NULL,
344 g_cclosure_marshal_VOID__OBJECT,
345 G_TYPE_NONE,
346 1,
347 FP_TYPE_DEVICE);
348 120 }
349
350 static void
351 143 fp_context_init (FpContext *self)
352 {
353 286 g_autoptr(GError) error = NULL;
354 143 FpContextPrivate *priv = fp_context_get_instance_private (self);
355 143 guint i;
356
357 143 g_debug ("Initializing FpContext (libfprint version " LIBFPRINT_VERSION ")");
358
359 143 priv->drivers = fpi_get_driver_types ();
360
361
1/2
✓ Branch 1 taken 143 times.
✗ Branch 2 not taken.
143 if (get_drivers_allowlist_env ())
362 {
363
2/2
✓ Branch 0 taken 4719 times.
✓ Branch 1 taken 143 times.
4862 for (i = 0; i < priv->drivers->len;)
364 {
365 4719 GType driver = g_array_index (priv->drivers, GType, i);
366 9438 g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver);
367
368
2/2
✓ Branch 1 taken 4342 times.
✓ Branch 2 taken 377 times.
4719 if (!is_driver_allowed (cls->id))
369 4342 g_array_remove_index (priv->drivers, i);
370 else
371 377 ++i;
372 }
373 }
374
375 143 priv->devices = g_ptr_array_new_with_free_func (g_object_unref);
376
377 143 priv->cancellable = g_cancellable_new ();
378 143 priv->usb_ctx = g_usb_context_new (&error);
379
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 143 times.
143 if (!priv->usb_ctx)
380 {
381 g_message ("Could not initialise USB Subsystem: %s", error->message);
382 }
383 else
384 {
385 143 g_usb_context_set_debug (priv->usb_ctx, G_LOG_LEVEL_INFO);
386 143 g_signal_connect_object (priv->usb_ctx,
387 "device-added",
388 G_CALLBACK (usb_device_added_cb),
389 self,
390 G_CONNECT_SWAPPED);
391 143 g_signal_connect_object (priv->usb_ctx,
392 "device-removed",
393 G_CALLBACK (usb_device_removed_cb),
394 self,
395 G_CONNECT_SWAPPED);
396 }
397 143 }
398
399 /**
400 * fp_context_new:
401 *
402 * Create a new #FpContext.
403 *
404 * Returns: (transfer full): a newly created #FpContext
405 */
406 FpContext *
407 26 fp_context_new (void)
408 {
409 26 return g_object_new (FP_TYPE_CONTEXT, NULL);
410 }
411
412 /**
413 * fp_context_enumerate:
414 * @context: a #FpContext
415 *
416 * Enumerate all devices. You should call this function exactly once
417 * at startup. Please note that it iterates the mainloop until all
418 * devices are enumerated.
419 */
420 void
421 174 fp_context_enumerate (FpContext *context)
422 {
423 174 FpContextPrivate *priv = fp_context_get_instance_private (context);
424 174 gboolean dispatched;
425 174 gint i;
426
427
1/2
✓ Branch 1 taken 174 times.
✗ Branch 2 not taken.
174 g_return_if_fail (FP_IS_CONTEXT (context));
428
429
2/2
✓ Branch 0 taken 142 times.
✓ Branch 1 taken 32 times.
174 if (priv->enumerated)
430 return;
431
432 142 priv->enumerated = TRUE;
433
434 /* USB devices are handled from callbacks */
435
1/2
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
142 if (priv->usb_ctx)
436 142 g_usb_context_enumerate (priv->usb_ctx);
437
438 /* Handle Virtual devices based on environment variables */
439
2/2
✓ Branch 0 taken 374 times.
✓ Branch 1 taken 142 times.
516 for (i = 0; i < priv->drivers->len; i++)
440 {
441 374 GType driver = g_array_index (priv->drivers, GType, i);
442 748 g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver);
443 374 const FpIdEntry *entry;
444
445
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 348 times.
374 if (cls->type != FP_DEVICE_TYPE_VIRTUAL)
446 26 continue;
447
448
2/2
✓ Branch 0 taken 464 times.
✓ Branch 1 taken 348 times.
812 for (entry = cls->id_table; entry->pid; entry++)
449 {
450 464 const gchar *val;
451
452 464 val = g_getenv (entry->virtual_envvar);
453
3/4
✓ Branch 0 taken 115 times.
✓ Branch 1 taken 349 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 115 times.
464 if (!val || val[0] == '\0')
454 349 continue;
455
456 115 g_debug ("Found virtual environment device: %s, %s", entry->virtual_envvar, val);
457 115 priv->pending_devices++;
458 115 g_async_initable_new_async (driver,
459 G_PRIORITY_LOW,
460 priv->cancellable,
461 async_device_init_done_cb,
462 context,
463 "fpi-environ", val,
464 115 "fpi-driver-data", entry->driver_data,
465 NULL);
466 115 g_debug ("created");
467 }
468 }
469
470
471 #ifdef HAVE_UDEV
472 {
473 142 g_autoptr(GUdevClient) udev_client = g_udev_client_new (NULL);
474
475 /* This uses a very simple algorithm to allocate devices to drivers and assumes that no two drivers will want the same device. Future improvements
476 * could add a usb_discover style udev_discover that returns a score, however for internal devices the potential overlap should be very low between
477 * separate drivers.
478 */
479
480
1/2
✓ Branch 1 taken 142 times.
✗ Branch 2 not taken.
284 g_autoptr(GList) spidev_devices = g_udev_client_query_by_subsystem (udev_client, "spidev");
481
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 142 times.
142 g_autoptr(GList) hidraw_devices = g_udev_client_query_by_subsystem (udev_client, "hidraw");
482
483 /* for each potential driver, try to match all requested resources. */
484
2/2
✓ Branch 1 taken 374 times.
✓ Branch 2 taken 142 times.
658 for (i = 0; i < priv->drivers->len; i++)
485 {
486 374 GType driver = g_array_index (priv->drivers, GType, i);
487 748 g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver);
488 374 const FpIdEntry *entry;
489
490
2/2
✓ Branch 0 taken 373 times.
✓ Branch 1 taken 1 times.
374 if (cls->type != FP_DEVICE_TYPE_UDEV)
491 373 continue;
492
493
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
10 for (entry = cls->id_table; entry->udev_types; entry++)
494 {
495 9 GList *matched_spidev = NULL, *matched_hidraw = NULL;
496
497
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (entry->udev_types & FPI_DEVICE_UDEV_SUBTYPE_SPIDEV)
498 {
499
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
9 for (matched_spidev = spidev_devices; matched_spidev; matched_spidev = matched_spidev->next)
500 {
501 1 const gchar * sysfs = g_udev_device_get_sysfs_path (matched_spidev->data);
502
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!sysfs)
503 continue;
504
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (strstr (sysfs, entry->spi_acpi_id))
505 break;
506 }
507 /* If match was not found exit */
508
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
9 if (matched_spidev == NULL)
509 8 continue;
510 }
511
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (entry->udev_types & FPI_DEVICE_UDEV_SUBTYPE_HIDRAW)
512 {
513
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 for (matched_hidraw = hidraw_devices; matched_hidraw; matched_hidraw = matched_hidraw->next)
514 {
515 /* Find the parent HID node, and check the vid/pid from its HID_ID property */
516 2 g_autoptr(GUdevDevice) parent = g_udev_device_get_parent_with_subsystem (matched_hidraw->data, "hid", NULL);
517 1 const gchar * hid_id = g_udev_device_get_property (parent, "HID_ID");
518 1 guint32 vendor, product;
519
520
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!parent || !hid_id)
521 continue;
522
523
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (sscanf (hid_id, "%*X:%X:%X", &vendor, &product) != 2)
524 continue;
525
526
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (vendor == entry->hid_id.vid && product == entry->hid_id.pid)
527 break;
528 }
529 /* If match was not found exit */
530
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (matched_hidraw == NULL)
531 continue;
532 }
533 1 priv->pending_devices++;
534
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 g_async_initable_new_async (driver,
535 G_PRIORITY_LOW,
536 priv->cancellable,
537 async_device_init_done_cb,
538 context,
539 1 "fpi-driver-data", entry->driver_data,
540 1 "fpi-udev-data-spidev", (matched_spidev ? g_udev_device_get_device_file (matched_spidev->data) : NULL),
541 1 "fpi-udev-data-hidraw", (matched_hidraw ? g_udev_device_get_device_file (matched_hidraw->data) : NULL),
542 NULL);
543 /* remove entries from list to avoid conflicts */
544
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (matched_spidev)
545 {
546 1 g_object_unref (matched_spidev->data);
547 1 spidev_devices = g_list_delete_link (spidev_devices, matched_spidev);
548 }
549
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (matched_hidraw)
550 {
551 1 g_object_unref (matched_hidraw->data);
552 1 hidraw_devices = g_list_delete_link (hidraw_devices, matched_hidraw);
553 }
554 }
555 }
556
557 /* free all unused elemnts in both lists */
558 142 g_list_foreach (spidev_devices, (GFunc) g_object_unref, NULL);
559
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 142 times.
142 g_list_foreach (hidraw_devices, (GFunc) g_object_unref, NULL);
560 }
561 #endif
562
563 /* Iterate until 1. we have no pending devices, and 2. the mainloop is idle
564 * This takes care of processing hotplug events that happened during
565 * enumeration.
566 * This is important due to USB `persist` being turned off. At resume time,
567 * devices will disappear and immediately re-appear. In this situation,
568 * enumerate could first see the old state with a removed device resulting
569 * in it to not be discovered.
570 * As a hotplug event is seemingly emitted by the kernel immediately, we can
571 * simply make sure to process all events before returning from enumerate.
572 */
573 dispatched = TRUE;
574
4/4
✓ Branch 0 taken 286 times.
✓ Branch 1 taken 284 times.
✓ Branch 2 taken 142 times.
✓ Branch 3 taken 142 times.
570 while (priv->pending_devices || dispatched)
575 428 dispatched = g_main_context_iteration (NULL, !!priv->pending_devices);
576 }
577
578 /**
579 * fp_context_get_devices:
580 * @context: a #FpContext
581 *
582 * Get all devices. fp_context_enumerate() will be called as needed.
583 *
584 * Returns: (transfer none) (element-type FpDevice): a new #GPtrArray of #FpDevice's.
585 */
586 GPtrArray *
587 147 fp_context_get_devices (FpContext *context)
588 {
589 147 FpContextPrivate *priv = fp_context_get_instance_private (context);
590
591
1/2
✓ Branch 1 taken 147 times.
✗ Branch 2 not taken.
147 g_return_val_if_fail (FP_IS_CONTEXT (context), NULL);
592
593 147 fp_context_enumerate (context);
594
595 147 return priv->devices;
596 }
597