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 |