udev: partial support for optical discs
[vlc.git] / 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 <poll.h>
32 #include <errno.h>
33
34 static int OpenV4L (vlc_object_t *);
35 static int OpenDisc (vlc_object_t *);
36 static void Close (vlc_object_t *);
37
38 /*
39  * Module descriptor
40  */
41 vlc_module_begin ()
42     set_shortname (N_("Devices"))
43     set_description (N_("Capture devices"))
44     set_category (CAT_PLAYLIST)
45     set_subcategory (SUBCAT_PLAYLIST_SD)
46     set_capability ("services_discovery", 0)
47     set_callbacks (OpenV4L, Close)
48     add_shortcut ("v4l")
49
50     add_submodule ()
51     set_shortname (N_("Discs"))
52     set_description (N_("Discs"))
53     set_category (CAT_PLAYLIST)
54     set_subcategory (SUBCAT_PLAYLIST_SD)
55     set_capability ("services_discovery", 0)
56     set_callbacks (OpenDisc, Close)
57     add_shortcut ("disc")
58
59 vlc_module_end ()
60
61 struct subsys
62 {
63     const char *name;
64     char * (*get_mrl) (struct udev_device *dev);
65     char * (*get_name) (struct udev_device *dev);
66     char * (*get_cat) (struct udev_device *dev);
67 };
68
69 struct services_discovery_sys_t
70 {
71     const struct subsys *subsys;
72     struct udev_monitor *monitor;
73     vlc_thread_t         thread;
74     input_item_t       **itemv;
75     size_t               itemc;
76 };
77
78 static void *Run (void *);
79 static void HandleDevice (services_discovery_t *, struct udev_device *, bool);
80
81 /**
82  * Probes and initializes.
83  */
84 static int Open (vlc_object_t *obj, const struct subsys *subsys)
85 {
86     services_discovery_t *sd = (services_discovery_t *)obj;
87     services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
88
89     if (p_sys == NULL)
90         return VLC_ENOMEM;
91     sd->p_sys = p_sys;
92     p_sys->subsys = subsys;
93     p_sys->itemv = NULL;
94     p_sys->itemc = 0;
95
96     struct udev_monitor *mon = NULL;
97     struct udev *udev = udev_new ();
98     if (udev == NULL)
99         goto error;
100
101     mon = udev_monitor_new_from_netlink (udev, "udev");
102     if (mon == NULL
103      || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name,
104                                                          NULL))
105         goto error;
106     p_sys->monitor = mon;
107
108     /* Enumerate existing devices */
109     struct udev_enumerate *devenum = udev_enumerate_new (udev);
110     if (devenum == NULL)
111         goto error;
112     if (udev_enumerate_add_match_subsystem (devenum, subsys->name))
113     {
114         udev_enumerate_unref (devenum);
115         goto error;
116     }
117
118     udev_monitor_enable_receiving (mon);
119     /* Note that we enumerate _after_ monitoring is enabled so that we do not
120      * loose device events occuring while we are enumerating. We could still
121      * loose events if the Netlink socket receive buffer overflows. */
122     udev_enumerate_scan_devices (devenum);
123     struct udev_list_entry *devlist = udev_enumerate_get_list_entry (devenum);
124     struct udev_list_entry *deventry;
125     udev_list_entry_foreach (deventry, devlist)
126     {
127         const char *path = udev_list_entry_get_name (deventry);
128         struct udev_device *dev = udev_device_new_from_syspath (udev, path);
129         HandleDevice (sd, dev, true);
130         udev_device_unref (dev);
131     }
132     udev_enumerate_unref (devenum);
133
134     if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
135     {   /* Fallback without thread */
136         udev_monitor_unref (mon);
137         udev_unref (udev);
138         p_sys->monitor = NULL;
139     }
140     return VLC_SUCCESS;
141
142 error:
143     if (mon != NULL)
144         udev_monitor_unref (mon);
145     if (udev != NULL)
146         udev_unref (udev);
147     free (p_sys);
148     return VLC_EGENERIC;
149 }
150
151
152 /**
153  * Releases resources
154  */
155 static void Close (vlc_object_t *obj)
156 {
157     services_discovery_t *sd = (services_discovery_t *)obj;
158     services_discovery_sys_t *p_sys = sd->p_sys;
159
160     if (p_sys->monitor != NULL)
161     {
162         struct udev *udev = udev_monitor_get_udev (p_sys->monitor);
163
164         vlc_cancel (p_sys->thread);
165         vlc_join (p_sys->thread, NULL);
166         udev_monitor_unref (p_sys->monitor);
167         udev_unref (udev);
168     }
169
170     for (size_t i = 0; i < p_sys->itemc; i++)
171         vlc_gc_decref (p_sys->itemv[i]);
172     free (p_sys->itemv);
173     free (p_sys);
174 }
175
176
177 static void *Run (void *data)
178 {
179     services_discovery_t *sd = data;
180     services_discovery_sys_t *p_sys = sd->p_sys;
181     struct udev_monitor *mon = p_sys->monitor;
182
183     int fd = udev_monitor_get_fd (mon);
184     struct pollfd ufd = { .fd = fd, .events = POLLIN, };
185
186     for (;;)
187     {
188         while (poll (&ufd, 1, -1) == -1)
189             if (errno != EINTR)
190                 break;
191
192         struct udev_device *dev = udev_monitor_receive_device (mon);
193         if (dev == NULL)
194             continue;
195
196         /* FIXME: handle change, offline, online */
197         const char *action = udev_device_get_action (dev);
198         if (!strcmp (action, "add"))
199             HandleDevice (sd, dev, true);
200         else if (!strcmp (action, "remove"))
201             HandleDevice (sd, dev, false);
202
203         udev_device_unref (dev);
204     }
205     return NULL;
206 }
207
208 static int hex (char c)
209 {
210     if (c >= '0' && c <= '9')
211         return c - '0';
212     if (c >= 'A' && c <= 'F')
213         return c + 10 - 'A';
214     if (c >= 'a' && c <= 'f')
215         return c + 10 - 'a';
216     return -1;
217 }
218
219 static char *decode (const char *enc)
220 {
221     char *ret = enc ? strdup (enc) : NULL;
222     if (ret == NULL)
223         return NULL;
224
225     char *out = ret;
226     for (const char *in = ret; *in; out++)
227     {
228         int h1, h2;
229
230         if ((in[0] == '\\') && (in[1] == 'x')
231          && ((h1 = hex (in[2])) != -1)
232          && ((h2 = hex (in[3])) != -1))
233         {
234             *out = (h1 << 4) | h2;
235             in += 4;
236         }
237         else
238         {
239             *out = *in;
240             in++;
241         }
242     }
243     *out = 0;
244     return ret;
245 }
246
247 static char *decode_property (struct udev_device *dev, const char *name)
248 {
249     return decode (udev_device_get_property_value (dev, name));
250 }
251
252 static void HandleDevice (services_discovery_t *sd, struct udev_device *dev,
253                           bool add)
254 {
255     services_discovery_sys_t *p_sys = sd->p_sys;
256
257     /* Determine media location */
258     char *mrl = p_sys->subsys->get_mrl (dev);
259     if (mrl == NULL)
260         return;
261
262     /* Find item in list */
263     input_item_t *item = NULL;
264     size_t i;
265
266     for (i = 0; i < p_sys->itemc; i++)
267     {
268         input_item_t *oitem = p_sys->itemv[i];
269         char *omrl = input_item_GetURI (oitem);
270
271         if (!strcmp (omrl, mrl))
272         {
273             item = oitem;
274             break;
275         }
276     }
277
278     /* Add/Remove old item */
279     if (add && (item == NULL))
280     {
281         char *name = p_sys->subsys->get_name (dev);
282         item = input_item_NewWithType (VLC_OBJECT (sd), mrl,
283                                        name ? name : "Unnamed",
284                                        0, NULL, 0, -1, ITEM_TYPE_CARD);
285         free (name);
286         if (item != NULL)
287         {
288             msg_Dbg (sd, "adding %s", mrl);
289             name = p_sys->subsys->get_cat (dev);
290             services_discovery_AddItem (sd, item, name ? name : "Generic");
291             free (name);
292
293             TAB_APPEND (p_sys->itemc, p_sys->itemv, item);
294         }
295     }
296     else if (!add && (item != NULL))
297     {
298         msg_Dbg (sd, "removing %s", mrl);
299         services_discovery_RemoveItem (sd, item);
300         vlc_gc_decref (item);
301         TAB_REMOVE (p_sys->itemc, p_sys->itemv, i);
302     }
303     free (mrl);
304 }
305
306 /*** Video4Linux support ***/
307 static bool is_v4l_legacy (struct udev_device *dev)
308 {
309     const char *version;
310
311     version = udev_device_get_property_value (dev, "ID_V4L_VERSION");
312     return version && !strcmp (version, "1");
313 }
314
315 static char *v4l_get_mrl (struct udev_device *dev)
316 {
317     /* Determine media location */
318     const char *scheme = "v4l2";
319     if (is_v4l_legacy (dev))
320         scheme = "v4l";
321     const char *node = udev_device_get_devnode (dev);
322     char *mrl;
323
324     if (asprintf (&mrl, "%s://%s", scheme, node) == -1)
325         mrl = NULL;
326     return mrl;
327 }
328
329 static char *v4l_get_name (struct udev_device *dev)
330 {
331     const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT");
332     return prd ? strdup (prd) : NULL;
333 }
334
335 static char *v4l_get_cat (struct udev_device *dev)
336 {
337     return decode_property (dev, "ID_VENDOR_ENC");
338 }
339
340 int OpenV4L (vlc_object_t *obj)
341 {
342     static const struct subsys subsys = {
343         "video4linux", v4l_get_mrl, v4l_get_name, v4l_get_cat,
344     };
345
346     return Open (obj, &subsys);
347 }
348
349 /*** Discs support ***/
350 static char *disc_get_mrl (struct udev_device *dev)
351 {
352     const char *val;
353
354     val = udev_device_get_property_value (dev, "ID_CDROM");
355     if (val == NULL)
356         return NULL; /* Ignore non-optical block devices */
357
358     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_STATE");
359     if ((val == NULL) || !strcmp (val, "blank"))
360         return NULL; /* ignore empty drives and virgin recordable discs */
361
362     const char *scheme = "file";
363     val = udev_device_get_property_value (dev,
364                                           "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
365     if (val && atoi (val))
366         scheme = "cdda"; /* Audio CD rather than file system */
367 #if 0 /* we can use file:// for DVDs */
368     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
369     if (val && atoi (val))
370         scheme = "dvd";
371 #endif
372     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
373     if (val && atoi (val))
374         scheme = "bd";
375 #ifdef LOL
376     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
377     if (val && atoi (val))
378         scheme = "hddvd";
379 #endif
380
381     val = udev_device_get_devnode (dev);
382     char *mrl;
383
384     if (asprintf (&mrl, "%s://%s", scheme, val) == -1)
385         mrl = NULL;
386     return mrl;
387 }
388
389 static char *disc_get_name (struct udev_device *dev)
390 {
391     return decode_property (dev, "ID_FS_LABEL_ENC");
392 }
393
394 static char *disc_get_cat (struct udev_device *dev)
395 {
396     const char *val;
397     const char *cat = "Unknown";
398
399     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_CD");
400     if (val && atoi (val))
401         cat = "CD";
402     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
403     if (val && atoi (val))
404         cat = "DVD";
405     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
406     if (val && atoi (val))
407         cat = "Blue-ray disc";
408     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
409     if (val && atoi (val))
410         cat = "HD DVD";
411
412     return strdup (cat);
413 }
414
415 int OpenDisc (vlc_object_t *obj)
416 {
417     static const struct subsys subsys = {
418         "block", disc_get_mrl, disc_get_name, disc_get_cat,
419     };
420
421     return Open (obj, &subsys);
422 }