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