udev: refactor
[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 void Close (vlc_object_t *);
36
37 /*
38  * Module descriptor
39  */
40 vlc_module_begin ()
41     set_shortname (N_("Devices"))
42     set_description (N_("Capture devices"))
43     set_category (CAT_PLAYLIST)
44     set_subcategory (SUBCAT_PLAYLIST_SD)
45     set_capability ("services_discovery", 0)
46     set_callbacks (OpenV4L, Close)
47
48     add_shortcut ("udev")
49 vlc_module_end ()
50
51 struct subsys
52 {
53     const char *name;
54     char * (*get_mrl) (struct udev_device *dev);
55     char * (*get_name) (struct udev_device *dev);
56     char * (*get_cat) (struct udev_device *dev);
57 };
58
59 struct services_discovery_sys_t
60 {
61     const struct subsys *subsys;
62     struct udev_monitor *monitor;
63     vlc_thread_t         thread;
64     input_item_t       **itemv;
65     size_t               itemc;
66 };
67
68 static void *Run (void *);
69 static void HandleDevice (services_discovery_t *, struct udev_device *, bool);
70
71 /**
72  * Probes and initializes.
73  */
74 static int Open (vlc_object_t *obj, const struct subsys *subsys)
75 {
76     services_discovery_t *sd = (services_discovery_t *)obj;
77     services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
78
79     if (p_sys == NULL)
80         return VLC_ENOMEM;
81     sd->p_sys = p_sys;
82     p_sys->subsys = subsys;
83     p_sys->itemv = NULL;
84     p_sys->itemc = 0;
85
86     struct udev_monitor *mon = NULL;
87     struct udev *udev = udev_new ();
88     if (udev == NULL)
89         goto error;
90
91     mon = udev_monitor_new_from_netlink (udev, "udev");
92     if (mon == NULL
93      || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name,
94                                                          NULL))
95         goto error;
96     p_sys->monitor = mon;
97
98     /* Enumerate existing devices */
99     struct udev_enumerate *devenum = udev_enumerate_new (udev);
100     if (devenum == NULL)
101         goto error;
102     if (udev_enumerate_add_match_subsystem (devenum, subsys->name))
103     {
104         udev_enumerate_unref (devenum);
105         goto error;
106     }
107
108     udev_monitor_enable_receiving (mon);
109     /* Note that we enumerate _after_ monitoring is enabled so that we do not
110      * loose device events occuring while we are enumerating. We could still
111      * loose events if the Netlink socket receive buffer overflows. */
112     udev_enumerate_scan_devices (devenum);
113     struct udev_list_entry *devlist = udev_enumerate_get_list_entry (devenum);
114     struct udev_list_entry *deventry;
115     udev_list_entry_foreach (deventry, devlist)
116     {
117         const char *path = udev_list_entry_get_name (deventry);
118         struct udev_device *dev = udev_device_new_from_syspath (udev, path);
119         HandleDevice (sd, dev, true);
120         udev_device_unref (dev);
121     }
122     udev_enumerate_unref (devenum);
123
124     if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
125     {   /* Fallback without thread */
126         udev_monitor_unref (mon);
127         udev_unref (udev);
128         p_sys->monitor = NULL;
129     }
130     return VLC_SUCCESS;
131
132 error:
133     if (mon != NULL)
134         udev_monitor_unref (mon);
135     if (udev != NULL)
136         udev_unref (udev);
137     free (p_sys);
138     return VLC_EGENERIC;
139 }
140
141
142 /**
143  * Releases resources
144  */
145 static void Close (vlc_object_t *obj)
146 {
147     services_discovery_t *sd = (services_discovery_t *)obj;
148     services_discovery_sys_t *p_sys = sd->p_sys;
149
150     if (p_sys->monitor != NULL)
151     {
152         struct udev *udev = udev_monitor_get_udev (p_sys->monitor);
153
154         vlc_cancel (p_sys->thread);
155         vlc_join (p_sys->thread, NULL);
156         udev_monitor_unref (p_sys->monitor);
157         udev_unref (udev);
158     }
159
160     for (size_t i = 0; i < p_sys->itemc; i++)
161         vlc_gc_decref (p_sys->itemv[i]);
162     free (p_sys->itemv);
163     free (p_sys);
164 }
165
166
167 static void *Run (void *data)
168 {
169     services_discovery_t *sd = data;
170     services_discovery_sys_t *p_sys = sd->p_sys;
171     struct udev_monitor *mon = p_sys->monitor;
172
173     int fd = udev_monitor_get_fd (mon);
174     struct pollfd ufd = { .fd = fd, .events = POLLIN, };
175
176     for (;;)
177     {
178         while (poll (&ufd, 1, -1) == -1)
179             if (errno != EINTR)
180                 break;
181
182         struct udev_device *dev = udev_monitor_receive_device (mon);
183         if (dev == NULL)
184             continue;
185
186         /* FIXME: handle change, offline, online */
187         const char *action = udev_device_get_action (dev);
188         if (!strcmp (action, "add"))
189             HandleDevice (sd, dev, true);
190         else if (!strcmp (action, "remove"))
191             HandleDevice (sd, dev, false);
192
193         udev_device_unref (dev);
194     }
195     return NULL;
196 }
197
198 static int hex (char c)
199 {
200     if (c >= '0' && c <= '9')
201         return c - '0';
202     if (c >= 'A' && c <= 'F')
203         return c + 10 - 'A';
204     if (c >= 'a' && c <= 'f')
205         return c + 10 - 'a';
206     return -1;
207 }
208
209 static char *decode (const char *enc)
210 {
211     char *ret = enc ? strdup (enc) : NULL;
212     if (ret == NULL)
213         return NULL;
214
215     char *out = ret;
216     for (const char *in = ret; *in; out++)
217     {
218         int h1, h2;
219
220         if ((in[0] == '\\') && (in[1] == 'x')
221          && ((h1 = hex (in[2])) != -1)
222          && ((h2 = hex (in[3])) != -1))
223         {
224             *out = (h1 << 4) | h2;
225             in += 4;
226         }
227         else
228         {
229             *out = *in;
230             in++;
231         }
232     }
233     *out = 0;
234     return ret;
235 }
236
237 static char *decode_property (struct udev_device *dev, const char *name)
238 {
239     return decode (udev_device_get_property_value (dev, name));
240 }
241
242 static void HandleDevice (services_discovery_t *sd, struct udev_device *dev,
243                           bool add)
244 {
245     services_discovery_sys_t *p_sys = sd->p_sys;
246
247     /* Determine media location */
248     char *mrl = p_sys->subsys->get_mrl (dev);
249     if (mrl == NULL)
250         return;
251
252     /* Find item in list */
253     input_item_t *item = NULL;
254     size_t i;
255
256     for (i = 0; i < p_sys->itemc; i++)
257     {
258         input_item_t *oitem = p_sys->itemv[i];
259         char *omrl = input_item_GetURI (oitem);
260
261         if (!strcmp (omrl, mrl))
262         {
263             item = oitem;
264             break;
265         }
266     }
267
268     /* Add/Remove old item */
269     if (add && (item == NULL))
270     {
271         char *name = p_sys->subsys->get_name (dev);
272         item = input_item_NewWithType (VLC_OBJECT (sd), mrl,
273                                        name ? name : "Unnamed",
274                                        0, NULL, 0, -1, ITEM_TYPE_CARD);
275         free (name);
276         if (item != NULL)
277         {
278             msg_Dbg (sd, "adding %s", mrl);
279             name = p_sys->subsys->get_cat (dev);
280             services_discovery_AddItem (sd, item, name ? name : "Generic");
281             free (name);
282
283             TAB_APPEND (p_sys->itemc, p_sys->itemv, item);
284         }
285     }
286     else if (!add && (item != NULL))
287     {
288         msg_Dbg (sd, "removing %s", mrl);
289         services_discovery_RemoveItem (sd, item);
290         vlc_gc_decref (item);
291         TAB_REMOVE (p_sys->itemc, p_sys->itemv, i);
292     }
293     free (mrl);
294 }
295
296 /*** Video4Linux support ***/
297 static bool is_v4l_legacy (struct udev_device *dev)
298 {
299     const char *version;
300
301     version = udev_device_get_property_value (dev, "ID_V4L_VERSION");
302     return version && !strcmp (version, "1");
303 }
304
305 static char *v4l_get_mrl (struct udev_device *dev)
306 {
307     /* Determine media location */
308     const char *scheme = "v4l2";
309     if (is_v4l_legacy (dev))
310         scheme = "v4l";
311     const char *node = udev_device_get_devnode (dev);
312     char *mrl;
313
314     if (asprintf (&mrl, "%s://%s", scheme, node) == -1)
315         mrl = NULL;
316     return mrl;
317 }
318
319 static char *v4l_get_name (struct udev_device *dev)
320 {
321     const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT");
322     return prd ? strdup (prd) : NULL;
323 }
324
325 static char *v4l_get_cat (struct udev_device *dev)
326 {
327     return decode_property (dev, "ID_VENDOR_ENC");
328 }
329
330 int OpenV4L (vlc_object_t *obj)
331 {
332     static const struct subsys subsys = {
333         "video4linux", v4l_get_mrl, v4l_get_name, v4l_get_cat,
334     };
335
336     return Open (obj, &subsys);
337 }