X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fservices_discovery%2Fudev.c;h=8dbc2b354bdba615e867a0c49fc7bfe675567e91;hb=272f2282872a605d3ba897aa0ff9576bf89e8165;hp=14c1703480eff0411b172481e003b304c0ec8c05;hpb=020c20ecbb40fa7722cfaa121079ae17891809fd;p=vlc diff --git a/modules/services_discovery/udev.c b/modules/services_discovery/udev.c index 14c1703480..8dbc2b354b 100644 --- a/modules/services_discovery/udev.c +++ b/modules/services_discovery/udev.c @@ -5,70 +5,234 @@ /***************************************************************************** * Copyright © 2009 Rémi Denis-Courmont * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. * - * This library is distributed in the hope that it will be useful, + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - ****************************************************************************/ + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ #ifdef HAVE_CONFIG_H # include #endif +#include +#include +#include + #include + #include #include #include -#include -#include +#ifdef HAVE_ALSA +# include +#endif +#include -static int Open (vlc_object_t *); +static int OpenV4L (vlc_object_t *); +#ifdef HAVE_ALSA +static int OpenALSA (vlc_object_t *); +#endif +static int OpenDisc (vlc_object_t *); static void Close (vlc_object_t *); +static int vlc_sd_probe_Open (vlc_object_t *); /* * Module descriptor */ vlc_module_begin () - set_shortname (N_("Devices")) - set_description (N_("Capture devices")) + set_shortname (N_("Video capture")) + set_description (N_("Video capture (Video4Linux)")) + set_category (CAT_PLAYLIST) + set_subcategory (SUBCAT_PLAYLIST_SD) + set_capability ("services_discovery", 0) + set_callbacks (OpenV4L, Close) + add_shortcut ("v4l", "video") +#ifdef HAVE_ALSA + add_submodule () + set_shortname (N_("Audio capture")) + set_description (N_("Audio capture (ALSA)")) set_category (CAT_PLAYLIST) set_subcategory (SUBCAT_PLAYLIST_SD) set_capability ("services_discovery", 0) - set_callbacks (Open, Close) + set_callbacks (OpenALSA, Close) + add_shortcut ("alsa", "audio") +#endif + add_submodule () + set_shortname (N_("Discs")) + set_description (N_("Discs")) + set_category (CAT_PLAYLIST) + set_subcategory (SUBCAT_PLAYLIST_SD) + set_capability ("services_discovery", 0) + set_callbacks (OpenDisc, Close) + add_shortcut ("disc") + + VLC_SD_PROBE_SUBMODULE - add_shortcut ("udev") vlc_module_end () +static int vlc_sd_probe_Open (vlc_object_t *obj) +{ + vlc_probe_t *probe = (vlc_probe_t *)obj; + + struct udev *udev = udev_new (); + if (udev == NULL) + return VLC_PROBE_CONTINUE; + + struct udev_monitor *mon = udev_monitor_new_from_netlink (udev, "udev"); + if (mon != NULL) + { + vlc_sd_probe_Add (probe, "v4l{longname=\"Video capture\"}", + N_("Video capture"), SD_CAT_DEVICES); +#ifdef HAVE_ALSA + if (!module_exists ("pulselist")) + vlc_sd_probe_Add (probe, "alsa{longname=\"Audio capture\"}", + N_("Audio capture"), SD_CAT_DEVICES); +#endif + vlc_sd_probe_Add (probe, "disc{longname=\"Discs\"}", N_("Discs"), + SD_CAT_DEVICES); + udev_monitor_unref (mon); + } + udev_unref (udev); + return VLC_PROBE_CONTINUE; +} + +struct device +{ + dev_t devnum; /* must be first */ + input_item_t *item; + services_discovery_t *sd; +}; + +struct subsys +{ + const char *name; + char * (*get_mrl) (struct udev_device *dev); + char * (*get_name) (struct udev_device *dev); + int item_type; +}; + struct services_discovery_sys_t { + const struct subsys *subsys; struct udev_monitor *monitor; vlc_thread_t thread; + void *root; }; +/** + * Compares two devices (to support binary search). + */ +static int cmpdev (const void *a, const void *b) +{ + const dev_t *da = a, *db = b; + dev_t delta = *da - *db; + + if (sizeof (delta) > sizeof (int)) + return delta ? ((delta > 0) ? 1 : -1) : 0; + return (signed)delta; +} + +static void DestroyDevice (void *data) +{ + struct device *d = data; + + if (d->sd) + services_discovery_RemoveItem (d->sd, d->item); + vlc_gc_decref (d->item); + free (d); +} + +static char *decode_property (struct udev_device *, const char *); + +/** + * Adds a udev device. + */ +static int AddDevice (services_discovery_t *sd, struct udev_device *dev) +{ + services_discovery_sys_t *p_sys = sd->p_sys; + + char *mrl = p_sys->subsys->get_mrl (dev); + if (mrl == NULL) + return 0; /* don't know if it was an error... */ + char *name = p_sys->subsys->get_name (dev); + input_item_t *item = input_item_NewWithType (mrl, name ? name : mrl, + 0, NULL, 0, -1, + p_sys->subsys->item_type); + msg_Dbg (sd, "adding %s (%s)", mrl, name); + free (name); + free (mrl); + if (item == NULL) + return -1; + + struct device *d = malloc (sizeof (*d)); + if (d == NULL) + { + vlc_gc_decref (item); + return -1; + } + d->devnum = udev_device_get_devnum (dev); + d->item = item; + d->sd = NULL; + + struct device **dp = tsearch (d, &p_sys->root, cmpdev); + if (dp == NULL) /* Out-of-memory */ + { + DestroyDevice (d); + return -1; + } + if (*dp != d) /* Overwrite existing device */ + { + DestroyDevice (*dp); + *dp = d; + } + + services_discovery_AddItem (sd, item, NULL); + d->sd = sd; + return 0; +} + +/** + * Removes a udev device (if present). + */ +static void RemoveDevice (services_discovery_t *sd, struct udev_device *dev) +{ + services_discovery_sys_t *p_sys = sd->p_sys; + + dev_t num = udev_device_get_devnum (dev); + struct device **dp = tfind (&(dev_t){ num }, &p_sys->root, cmpdev); + if (dp == NULL) + return; + + struct device *d = *dp; + tdelete (d, &p_sys->root, cmpdev); + DestroyDevice (d); +} + static void *Run (void *); -static void HandleDevice (services_discovery_t *, struct udev_device *, bool); /** * Probes and initializes. */ -static int Open (vlc_object_t *obj) +static int Open (vlc_object_t *obj, const struct subsys *subsys) { - const char subsys[] = "video4linux"; services_discovery_t *sd = (services_discovery_t *)obj; services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys)); if (p_sys == NULL) return VLC_ENOMEM; sd->p_sys = p_sys; + p_sys->subsys = subsys; + p_sys->root = NULL; struct udev_monitor *mon = NULL; struct udev *udev = udev_new (); @@ -77,7 +241,8 @@ static int Open (vlc_object_t *obj) mon = udev_monitor_new_from_netlink (udev, "udev"); if (mon == NULL - || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys, NULL)) + || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name, + NULL)) goto error; p_sys->monitor = mon; @@ -85,7 +250,7 @@ static int Open (vlc_object_t *obj) struct udev_enumerate *devenum = udev_enumerate_new (udev); if (devenum == NULL) goto error; - if (udev_enumerate_add_match_subsystem (devenum, subsys)) + if (udev_enumerate_add_match_subsystem (devenum, subsys->name)) { udev_enumerate_unref (devenum); goto error; @@ -102,13 +267,17 @@ static int Open (vlc_object_t *obj) { const char *path = udev_list_entry_get_name (deventry); struct udev_device *dev = udev_device_new_from_syspath (udev, path); - HandleDevice (sd, dev, true); + AddDevice (sd, dev); udev_device_unref (dev); } udev_enumerate_unref (devenum); if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW)) - goto error; + { /* Fallback without thread */ + udev_monitor_unref (mon); + udev_unref (udev); + p_sys->monitor = NULL; + } return VLC_SUCCESS; error: @@ -120,7 +289,6 @@ error: return VLC_EGENERIC; } - /** * Releases resources */ @@ -128,16 +296,21 @@ static void Close (vlc_object_t *obj) { services_discovery_t *sd = (services_discovery_t *)obj; services_discovery_sys_t *p_sys = sd->p_sys; - struct udev *udev = udev_monitor_get_udev (p_sys->monitor); - vlc_cancel (p_sys->thread); - vlc_join (p_sys->thread, NULL); - udev_monitor_unref (p_sys->monitor); - udev_unref (udev); + if (p_sys->monitor != NULL) + { + struct udev *udev = udev_monitor_get_udev (p_sys->monitor); + + vlc_cancel (p_sys->thread); + vlc_join (p_sys->thread, NULL); + udev_monitor_unref (p_sys->monitor); + udev_unref (udev); + } + + tdestroy (p_sys->root, DestroyDevice); free (p_sys); } - static void *Run (void *data) { services_discovery_t *sd = data; @@ -153,21 +326,30 @@ static void *Run (void *data) if (errno != EINTR) break; + int canc = vlc_savecancel (); struct udev_device *dev = udev_monitor_receive_device (mon); if (dev == NULL) continue; - /* FIXME: handle change, offline, online */ - if (!strcmp (udev_device_get_action (dev), "add")) - HandleDevice (sd, dev, true); - else if (!strcmp (udev_device_get_action (dev), "remove")) - HandleDevice (sd, dev, false); - - //udev_device_unref (dev); + const char *action = udev_device_get_action (dev); + if (!strcmp (action, "add")) + AddDevice (sd, dev); + else if (!strcmp (action, "remove")) + RemoveDevice (sd, dev); + else if (!strcmp (action, "change")) + { + RemoveDevice (sd, dev); + AddDevice (sd, dev); + } + udev_device_unref (dev); + vlc_restorecancel (canc); } return NULL; } +/** + * Converts an hexadecimal digit to an integer. + */ static int hex (char c) { if (c >= '0' && c <= '9') @@ -179,6 +361,9 @@ static int hex (char c) return -1; } +/** + * Decodes a udev hexadecimal-encoded property. + */ static char *decode (const char *enc) { char *ret = enc ? strdup (enc) : NULL; @@ -212,39 +397,220 @@ static char *decode_property (struct udev_device *dev, const char *name) return decode (udev_device_get_property_value (dev, name)); } -static void HandleDevice (services_discovery_t *sd, struct udev_device *dev, - bool add) + +/*** Video4Linux support ***/ +static bool v4l_is_legacy (struct udev_device *dev) { - //services_discovery_sys_t *p_sys = sd->p_sys; - if (!add) - { - msg_Err (sd, "FIXME: removing device not implemented!"); - return; - } + const char *version; + + version = udev_device_get_property_value (dev, "ID_V4L_VERSION"); + return (version != NULL) && !strcmp (version, "1"); +} + +static bool v4l_can_capture (struct udev_device *dev) +{ + const char *caps; + + caps = udev_device_get_property_value (dev, "ID_V4L_CAPABILITIES"); + return (caps != NULL) && (strstr (caps, ":capture:") != NULL); +} + +static char *v4l_get_mrl (struct udev_device *dev) +{ + /* Determine media location */ + if (v4l_is_legacy (dev) || !v4l_can_capture (dev)) + return NULL; - const char *scheme = "v4l2"; /* FIXME: V4L v1 */ const char *node = udev_device_get_devnode (dev); - char *vnd = decode_property (dev, "ID_VENDOR_ENC"); - char *name = decode_property (dev, "ID_MODEL_ENC"); + char *mrl; + if (asprintf (&mrl, "v4l2://%s", node) == -1) + mrl = NULL; + return mrl; +} + +static char *v4l_get_name (struct udev_device *dev) +{ + const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT"); + return prd ? strdup (prd) : NULL; +} + +int OpenV4L (vlc_object_t *obj) +{ + static const struct subsys subsys = { + "video4linux", v4l_get_mrl, v4l_get_name, ITEM_TYPE_CARD, + }; + + return Open (obj, &subsys); +} + + +#ifdef HAVE_ALSA +/*** Advanced Linux Sound Architecture support ***/ +#include + +static int alsa_get_device (struct udev_device *dev, unsigned *restrict pcard, + unsigned *restrict pdevice) +{ + const char *node = udev_device_get_devpath (dev); + char type; + + node = strrchr (node, '/'); + if (node == NULL) + return -1; + if (sscanf (node, "/pcmC%uD%u%c", pcard, pdevice, &type) < 3) + return -1; + if (type != 'c') + return -1; + return 0; +} + + +static char *alsa_get_mrl (struct udev_device *dev) +{ + /* Determine media location */ char *mrl; - if (asprintf (&mrl, "%s://%s", scheme, node) == -1) - return; + unsigned card, device; - /* FIXME: check for duplicates (race between monitor starting to receive - * and initial enumeration). */ - input_item_t *item; - item = input_item_NewWithType (VLC_OBJECT (sd), mrl, - name ? name : "Unnamed", - 0, NULL, 0, -1, ITEM_TYPE_CARD); - msg_Dbg (sd, "adding %s", mrl); - free (name); - free (mrl); + if (alsa_get_device (dev, &card, &device)) + return NULL; + + if (asprintf (&mrl, "alsa://plughw:%u,%u", card, device) == -1) + mrl = NULL; + return mrl; +} + +static char *alsa_get_name (struct udev_device *dev) +{ + char *name = NULL; + unsigned card, device; + + if (alsa_get_device (dev, &card, &device)) + return NULL; + + char card_name[4 + 3 * sizeof (int)]; + snprintf (card_name, sizeof (card_name), "hw:%u", card); + + snd_ctl_t *ctl; + if (snd_ctl_open (&ctl, card_name, 0)) + return NULL; + + snd_pcm_info_t *pcm_info; + snd_pcm_info_alloca (&pcm_info); + snd_pcm_info_set_device (pcm_info, device); + snd_pcm_info_set_subdevice (pcm_info, 0); + snd_pcm_info_set_stream (pcm_info, SND_PCM_STREAM_CAPTURE); + if (snd_ctl_pcm_info (ctl, pcm_info)) + goto out; + + name = strdup (snd_pcm_info_get_name (pcm_info)); +out: + snd_ctl_close (ctl); + return name; +} + +int OpenALSA (vlc_object_t *obj) +{ + static const struct subsys subsys = { + "sound", alsa_get_mrl, alsa_get_name, ITEM_TYPE_CARD, + }; + + return Open (obj, &subsys); +} +#endif /* HAVE_ALSA */ + + +/*** Discs support ***/ +static char *disc_get_mrl (struct udev_device *dev) +{ + const char *val; + + val = udev_device_get_property_value (dev, "ID_CDROM"); + if (val == NULL) + return NULL; /* Ignore non-optical block devices */ + + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_STATE"); + if (val && !strcmp (val, "blank")) + return NULL; /* ignore empty drives and virgin recordable discs */ + + const char *scheme = NULL; + val = udev_device_get_property_value (dev, + "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO"); + if (val && atoi (val)) + scheme = "cdda"; /* Audio CD rather than file system */ + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD"); + if (val && atoi (val)) + scheme = "dvd"; + + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD"); + if (val && atoi (val)) + scheme = "bluray"; +#ifdef LOL + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD"); + if (val && atoi (val)) + scheme = "hddvd"; +#endif + + /* We didn't get any property that could tell we have optical disc + that we can play */ + if (scheme == NULL) + return NULL; - if (item != NULL) + val = udev_device_get_devnode (dev); + return vlc_path2uri (val, scheme); +} + +static char *disc_get_name (struct udev_device *dev) +{ + char *name = NULL; + struct udev_list_entry *list, *entry; + + list = udev_device_get_properties_list_entry (dev); + if (unlikely(list == NULL)) + return NULL; + + const char *cat = NULL; + udev_list_entry_foreach (entry, list) { - services_discovery_AddItem (sd, item, vnd ? vnd : "Generic"); - vlc_gc_decref (item); + const char *name = udev_list_entry_get_name (entry); + + if (strncmp (name, "ID_CDROM_MEDIA_", 15)) + continue; + if (!atoi (udev_list_entry_get_value (entry))) + continue; + name += 15; + + if (!strncmp (name, "CD", 2)) + cat = N_("CD"); + else if (!strncmp (name, "DVD", 3)) + cat = N_("DVD"); + else if (!strncmp (name, "BD", 2)) + cat = N_("Blu-Ray"); + else if (!strncmp (name, "HDDVD", 5)) + cat = N_("HD DVD"); + + if (cat != NULL) + break; } - free (vnd); + + if (cat == NULL) + cat = N_("Unknown type"); + + char *label = decode_property (dev, "ID_FS_LABEL_ENC"); + + if (label) + if (asprintf(&name, "%s (%s)", label, vlc_gettext(cat)) < 0) + name = NULL; + free(label); + + return name; +} + +int OpenDisc (vlc_object_t *obj) +{ + static const struct subsys subsys = { + "block", disc_get_mrl, disc_get_name, ITEM_TYPE_DISC, + }; + + return Open (obj, &subsys); }