]> git.sesse.net Git - vlc/blob - modules/services_discovery/udev.c
udev: remove remaining items at exit
[vlc] / modules / services_discovery / udev.c
1 /**
2  * @file udev.c
3  * @brief List of multimedia devices for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2009 Rémi Denis-Courmont
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; either version 2.1
11  * of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  ****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <libudev.h>
28 #include <vlc_common.h>
29 #include <vlc_services_discovery.h>
30 #include <vlc_plugin.h>
31 #include <search.h>
32 #include <poll.h>
33 #include <errno.h>
34
35 static int OpenV4L (vlc_object_t *);
36 static int OpenDisc (vlc_object_t *);
37 static void Close (vlc_object_t *);
38
39 /*
40  * Module descriptor
41  */
42 vlc_module_begin ()
43     set_shortname (N_("Devices"))
44     set_description (N_("Capture devices"))
45     set_category (CAT_PLAYLIST)
46     set_subcategory (SUBCAT_PLAYLIST_SD)
47     set_capability ("services_discovery", 0)
48     set_callbacks (OpenV4L, Close)
49     add_shortcut ("v4l")
50
51     add_submodule ()
52     set_shortname (N_("Discs"))
53     set_description (N_("Discs"))
54     set_category (CAT_PLAYLIST)
55     set_subcategory (SUBCAT_PLAYLIST_SD)
56     set_capability ("services_discovery", 0)
57     set_callbacks (OpenDisc, Close)
58     add_shortcut ("disc")
59
60 vlc_module_end ()
61
62 struct device
63 {
64     dev_t devnum; /* must be first */
65     input_item_t *item;
66     services_discovery_t *sd;
67 };
68
69 struct subsys
70 {
71     const char *name;
72     char * (*get_mrl) (struct udev_device *dev);
73     char * (*get_name) (struct udev_device *dev);
74     char * (*get_cat) (struct udev_device *dev);
75     int item_type;
76 };
77
78 struct services_discovery_sys_t
79 {
80     const struct subsys *subsys;
81     struct udev_monitor *monitor;
82     vlc_thread_t         thread;
83     void                *root;
84 };
85
86 /**
87  * Compares two devices (to support binary search).
88  */
89 static int cmpdev (const void *a, const void *b)
90 {
91     const dev_t *da = a, *db = b;
92     dev_t delta = *da - *db;
93
94     if (sizeof (delta) > sizeof (int))
95         return delta ? ((delta > 0) ? 1 : -1) : 0;
96     return (signed)delta;
97 }
98
99 static void DestroyDevice (void *data)
100 {
101     struct device *d = data;
102
103     if (d->sd)
104         services_discovery_RemoveItem (d->sd, d->item);
105     vlc_gc_decref (d->item);
106     free (d);
107 }
108
109 static char *decode_property (struct udev_device *, const char *);
110
111 /**
112  * Adds a udev device.
113  */
114 static int AddDevice (services_discovery_t *sd, struct udev_device *dev)
115 {
116     services_discovery_sys_t *p_sys = sd->p_sys;
117
118     char *mrl = p_sys->subsys->get_mrl (dev);
119     if (mrl == NULL)
120         return 0; /* don't know if it was an error... */
121     char *name = p_sys->subsys->get_name (dev);
122     input_item_t *item = input_item_NewWithType (VLC_OBJECT (sd), mrl,
123                                                  name ? name : mrl,
124                                                  0, NULL, 0, -1,
125                                                  p_sys->subsys->item_type);
126     msg_Dbg (sd, "adding %s (%s)", mrl, name);
127     free (name);
128     free (mrl);
129     if (item == NULL)
130         return -1;
131
132     struct device *d = malloc (sizeof (*d));
133     if (d == NULL)
134     {
135         vlc_gc_decref (item);
136         return -1;
137     }
138     d->devnum = udev_device_get_devnum (dev);
139     d->item = item;
140     d->sd = NULL;
141
142     struct device **dp = tsearch (d, &p_sys->root, cmpdev);
143     if (dp == NULL) /* Out-of-memory */
144     {
145         DestroyDevice (d);
146         return -1;
147     }
148     if (*dp != d) /* Overwrite existing device */
149     {
150         DestroyDevice (*dp);
151         *dp = d;
152     }
153
154     name = p_sys->subsys->get_cat (dev);
155     services_discovery_AddItem (sd, item, name ? name : "Generic");
156     d->sd = sd;
157     free (name);
158     return 0;
159 }
160
161 /**
162  * Removes a udev device (if present).
163  */
164 static void RemoveDevice (services_discovery_t *sd, struct udev_device *dev)
165 {
166     services_discovery_sys_t *p_sys = sd->p_sys;
167
168     dev_t num = udev_device_get_devnum (dev);
169     struct device **dp = tfind (&(dev_t){ num }, &p_sys->root, cmpdev);
170     if (dp == NULL)
171         return;
172
173     struct device *d = *dp;
174     tdelete (d, &p_sys->root, cmpdev);
175     DestroyDevice (d);
176 }
177
178 static void *Run (void *);
179
180 /**
181  * Probes and initializes.
182  */
183 static int Open (vlc_object_t *obj, const struct subsys *subsys)
184 {
185     services_discovery_t *sd = (services_discovery_t *)obj;
186     services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
187
188     if (p_sys == NULL)
189         return VLC_ENOMEM;
190     sd->p_sys = p_sys;
191     p_sys->subsys = subsys;
192     p_sys->root = NULL;
193
194     struct udev_monitor *mon = NULL;
195     struct udev *udev = udev_new ();
196     if (udev == NULL)
197         goto error;
198
199     mon = udev_monitor_new_from_netlink (udev, "udev");
200     if (mon == NULL
201      || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name,
202                                                          NULL))
203         goto error;
204     p_sys->monitor = mon;
205
206     /* Enumerate existing devices */
207     struct udev_enumerate *devenum = udev_enumerate_new (udev);
208     if (devenum == NULL)
209         goto error;
210     if (udev_enumerate_add_match_subsystem (devenum, subsys->name))
211     {
212         udev_enumerate_unref (devenum);
213         goto error;
214     }
215
216     udev_monitor_enable_receiving (mon);
217     /* Note that we enumerate _after_ monitoring is enabled so that we do not
218      * loose device events occuring while we are enumerating. We could still
219      * loose events if the Netlink socket receive buffer overflows. */
220     udev_enumerate_scan_devices (devenum);
221     struct udev_list_entry *devlist = udev_enumerate_get_list_entry (devenum);
222     struct udev_list_entry *deventry;
223     udev_list_entry_foreach (deventry, devlist)
224     {
225         const char *path = udev_list_entry_get_name (deventry);
226         struct udev_device *dev = udev_device_new_from_syspath (udev, path);
227         AddDevice (sd, dev);
228         udev_device_unref (dev);
229     }
230     udev_enumerate_unref (devenum);
231
232     if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
233     {   /* Fallback without thread */
234         udev_monitor_unref (mon);
235         udev_unref (udev);
236         p_sys->monitor = NULL;
237     }
238     return VLC_SUCCESS;
239
240 error:
241     if (mon != NULL)
242         udev_monitor_unref (mon);
243     if (udev != NULL)
244         udev_unref (udev);
245     free (p_sys);
246     return VLC_EGENERIC;
247 }
248
249 /**
250  * Releases resources
251  */
252 static void Close (vlc_object_t *obj)
253 {
254     services_discovery_t *sd = (services_discovery_t *)obj;
255     services_discovery_sys_t *p_sys = sd->p_sys;
256
257     if (p_sys->monitor != NULL)
258     {
259         struct udev *udev = udev_monitor_get_udev (p_sys->monitor);
260
261         vlc_cancel (p_sys->thread);
262         vlc_join (p_sys->thread, NULL);
263         udev_monitor_unref (p_sys->monitor);
264         udev_unref (udev);
265     }
266
267     tdestroy (p_sys->root, DestroyDevice);
268     free (p_sys);
269 }
270
271 static void *Run (void *data)
272 {
273     services_discovery_t *sd = data;
274     services_discovery_sys_t *p_sys = sd->p_sys;
275     struct udev_monitor *mon = p_sys->monitor;
276
277     int fd = udev_monitor_get_fd (mon);
278     struct pollfd ufd = { .fd = fd, .events = POLLIN, };
279
280     for (;;)
281     {
282         while (poll (&ufd, 1, -1) == -1)
283             if (errno != EINTR)
284                 break;
285
286         int canc = vlc_savecancel ();
287         struct udev_device *dev = udev_monitor_receive_device (mon);
288         if (dev == NULL)
289             continue;
290
291         const char *action = udev_device_get_action (dev);
292         if (!strcmp (action, "add"))
293             AddDevice (sd, dev);
294         else if (!strcmp (action, "remove"))
295             RemoveDevice (sd, dev);
296         else if (!strcmp (action, "change"))
297         {
298             RemoveDevice (sd, dev);
299             AddDevice (sd, dev);
300         }
301         udev_device_unref (dev);
302         vlc_restorecancel (canc);
303     }
304     return NULL;
305 }
306
307 /**
308  * Converts an hexadecimal digit to an integer.
309  */
310 static int hex (char c)
311 {
312     if (c >= '0' && c <= '9')
313         return c - '0';
314     if (c >= 'A' && c <= 'F')
315         return c + 10 - 'A';
316     if (c >= 'a' && c <= 'f')
317         return c + 10 - 'a';
318     return -1;
319 }
320
321 /**
322  * Decodes a udev hexadecimal-encoded property.
323  */
324 static char *decode (const char *enc)
325 {
326     char *ret = enc ? strdup (enc) : NULL;
327     if (ret == NULL)
328         return NULL;
329
330     char *out = ret;
331     for (const char *in = ret; *in; out++)
332     {
333         int h1, h2;
334
335         if ((in[0] == '\\') && (in[1] == 'x')
336          && ((h1 = hex (in[2])) != -1)
337          && ((h2 = hex (in[3])) != -1))
338         {
339             *out = (h1 << 4) | h2;
340             in += 4;
341         }
342         else
343         {
344             *out = *in;
345             in++;
346         }
347     }
348     *out = 0;
349     return ret;
350 }
351
352 static char *decode_property (struct udev_device *dev, const char *name)
353 {
354     return decode (udev_device_get_property_value (dev, name));
355 }
356
357 /*** Video4Linux support ***/
358 static bool is_v4l_legacy (struct udev_device *dev)
359 {
360     const char *version;
361
362     version = udev_device_get_property_value (dev, "ID_V4L_VERSION");
363     return version && !strcmp (version, "1");
364 }
365
366 static char *v4l_get_mrl (struct udev_device *dev)
367 {
368     /* Determine media location */
369     const char *scheme = "v4l2";
370     if (is_v4l_legacy (dev))
371         scheme = "v4l";
372     const char *node = udev_device_get_devnode (dev);
373     char *mrl;
374
375     if (asprintf (&mrl, "%s://%s", scheme, node) == -1)
376         mrl = NULL;
377     return mrl;
378 }
379
380 static char *v4l_get_name (struct udev_device *dev)
381 {
382     const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT");
383     return prd ? strdup (prd) : NULL;
384 }
385
386 static char *v4l_get_cat (struct udev_device *dev)
387 {
388     return decode_property (dev, "ID_VENDOR_ENC");
389 }
390
391 int OpenV4L (vlc_object_t *obj)
392 {
393     static const struct subsys subsys = {
394         "video4linux", v4l_get_mrl, v4l_get_name, v4l_get_cat, ITEM_TYPE_CARD,
395     };
396
397     return Open (obj, &subsys);
398 }
399
400 /*** Discs support ***/
401 static char *disc_get_mrl (struct udev_device *dev)
402 {
403     const char *val;
404
405     val = udev_device_get_property_value (dev, "ID_CDROM");
406     if (val == NULL)
407         return NULL; /* Ignore non-optical block devices */
408
409     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_STATE");
410     if ((val == NULL) || !strcmp (val, "blank"))
411         return NULL; /* ignore empty drives and virgin recordable discs */
412
413     const char *scheme = "file";
414     val = udev_device_get_property_value (dev,
415                                           "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
416     if (val && atoi (val))
417         scheme = "cdda"; /* Audio CD rather than file system */
418 #if 0 /* we can use file:// for DVDs */
419     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
420     if (val && atoi (val))
421         scheme = "dvd";
422 #endif
423     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
424     if (val && atoi (val))
425         scheme = "bd";
426 #ifdef LOL
427     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
428     if (val && atoi (val))
429         scheme = "hddvd";
430 #endif
431
432     val = udev_device_get_devnode (dev);
433     char *mrl;
434
435     if (asprintf (&mrl, "%s://%s", scheme, val) == -1)
436         mrl = NULL;
437     return mrl;
438 }
439
440 static char *disc_get_name (struct udev_device *dev)
441 {
442     return decode_property (dev, "ID_FS_LABEL_ENC");
443 }
444
445 static char *disc_get_cat (struct udev_device *dev)
446 {
447     const char *val;
448     const char *cat = "Unknown";
449
450     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_CD");
451     if (val && atoi (val))
452         cat = "CD";
453     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
454     if (val && atoi (val))
455         cat = "DVD";
456     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
457     if (val && atoi (val))
458         cat = "Blue-ray disc";
459     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
460     if (val && atoi (val))
461         cat = "HD DVD";
462
463     return strdup (cat);
464 }
465
466 int OpenDisc (vlc_object_t *obj)
467 {
468     static const struct subsys subsys = {
469         "block", disc_get_mrl, disc_get_name, disc_get_cat, ITEM_TYPE_DISC,
470     };
471
472     return Open (obj, &subsys);
473 }