mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-11-10 00:35:42 +01:00
alsactl: handle detection of new sound card
At present, plug-and-play is not supported in a mode of 'monitor', thus new sound card is not handled during runtime. This is not happy. This commit uses Linux-specific inotify(7) to monitor '/dev/snd' directory. When some files are newly added to the directory, event dispatcher is suspended. Event sources are scanned again and the dispatcher continue to run. Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp> Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
parent
440ea7d38d
commit
e57a0a0809
1 changed files with 108 additions and 10 deletions
|
@ -23,6 +23,9 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <time.h>
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
@ -133,6 +136,18 @@ static int open_ctl(const char *name, snd_ctl_t **ctlp)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool seek_entry_by_name(struct list_head *srcs, const char *name)
|
||||||
|
{
|
||||||
|
struct src_entry *entry;
|
||||||
|
|
||||||
|
list_for_each_entry(entry, srcs, list) {
|
||||||
|
if (!strcmp(entry->name, name))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int prepare_source_entry(struct list_head *srcs, const char *name)
|
static int prepare_source_entry(struct list_head *srcs, const char *name)
|
||||||
{
|
{
|
||||||
snd_ctl_t *handle;
|
snd_ctl_t *handle;
|
||||||
|
@ -144,6 +159,8 @@ static int prepare_source_entry(struct list_head *srcs, const char *name)
|
||||||
|
|
||||||
snd_card_iterator_init(&iter);
|
snd_card_iterator_init(&iter);
|
||||||
while ((cardname = snd_card_iterator_next(&iter))) {
|
while ((cardname = snd_card_iterator_next(&iter))) {
|
||||||
|
if (seek_entry_by_name(srcs, cardname))
|
||||||
|
continue;
|
||||||
err = open_ctl(cardname, &handle);
|
err = open_ctl(cardname, &handle);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
|
@ -152,6 +169,8 @@ static int prepare_source_entry(struct list_head *srcs, const char *name)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (seek_entry_by_name(srcs, name))
|
||||||
|
return 0;
|
||||||
err = open_ctl(name, &handle);
|
err = open_ctl(name, &handle);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
|
@ -163,6 +182,41 @@ static int prepare_source_entry(struct list_head *srcs, const char *name)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int check_control_cdev(int infd, bool *retry)
|
||||||
|
{
|
||||||
|
struct inotify_event *ev;
|
||||||
|
char *buf;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
buf = calloc(1, sizeof(*ev) + NAME_MAX);
|
||||||
|
if (!buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
ssize_t len = read(infd, buf, sizeof(*ev) + NAME_MAX);
|
||||||
|
if (len < 0) {
|
||||||
|
if (errno != EAGAIN)
|
||||||
|
err = errno;
|
||||||
|
break;
|
||||||
|
} else if (len == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
|
while (pos < len) {
|
||||||
|
ev = (struct inotify_event *)(buf + pos);
|
||||||
|
if ((ev->mask & IN_CREATE) &&
|
||||||
|
strstr(ev->name, "controlC") == ev->name)
|
||||||
|
*retry = true;
|
||||||
|
pos += sizeof(*ev) + ev->len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
static int print_event(snd_ctl_t *ctl, const char *name)
|
static int print_event(snd_ctl_t *ctl, const char *name)
|
||||||
{
|
{
|
||||||
snd_ctl_event_t *event;
|
snd_ctl_event_t *event;
|
||||||
|
@ -236,13 +290,18 @@ end:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int prepare_dispatcher(int epfd, struct list_head *srcs)
|
static int prepare_dispatcher(int epfd, int infd, struct list_head *srcs)
|
||||||
{
|
{
|
||||||
|
struct epoll_event ev = {0};
|
||||||
struct src_entry *entry;
|
struct src_entry *entry;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
|
ev.events = EPOLLIN;
|
||||||
|
ev.data.fd = infd;
|
||||||
|
if (epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
list_for_each_entry(entry, srcs, list) {
|
list_for_each_entry(entry, srcs, list) {
|
||||||
struct epoll_event ev;
|
|
||||||
ev.events = EPOLLIN;
|
ev.events = EPOLLIN;
|
||||||
ev.data.ptr = (void *)entry;
|
ev.data.ptr = (void *)entry;
|
||||||
err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry);
|
err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry);
|
||||||
|
@ -253,7 +312,8 @@ static int prepare_dispatcher(int epfd, struct list_head *srcs)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int run_dispatcher(int epfd, struct list_head *srcs)
|
static int run_dispatcher(int epfd, int infd, struct list_head *srcs,
|
||||||
|
bool *retry)
|
||||||
{
|
{
|
||||||
struct src_entry *entry;
|
struct src_entry *entry;
|
||||||
unsigned int max_ev_count;
|
unsigned int max_ev_count;
|
||||||
|
@ -282,7 +342,15 @@ static int run_dispatcher(int epfd, struct list_head *srcs)
|
||||||
|
|
||||||
for (i = 0; i < count; ++i) {
|
for (i = 0; i < count; ++i) {
|
||||||
struct epoll_event *ev = epev + i;
|
struct epoll_event *ev = epev + i;
|
||||||
struct src_entry *entry = (struct src_entry *)ev->data.ptr;
|
|
||||||
|
if (ev->data.fd == infd) {
|
||||||
|
err = check_control_cdev(infd, retry);
|
||||||
|
if (err < 0 || *retry)
|
||||||
|
goto end;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = ev->data.ptr;
|
||||||
if (ev->events & EPOLLIN)
|
if (ev->events & EPOLLIN)
|
||||||
print_event(entry->handle, entry->name);
|
print_event(entry->handle, entry->name);
|
||||||
if (ev->events & EPOLLERR) {
|
if (ev->events & EPOLLERR) {
|
||||||
|
@ -290,42 +358,72 @@ static int run_dispatcher(int epfd, struct list_head *srcs)
|
||||||
remove_source_entry(entry);
|
remove_source_entry(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (err < 0)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
end:
|
||||||
free(epev);
|
free(epev);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clear_dispatcher(int epfd, struct list_head *srcs)
|
static void clear_dispatcher(int epfd, int infd, struct list_head *srcs)
|
||||||
{
|
{
|
||||||
struct src_entry *entry;
|
struct src_entry *entry;
|
||||||
|
|
||||||
list_for_each_entry(entry, srcs, list)
|
list_for_each_entry(entry, srcs, list)
|
||||||
operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
|
operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
|
||||||
|
|
||||||
|
epoll_ctl(epfd, EPOLL_CTL_DEL, infd, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int monitor(const char *name)
|
int monitor(const char *name)
|
||||||
{
|
{
|
||||||
LIST_HEAD(srcs);
|
LIST_HEAD(srcs);
|
||||||
int epfd;
|
int epfd;
|
||||||
|
int infd;
|
||||||
|
int wd = 0;
|
||||||
|
bool retry;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
epfd = epoll_create(1);
|
epfd = epoll_create(1);
|
||||||
if (epfd < 0)
|
if (epfd < 0)
|
||||||
return -errno;
|
return -errno;
|
||||||
|
|
||||||
|
infd = inotify_init1(IN_NONBLOCK);
|
||||||
|
if (infd < 0) {
|
||||||
|
err = -errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
wd = inotify_add_watch(infd, "/dev/snd/", IN_CREATE);
|
||||||
|
if (wd < 0) {
|
||||||
|
err = -errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
retry:
|
||||||
|
retry = false;
|
||||||
err = prepare_source_entry(&srcs, name);
|
err = prepare_source_entry(&srcs, name);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = prepare_dispatcher(epfd, &srcs);
|
err = prepare_dispatcher(epfd, infd, &srcs);
|
||||||
if (err >= 0)
|
if (err >= 0)
|
||||||
err = run_dispatcher(epfd, &srcs);
|
err = run_dispatcher(epfd, infd, &srcs, &retry);
|
||||||
clear_dispatcher(epfd, &srcs);
|
clear_dispatcher(epfd, infd, &srcs);
|
||||||
|
|
||||||
|
if (retry) {
|
||||||
|
// A simple makeshift for timing gap between creation of nodes
|
||||||
|
// by devtmpfs and chmod() by udevd.
|
||||||
|
struct timespec req = { .tv_sec = 1 };
|
||||||
|
nanosleep(&req, NULL);
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
error:
|
error:
|
||||||
clear_source_list(&srcs);
|
clear_source_list(&srcs);
|
||||||
|
|
||||||
|
if (wd > 0)
|
||||||
|
inotify_rm_watch(infd, wd);
|
||||||
|
close(infd);
|
||||||
|
|
||||||
close(epfd);
|
close(epfd);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
|
|
Loading…
Reference in a new issue