mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-11-12 21:15:41 +01:00
cc5c3357cf
For the plug-and-play hardware, like USB devices, it may be helpful to manage the sound state periodically, before the devices are removed. This change implements new commands 'daemon' and 'rdaemon' to save the sound state in defined intervals when the sound controls are changed. The udev rules can notify the daemon using the 'kill' or 'nrestore' commands to rescan available cards in the system. Signed-off-by: Jaroslav Kysela <perex@perex.cz>
330 lines
6.8 KiB
C
330 lines
6.8 KiB
C
/*
|
|
* Advanced Linux Sound Architecture Control Program
|
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include "aconfig.h"
|
|
#include "version.h"
|
|
#include <getopt.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
#include <poll.h>
|
|
#include <alsa/asoundlib.h>
|
|
#include "alsactl.h"
|
|
|
|
struct card {
|
|
int index;
|
|
int pfds;
|
|
snd_ctl_t *handle;
|
|
};
|
|
|
|
static int quit = 0;
|
|
static int rescan = 0;
|
|
static int save_now = 0;
|
|
|
|
static void signal_handler_quit(int sig)
|
|
{
|
|
quit = 1;
|
|
signal(sig, signal_handler_quit);
|
|
}
|
|
|
|
static void signal_handler_save_and_quit(int sig)
|
|
{
|
|
quit = save_now = 1;
|
|
signal(sig, signal_handler_quit);
|
|
}
|
|
|
|
static void signal_handler_rescan(int sig)
|
|
{
|
|
rescan = 1;
|
|
signal(sig, signal_handler_rescan);
|
|
}
|
|
|
|
static void card_free(struct card **card)
|
|
{
|
|
struct card *c = *card;
|
|
if (c->handle)
|
|
snd_ctl_close(c->handle);
|
|
free(c);
|
|
*card = NULL;
|
|
}
|
|
|
|
static void add_card(struct card ***cards, int *count, const char *cardname)
|
|
{
|
|
struct card *card, **cc;
|
|
int i, index, findex;
|
|
char device[16];
|
|
|
|
index = snd_card_get_index(cardname);
|
|
if (index < 0)
|
|
return;
|
|
for (i = 0, findex = -1; i < *count; i++) {
|
|
if ((*cards)[i] == NULL) {
|
|
findex = i;
|
|
} else {
|
|
if ((*cards)[i]->index == index)
|
|
return;
|
|
}
|
|
}
|
|
card = calloc(1, sizeof(*card));
|
|
if (card == NULL)
|
|
return;
|
|
card->index = index;
|
|
sprintf(device, "hw:%i", index);
|
|
if (snd_ctl_open(&card->handle, device, SND_CTL_READONLY|SND_CTL_NONBLOCK) < 0) {
|
|
card_free(&card);
|
|
return;
|
|
}
|
|
card->pfds = snd_ctl_poll_descriptors_count(card->handle);
|
|
if (card->pfds < 0) {
|
|
card_free(&card);
|
|
return;
|
|
}
|
|
if (snd_ctl_subscribe_events(card->handle, 1) < 0) {
|
|
card_free(&card);
|
|
return;
|
|
}
|
|
if (findex >= 0) {
|
|
(*cards)[findex] = card;
|
|
} else {
|
|
cc = realloc(*cards, sizeof(void *) * (*count + 1));
|
|
if (cc == NULL) {
|
|
card_free(&card);
|
|
return;
|
|
}
|
|
cc[*count] = card;
|
|
*count = *count + 1;
|
|
*cards = cc;
|
|
}
|
|
}
|
|
|
|
static void add_cards(struct card ***cards, int *count)
|
|
{
|
|
int card = -1;
|
|
char cardname[16];
|
|
|
|
while (1) {
|
|
if (snd_card_next(&card) < 0)
|
|
break;
|
|
if (card < 0)
|
|
break;
|
|
if (card >= 0) {
|
|
sprintf(cardname, "%i", card);
|
|
add_card(cards, count, cardname);
|
|
}
|
|
}
|
|
}
|
|
|
|
int card_events(struct card *card)
|
|
{
|
|
int res = 0;
|
|
snd_ctl_event_t *ev;
|
|
snd_ctl_event_alloca(&ev);
|
|
|
|
while (snd_ctl_read(card->handle, ev) == 1)
|
|
res = 1;
|
|
return res;
|
|
}
|
|
|
|
long read_pid_file(const char *pidfile)
|
|
{
|
|
int fd, err;
|
|
char pid_txt[12];
|
|
|
|
fd = open(pidfile, O_RDONLY);
|
|
if (fd >= 0) {
|
|
err = read(fd, pid_txt, 11);
|
|
if (err != 11)
|
|
err = err < 0 ? -errno : -EIO;
|
|
close(fd);
|
|
pid_txt[11] = '\0';
|
|
return atol(pid_txt);
|
|
} else {
|
|
return -errno;
|
|
}
|
|
}
|
|
|
|
int write_pid_file(const char *pidfile)
|
|
{
|
|
int fd, err;
|
|
char pid_txt[12];
|
|
|
|
sprintf(pid_txt, "%10li\n", (long)getpid());
|
|
fd = open(pidfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
|
|
if (fd >= 0) {
|
|
err = write(fd, pid_txt, 11);
|
|
if (err != 11) {
|
|
err = err < 0 ? -errno : -EIO;
|
|
unlink(pidfile);
|
|
} else {
|
|
err = 0;
|
|
}
|
|
close(fd);
|
|
} else {
|
|
err = -errno;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int state_daemon_kill(const char *pidfile, const char *cmd)
|
|
{
|
|
long pid;
|
|
int sig = SIGHUP;
|
|
|
|
if (cmd == NULL) {
|
|
error("Specify kill command (quit, rescan or save_and_quit)");
|
|
return -EINVAL;
|
|
}
|
|
if (strcmp(cmd, "rescan") == 0)
|
|
sig = SIGUSR1;
|
|
else if (strcmp(cmd, "save_and_quit") == 0)
|
|
sig = SIGUSR2;
|
|
else if (strcmp(cmd, "quit") == 0)
|
|
sig = SIGTERM;
|
|
if (sig == SIGHUP) {
|
|
error("Unknown kill command '%s'", cmd);
|
|
return -EINVAL;
|
|
}
|
|
pid = read_pid_file(pidfile);
|
|
if (pid > 0) {
|
|
if (kill(pid, sig) >= 0)
|
|
return 0;
|
|
return -errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int check_another_instance(const char *pidfile)
|
|
{
|
|
long pid;
|
|
|
|
pid = read_pid_file(pidfile);
|
|
if (pid >= 0) {
|
|
/* invoke new card rescan */
|
|
if (kill(pid, SIGUSR1) >= 0) {
|
|
usleep(1000);
|
|
pid = read_pid_file(pidfile);
|
|
if (pid >= 0)
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int state_daemon(const char *file, const char *cardname, int period,
|
|
const char *pidfile)
|
|
{
|
|
int count = 0, pcount, psize = 0, i, j, k, changed = 0;
|
|
time_t last_write, now;
|
|
unsigned short revents;
|
|
struct card **cards = NULL;
|
|
struct pollfd *pfd = NULL, *pfdn;
|
|
|
|
if (check_another_instance(pidfile))
|
|
return 0;
|
|
rescan = 1;
|
|
signal(SIGABRT, signal_handler_quit);
|
|
signal(SIGTERM, signal_handler_quit);
|
|
signal(SIGINT, signal_handler_quit);
|
|
signal(SIGUSR1, signal_handler_rescan);
|
|
signal(SIGUSR2, signal_handler_save_and_quit);
|
|
write_pid_file(pidfile);
|
|
time(&last_write);
|
|
while (!quit || save_now) {
|
|
if (save_now)
|
|
goto save;
|
|
if (rescan) {
|
|
if (cardname) {
|
|
add_card(&cards, &count, cardname);
|
|
} else {
|
|
add_cards(&cards, &count);
|
|
}
|
|
snd_config_update_free_global();
|
|
rescan = 0;
|
|
}
|
|
for (i = pcount = 0; i < count; i++) {
|
|
if (cards[i] == NULL)
|
|
continue;
|
|
pcount += cards[i]->pfds;
|
|
}
|
|
if (pcount > psize) {
|
|
pfdn = realloc(pfd, sizeof(struct pollfd) * pcount);
|
|
if (pfdn) {
|
|
psize = pcount;
|
|
pfd = pfdn;
|
|
} else {
|
|
error("No enough memory...");
|
|
goto out;
|
|
}
|
|
}
|
|
for (i = j = 0; i < count; i++) {
|
|
if (cards[i] == NULL)
|
|
continue;
|
|
k = snd_ctl_poll_descriptors(cards[i]->handle, pfd + j, pcount - j);
|
|
if (k != cards[i]->pfds) {
|
|
error("poll prepare failed: %i", k);
|
|
goto out;
|
|
}
|
|
j += k;
|
|
}
|
|
i = poll(pfd, j, (period / 2) * 1000);
|
|
if (i < 0 && errno == EINTR)
|
|
continue;
|
|
if (i < 0) {
|
|
error("poll failed: %s", strerror(errno));
|
|
break;
|
|
}
|
|
time(&now);
|
|
for (i = j = 0; i < count; i++) {
|
|
if (cards[i] == NULL)
|
|
continue;
|
|
k = snd_ctl_poll_descriptors_revents(cards[i]->handle,
|
|
pfd + j, cards[i]->pfds, &revents);
|
|
if (k < 0) {
|
|
error("poll post failed: %i\n", k);
|
|
goto out;
|
|
}
|
|
j += cards[i]->pfds;
|
|
if (revents & POLLIN) {
|
|
if (card_events(cards[i])) {
|
|
/* delay the write */
|
|
if (!changed)
|
|
last_write = now;
|
|
changed = 1;
|
|
}
|
|
}
|
|
}
|
|
if ((now - last_write >= period && changed) || save_now) {
|
|
save:
|
|
changed = save_now = 0;
|
|
save_state(file, cardname);
|
|
}
|
|
}
|
|
out:
|
|
free(pfd);
|
|
remove(pidfile);
|
|
for (i = 0; i < count; i++)
|
|
card_free(&cards[i]);
|
|
free(cards);
|
|
return 0;
|
|
}
|