X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fservices_discovery%2Fudev.c;h=c65c771e85a3c526a84d68a8e1b0b651232886c3;hb=26b5b697bd055842e538438d4a128be113148c6b;hp=5554c0fa684570d7a02f905c3212db80c301e98d;hpb=09dcfc63cacad331bd92965bfbaf0ca12850232f;p=vlc diff --git a/modules/services_discovery/udev.c b/modules/services_discovery/udev.c index 5554c0fa68..c65c771e85 100644 --- a/modules/services_discovery/udev.c +++ b/modules/services_discovery/udev.c @@ -28,51 +28,204 @@ #include #include #include +#include #include #include -static int Open (vlc_object_t *); +static int OpenV4L (vlc_object_t *); +static int OpenALSA (vlc_object_t *); +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 (Open, Close) + set_callbacks (OpenV4L, Close) + add_shortcut ("v4l") + + 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 (OpenALSA, Close) + add_shortcut ("alsa") + + 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); + vlc_sd_probe_Add (probe, "alsa{longname=\"Audio capture\"}", + N_("Audio capture"), SD_CAT_DEVICES); + vlc_sd_probe_Add (probe, "disc{longname=\"Discs\"}", N_("Discs"), + SD_CAT_MYCOMPUTER); + 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); + char * (*get_cat) (struct udev_device *dev); + int item_type; +}; + struct services_discovery_sys_t { + const struct subsys *subsys; struct udev_monitor *monitor; vlc_thread_t thread; - input_item_t **itemv; - size_t itemc; + 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 (VLC_OBJECT (sd), 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; + } + + name = p_sys->subsys->get_cat (dev); + services_discovery_AddItem (sd, item, name ? name : "Generic"); + d->sd = sd; + free (name); + 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->itemv = NULL; - p_sys->itemc = 0; + p_sys->subsys = subsys; + p_sys->root = NULL; struct udev_monitor *mon = NULL; struct udev *udev = udev_new (); @@ -81,7 +234,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; @@ -89,7 +243,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; @@ -106,7 +260,7 @@ 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); @@ -128,7 +282,6 @@ error: return VLC_EGENERIC; } - /** * Releases resources */ @@ -147,13 +300,10 @@ static void Close (vlc_object_t *obj) udev_unref (udev); } - for (size_t i = 0; i < p_sys->itemc; i++) - vlc_gc_decref (p_sys->itemv[i]); - free (p_sys->itemv); + tdestroy (p_sys->root, DestroyDevice); free (p_sys); } - static void *Run (void *data) { services_discovery_t *sd = data; @@ -169,21 +319,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); - + 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') @@ -195,6 +354,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; @@ -228,6 +390,8 @@ static char *decode_property (struct udev_device *dev, const char *name) return decode (udev_device_get_property_value (dev, name)); } + +/*** Video4Linux support ***/ static bool is_v4l_legacy (struct udev_device *dev) { const char *version; @@ -236,59 +400,180 @@ static bool is_v4l_legacy (struct udev_device *dev) return version && !strcmp (version, "1"); } -static void HandleDevice (services_discovery_t *sd, struct udev_device *dev, - bool add) +static char *v4l_get_mrl (struct udev_device *dev) { - services_discovery_sys_t *p_sys = sd->p_sys; - /* Determine media location */ const char *scheme = "v4l2"; if (is_v4l_legacy (dev)) scheme = "v4l"; const char *node = udev_device_get_devnode (dev); char *mrl; + if (asprintf (&mrl, "%s://%s", scheme, node) == -1) - return; + mrl = NULL; + return mrl; +} - /* Find item in list */ - input_item_t *item = NULL; - size_t i; +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; +} - for (i = 0; i < p_sys->itemc; i++) - { - input_item_t *oitem = p_sys->itemv[i]; - char *omrl = input_item_GetURI (oitem); +static char *v4l_get_cat (struct udev_device *dev) +{ + return decode_property (dev, "ID_VENDOR_ENC"); +} - if (!strcmp (omrl, mrl)) - { - item = oitem; - break; - } - } +int OpenV4L (vlc_object_t *obj) +{ + static const struct subsys subsys = { + "video4linux", v4l_get_mrl, v4l_get_name, v4l_get_cat, ITEM_TYPE_CARD, + }; - /* Add/Remove old item */ - if (add && (item == NULL)) - { - char *vnd = decode_property (dev, "ID_VENDOR_ENC"); - const char *name = udev_device_get_property_value (dev, - "ID_V4L_PRODUCT"); - item = input_item_NewWithType (VLC_OBJECT (sd), mrl, - name ? name : "Unnamed", - 0, NULL, 0, -1, ITEM_TYPE_CARD); - msg_Dbg (sd, "adding %s", mrl); - if (item != NULL) - { - services_discovery_AddItem (sd, item, vnd ? vnd : "Generic"); - TAB_APPEND (p_sys->itemc, p_sys->itemv, item); - } - free (vnd); - } - else if (!add && (item != NULL)) - { - msg_Dbg (sd, "removing %s", mrl); - services_discovery_RemoveItem (sd, item); - vlc_gc_decref (item); - TAB_REMOVE (p_sys->itemc, p_sys->itemv, i); - } - free (mrl); + return Open (obj, &subsys); +} + + +/*** Advanced Linux Sound Architecture support ***/ +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; + unsigned card, device; + + 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; + unsigned card, device; + + if (alsa_get_device (dev, &card, &device)) + return NULL; + + if (asprintf (&name, _("Device %u"), device) == -1) + name = NULL; + return name; +} + +static char *alsa_get_cat (struct udev_device *dev) +{ + char *name; + unsigned card, device; + + if (alsa_get_device (dev, &card, &device)) + return NULL; + + if (asprintf (&name, _("Card %u"), card) == -1) + name = NULL; + return name; +} + +int OpenALSA (vlc_object_t *obj) +{ + static const struct subsys subsys = { + "sound", alsa_get_mrl, alsa_get_name, alsa_get_cat, ITEM_TYPE_CARD, + }; + + return Open (obj, &subsys); +} + + +/*** 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 == NULL) || !strcmp (val, "blank")) + return NULL; /* ignore empty drives and virgin recordable discs */ + + const char *scheme = "file"; + 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 */ +#if 0 /* we can use file:// for DVDs */ + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD"); + if (val && atoi (val)) + scheme = "dvd"; +#endif + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD"); + if (val && atoi (val)) + scheme = "bd"; +#ifdef LOL + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD"); + if (val && atoi (val)) + scheme = "hddvd"; +#endif + + val = udev_device_get_devnode (dev); + char *mrl; + + if (asprintf (&mrl, "%s://%s", scheme, val) == -1) + mrl = NULL; + return mrl; +} + +static char *disc_get_name (struct udev_device *dev) +{ + return decode_property (dev, "ID_FS_LABEL_ENC"); +} + +static char *disc_get_cat (struct udev_device *dev) +{ + const char *val; + const char *cat = "Unknown"; + + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_CD"); + if (val && atoi (val)) + cat = "CD"; + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD"); + if (val && atoi (val)) + cat = "DVD"; + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD"); + if (val && atoi (val)) + cat = "Blue-ray disc"; + val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD"); + if (val && atoi (val)) + cat = "HD DVD"; + + return strdup (cat); +} + +int OpenDisc (vlc_object_t *obj) +{ + static const struct subsys subsys = { + "block", disc_get_mrl, disc_get_name, disc_get_cat, ITEM_TYPE_DISC, + }; + + return Open (obj, &subsys); }