]> git.sesse.net Git - vlc/blob - modules/services_discovery/udev.c
Use var_Inherit* instead of var_CreateGet*.
[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 <vlc_url.h>
32 #include <search.h>
33 #include <poll.h>
34 #include <errno.h>
35
36 static int OpenV4L (vlc_object_t *);
37 static int OpenALSA (vlc_object_t *);
38 static int OpenDisc (vlc_object_t *);
39 static void Close (vlc_object_t *);
40 static int vlc_sd_probe_Open (vlc_object_t *);
41
42 /*
43  * Module descriptor
44  */
45 vlc_module_begin ()
46     set_shortname (N_("Video capture"))
47     set_description (N_("Video capture (Video4Linux)"))
48     set_category (CAT_PLAYLIST)
49     set_subcategory (SUBCAT_PLAYLIST_SD)
50     set_capability ("services_discovery", 0)
51     set_callbacks (OpenV4L, Close)
52     add_shortcut ("v4l")
53
54     add_submodule ()
55     set_shortname (N_("Audio capture"))
56     set_description (N_("Audio capture (ALSA)"))
57     set_category (CAT_PLAYLIST)
58     set_subcategory (SUBCAT_PLAYLIST_SD)
59     set_capability ("services_discovery", 0)
60     set_callbacks (OpenALSA, Close)
61     add_shortcut ("alsa")
62
63     add_submodule ()
64     set_shortname (N_("Discs"))
65     set_description (N_("Discs"))
66     set_category (CAT_PLAYLIST)
67     set_subcategory (SUBCAT_PLAYLIST_SD)
68     set_capability ("services_discovery", 0)
69     set_callbacks (OpenDisc, Close)
70     add_shortcut ("disc")
71
72     VLC_SD_PROBE_SUBMODULE
73
74 vlc_module_end ()
75
76 static int vlc_sd_probe_Open (vlc_object_t *obj)
77 {
78     vlc_probe_t *probe = (vlc_probe_t *)obj;
79
80     struct udev *udev = udev_new ();
81     if (udev == NULL)
82         return VLC_PROBE_CONTINUE;
83
84     struct udev_monitor *mon = udev_monitor_new_from_netlink (udev, "udev");
85     if (mon != NULL)
86     {
87         vlc_sd_probe_Add (probe, "v4l{longname=\"Video capture\"}",
88                           N_("Video capture"), SD_CAT_DEVICES);
89         vlc_sd_probe_Add (probe, "alsa{longname=\"Audio capture\"}",
90                           N_("Audio capture"), SD_CAT_DEVICES);
91         vlc_sd_probe_Add (probe, "disc{longname=\"Discs\"}", N_("Discs"),
92                           SD_CAT_DEVICES);
93         udev_monitor_unref (mon);
94     }
95     udev_unref (udev);
96     return VLC_PROBE_CONTINUE;
97 }
98
99 struct device
100 {
101     dev_t devnum; /* must be first */
102     input_item_t *item;
103     services_discovery_t *sd;
104 };
105
106 struct subsys
107 {
108     const char *name;
109     char * (*get_mrl) (struct udev_device *dev);
110     char * (*get_name) (struct udev_device *dev);
111     char * (*get_cat) (struct udev_device *dev);
112     int item_type;
113 };
114
115 struct services_discovery_sys_t
116 {
117     const struct subsys *subsys;
118     struct udev_monitor *monitor;
119     vlc_thread_t         thread;
120     void                *root;
121 };
122
123 /**
124  * Compares two devices (to support binary search).
125  */
126 static int cmpdev (const void *a, const void *b)
127 {
128     const dev_t *da = a, *db = b;
129     dev_t delta = *da - *db;
130
131     if (sizeof (delta) > sizeof (int))
132         return delta ? ((delta > 0) ? 1 : -1) : 0;
133     return (signed)delta;
134 }
135
136 static void DestroyDevice (void *data)
137 {
138     struct device *d = data;
139
140     if (d->sd)
141         services_discovery_RemoveItem (d->sd, d->item);
142     vlc_gc_decref (d->item);
143     free (d);
144 }
145
146 static char *decode_property (struct udev_device *, const char *);
147
148 /**
149  * Adds a udev device.
150  */
151 static int AddDevice (services_discovery_t *sd, struct udev_device *dev)
152 {
153     services_discovery_sys_t *p_sys = sd->p_sys;
154
155     char *mrl = p_sys->subsys->get_mrl (dev);
156     if (mrl == NULL)
157         return 0; /* don't know if it was an error... */
158     char *name = p_sys->subsys->get_name (dev);
159     input_item_t *item = input_item_NewWithType (VLC_OBJECT (sd), mrl,
160                                                  name ? name : mrl,
161                                                  0, NULL, 0, -1,
162                                                  p_sys->subsys->item_type);
163     msg_Dbg (sd, "adding %s (%s)", mrl, name);
164     free (name);
165     free (mrl);
166     if (item == NULL)
167         return -1;
168
169     struct device *d = malloc (sizeof (*d));
170     if (d == NULL)
171     {
172         vlc_gc_decref (item);
173         return -1;
174     }
175     d->devnum = udev_device_get_devnum (dev);
176     d->item = item;
177     d->sd = NULL;
178
179     struct device **dp = tsearch (d, &p_sys->root, cmpdev);
180     if (dp == NULL) /* Out-of-memory */
181     {
182         DestroyDevice (d);
183         return -1;
184     }
185     if (*dp != d) /* Overwrite existing device */
186     {
187         DestroyDevice (*dp);
188         *dp = d;
189     }
190
191     name = p_sys->subsys->get_cat (dev);
192     services_discovery_AddItem (sd, item, name ? name : "Generic");
193     d->sd = sd;
194     free (name);
195     return 0;
196 }
197
198 /**
199  * Removes a udev device (if present).
200  */
201 static void RemoveDevice (services_discovery_t *sd, struct udev_device *dev)
202 {
203     services_discovery_sys_t *p_sys = sd->p_sys;
204
205     dev_t num = udev_device_get_devnum (dev);
206     struct device **dp = tfind (&(dev_t){ num }, &p_sys->root, cmpdev);
207     if (dp == NULL)
208         return;
209
210     struct device *d = *dp;
211     tdelete (d, &p_sys->root, cmpdev);
212     DestroyDevice (d);
213 }
214
215 static void *Run (void *);
216
217 /**
218  * Probes and initializes.
219  */
220 static int Open (vlc_object_t *obj, const struct subsys *subsys)
221 {
222     services_discovery_t *sd = (services_discovery_t *)obj;
223     services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
224
225     if (p_sys == NULL)
226         return VLC_ENOMEM;
227     sd->p_sys = p_sys;
228     p_sys->subsys = subsys;
229     p_sys->root = NULL;
230
231     struct udev_monitor *mon = NULL;
232     struct udev *udev = udev_new ();
233     if (udev == NULL)
234         goto error;
235
236     mon = udev_monitor_new_from_netlink (udev, "udev");
237     if (mon == NULL
238      || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name,
239                                                          NULL))
240         goto error;
241     p_sys->monitor = mon;
242
243     /* Enumerate existing devices */
244     struct udev_enumerate *devenum = udev_enumerate_new (udev);
245     if (devenum == NULL)
246         goto error;
247     if (udev_enumerate_add_match_subsystem (devenum, subsys->name))
248     {
249         udev_enumerate_unref (devenum);
250         goto error;
251     }
252
253     udev_monitor_enable_receiving (mon);
254     /* Note that we enumerate _after_ monitoring is enabled so that we do not
255      * loose device events occuring while we are enumerating. We could still
256      * loose events if the Netlink socket receive buffer overflows. */
257     udev_enumerate_scan_devices (devenum);
258     struct udev_list_entry *devlist = udev_enumerate_get_list_entry (devenum);
259     struct udev_list_entry *deventry;
260     udev_list_entry_foreach (deventry, devlist)
261     {
262         const char *path = udev_list_entry_get_name (deventry);
263         struct udev_device *dev = udev_device_new_from_syspath (udev, path);
264         AddDevice (sd, dev);
265         udev_device_unref (dev);
266     }
267     udev_enumerate_unref (devenum);
268
269     if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
270     {   /* Fallback without thread */
271         udev_monitor_unref (mon);
272         udev_unref (udev);
273         p_sys->monitor = NULL;
274     }
275     return VLC_SUCCESS;
276
277 error:
278     if (mon != NULL)
279         udev_monitor_unref (mon);
280     if (udev != NULL)
281         udev_unref (udev);
282     free (p_sys);
283     return VLC_EGENERIC;
284 }
285
286 /**
287  * Releases resources
288  */
289 static void Close (vlc_object_t *obj)
290 {
291     services_discovery_t *sd = (services_discovery_t *)obj;
292     services_discovery_sys_t *p_sys = sd->p_sys;
293
294     if (p_sys->monitor != NULL)
295     {
296         struct udev *udev = udev_monitor_get_udev (p_sys->monitor);
297
298         vlc_cancel (p_sys->thread);
299         vlc_join (p_sys->thread, NULL);
300         udev_monitor_unref (p_sys->monitor);
301         udev_unref (udev);
302     }
303
304     tdestroy (p_sys->root, DestroyDevice);
305     free (p_sys);
306 }
307
308 static void *Run (void *data)
309 {
310     services_discovery_t *sd = data;
311     services_discovery_sys_t *p_sys = sd->p_sys;
312     struct udev_monitor *mon = p_sys->monitor;
313
314     int fd = udev_monitor_get_fd (mon);
315     struct pollfd ufd = { .fd = fd, .events = POLLIN, };
316
317     for (;;)
318     {
319         while (poll (&ufd, 1, -1) == -1)
320             if (errno != EINTR)
321                 break;
322
323         int canc = vlc_savecancel ();
324         struct udev_device *dev = udev_monitor_receive_device (mon);
325         if (dev == NULL)
326             continue;
327
328         const char *action = udev_device_get_action (dev);
329         if (!strcmp (action, "add"))
330             AddDevice (sd, dev);
331         else if (!strcmp (action, "remove"))
332             RemoveDevice (sd, dev);
333         else if (!strcmp (action, "change"))
334         {
335             RemoveDevice (sd, dev);
336             AddDevice (sd, dev);
337         }
338         udev_device_unref (dev);
339         vlc_restorecancel (canc);
340     }
341     return NULL;
342 }
343
344 /**
345  * Converts an hexadecimal digit to an integer.
346  */
347 static int hex (char c)
348 {
349     if (c >= '0' && c <= '9')
350         return c - '0';
351     if (c >= 'A' && c <= 'F')
352         return c + 10 - 'A';
353     if (c >= 'a' && c <= 'f')
354         return c + 10 - 'a';
355     return -1;
356 }
357
358 /**
359  * Decodes a udev hexadecimal-encoded property.
360  */
361 static char *decode (const char *enc)
362 {
363     char *ret = enc ? strdup (enc) : NULL;
364     if (ret == NULL)
365         return NULL;
366
367     char *out = ret;
368     for (const char *in = ret; *in; out++)
369     {
370         int h1, h2;
371
372         if ((in[0] == '\\') && (in[1] == 'x')
373          && ((h1 = hex (in[2])) != -1)
374          && ((h2 = hex (in[3])) != -1))
375         {
376             *out = (h1 << 4) | h2;
377             in += 4;
378         }
379         else
380         {
381             *out = *in;
382             in++;
383         }
384     }
385     *out = 0;
386     return ret;
387 }
388
389 static char *decode_property (struct udev_device *dev, const char *name)
390 {
391     return decode (udev_device_get_property_value (dev, name));
392 }
393
394
395 /*** Video4Linux support ***/
396 static bool is_v4l_legacy (struct udev_device *dev)
397 {
398     const char *version;
399
400     version = udev_device_get_property_value (dev, "ID_V4L_VERSION");
401     return version && !strcmp (version, "1");
402 }
403
404 static char *v4l_get_mrl (struct udev_device *dev)
405 {
406     /* Determine media location */
407     const char *scheme = "v4l2";
408     if (is_v4l_legacy (dev))
409         scheme = "v4l";
410     const char *node = udev_device_get_devnode (dev);
411     char *mrl;
412
413     if (asprintf (&mrl, "%s://%s", scheme, node) == -1)
414         mrl = NULL;
415     return mrl;
416 }
417
418 static char *v4l_get_name (struct udev_device *dev)
419 {
420     const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT");
421     return prd ? strdup (prd) : NULL;
422 }
423
424 static char *v4l_get_cat (struct udev_device *dev)
425 {
426     return decode_property (dev, "ID_VENDOR_ENC");
427 }
428
429 int OpenV4L (vlc_object_t *obj)
430 {
431     static const struct subsys subsys = {
432         "video4linux", v4l_get_mrl, v4l_get_name, v4l_get_cat, ITEM_TYPE_CARD,
433     };
434
435     return Open (obj, &subsys);
436 }
437
438
439 #ifdef HAVE_ALSA
440 /*** Advanced Linux Sound Architecture support ***/
441 #include <alsa/asoundlib.h>
442
443 static int alsa_get_device (struct udev_device *dev, unsigned *restrict pcard,
444                             unsigned *restrict pdevice)
445 {
446     const char *node = udev_device_get_devpath (dev);
447     char type;
448
449     node = strrchr (node, '/');
450     if (node == NULL)
451         return -1;
452     if (sscanf (node, "/pcmC%uD%u%c", pcard, pdevice, &type) < 3)
453         return -1;
454     if (type != 'c')
455         return -1;
456     return 0;
457 }
458
459
460 static char *alsa_get_mrl (struct udev_device *dev)
461 {
462     /* Determine media location */
463     char *mrl;
464     unsigned card, device;
465
466     if (alsa_get_device (dev, &card, &device))
467         return NULL;
468
469     if (asprintf (&mrl, "alsa://plughw:%u,%u", card, device) == -1)
470         mrl = NULL;
471     return mrl;
472 }
473
474 static char *alsa_get_name (struct udev_device *dev)
475 {
476     char *name = NULL;
477     unsigned card, device;
478
479     if (alsa_get_device (dev, &card, &device))
480         return NULL;
481
482     char card_name[4 + 3 * sizeof (int)];
483     snprintf (card_name, sizeof (card_name), "hw:%u", card);
484
485     snd_ctl_t *ctl;
486     if (snd_ctl_open (&ctl, card_name, 0))
487         return NULL;
488
489     snd_pcm_info_t *pcm_info;
490     snd_pcm_info_alloca (&pcm_info);
491     snd_pcm_info_set_device (pcm_info, device);
492     snd_pcm_info_set_subdevice (pcm_info, 0);
493     snd_pcm_info_set_stream (pcm_info, SND_PCM_STREAM_CAPTURE);
494     if (snd_ctl_pcm_info (ctl, pcm_info))
495         goto out;
496
497     name = strdup (snd_pcm_info_get_name (pcm_info));
498 out:
499     snd_ctl_close (ctl);
500     return name;
501 }
502
503 static char *alsa_get_cat (struct udev_device *dev)
504 {
505     const char *vnd;
506
507     dev = udev_device_get_parent (dev);
508     if (dev == NULL)
509         return NULL;
510
511     vnd = udev_device_get_property_value (dev, "ID_VENDOR_FROM_DATABASE");
512     if (vnd == NULL)
513         /* FIXME: USB may take time to settle... the parent device */
514         vnd = udev_device_get_property_value (dev, "ID_BUS");
515     return vnd ? strdup (vnd) : NULL;
516 }
517
518 int OpenALSA (vlc_object_t *obj)
519 {
520     static const struct subsys subsys = {
521         "sound", alsa_get_mrl, alsa_get_name, alsa_get_cat, ITEM_TYPE_CARD,
522     };
523
524     return Open (obj, &subsys);
525 }
526 #endif /* HAVE_ALSA */
527
528
529 /*** Discs support ***/
530 static char *disc_get_mrl (struct udev_device *dev)
531 {
532     const char *val;
533
534     val = udev_device_get_property_value (dev, "ID_CDROM");
535     if (val == NULL)
536         return NULL; /* Ignore non-optical block devices */
537
538     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_STATE");
539     if (val && !strcmp (val, "blank"))
540         return NULL; /* ignore empty drives and virgin recordable discs */
541
542     const char *scheme = NULL;
543     val = udev_device_get_property_value (dev,
544                                           "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
545     if (val && atoi (val))
546         scheme = "cdda"; /* Audio CD rather than file system */
547     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
548     if (val && atoi (val))
549         scheme = "dvd";
550
551     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
552     if (val && atoi (val))
553         scheme = "bd";
554 #ifdef LOL
555     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
556     if (val && atoi (val))
557         scheme = "hddvd";
558 #endif
559
560     /* We didn't get any property that could tell we have optical disc
561        that we can play */
562     if (scheme == NULL)
563         return NULL;
564
565     val = udev_device_get_devnode (dev);
566     return make_URI (val, scheme);
567 }
568
569 static char *disc_get_name (struct udev_device *dev)
570 {
571     return decode_property (dev, "ID_FS_LABEL_ENC");
572 }
573
574 static char *disc_get_cat (struct udev_device *dev)
575 {
576     struct udev_list_entry *list, *entry;
577
578     list = udev_device_get_properties_list_entry (dev);
579     if (unlikely(list == NULL))
580         return NULL;
581
582     const char *cat = NULL;
583     udev_list_entry_foreach (entry, list)
584     {
585         const char *name = udev_list_entry_get_name (entry);
586
587         if (strncmp (name, "ID_CDROM_MEDIA_", 15))
588             continue;
589         if (!atoi (udev_list_entry_get_value (entry)))
590             continue;
591         name += 15;
592
593         if (!strncmp (name, "CD", 2))
594             cat = N_("CD");
595         else if (!strncmp (name, "DVD", 3))
596             cat = N_("DVD");
597         else if (!strncmp (name, "BD", 2))
598             cat = N_("Blu-Ray");
599         else if (!strncmp (name, "HDDVD", 5))
600             cat = N_("HD DVD");
601
602         if (cat != NULL)
603             break;
604     }
605
606     if (cat == NULL)
607         cat = N_("Unknown type");
608     return strdup (vlc_gettext (cat));
609 }
610
611 int OpenDisc (vlc_object_t *obj)
612 {
613     static const struct subsys subsys = {
614         "block", disc_get_mrl, disc_get_name, disc_get_cat, ITEM_TYPE_DISC,
615     };
616
617     return Open (obj, &subsys);
618 }