alsactl: add 'clean' command to remove the application controls

It is handy to remove all card controls created by applications.

This change allows to remove those controls for all cards, selected
card or selected card with a control id filter list like:

   alsactl clean 0 "name='PCM'" "name='Mic Phantom'"

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
Jaroslav Kysela 2021-02-26 19:28:03 +01:00
parent c1f8cc2f07
commit 17b4129e6c
6 changed files with 238 additions and 2 deletions

View file

@ -10,7 +10,7 @@ EXTRA_DIST=alsactl.1 alsactl_init.xml
AM_CFLAGS = -D_GNU_SOURCE
alsactl_SOURCES=alsactl.c state.c lock.c utils.c init_parse.c init_ucm.c \
daemon.c monitor.c
daemon.c monitor.c clean.c
alsactl_CFLAGS=$(AM_CFLAGS) -D__USE_GNU \
-DSYS_ASOUNDRC=\"$(ASOUND_STATE_DIR)/asound.state\" \

View file

@ -39,6 +39,8 @@ rescan, save_and_quit).
\fImonitor\fP is for monitoring the events received from the given
control device.
\fIclean\fP clean the controls created by applications.
If no soundcards are specified, setup for all cards will be saved,
loaded or monitored.

View file

@ -187,6 +187,7 @@ int main(int argc, char *argv[])
char *pidfile = SYS_PIDFILE;
char *cardname, ncardname[16];
char *cmd;
char *const *extra_args;
const char *const *tmp;
int removestate = 0;
int init_fallback = 1; /* new default behavior */
@ -346,6 +347,8 @@ int main(int argc, char *argv[])
}
}
extra_args = argc - optind > 2 ? argv + optind + 2 : NULL;
/* the global system file should be always locked */
if (strcmp(cfgfile, SYS_ASOUNDRC) == 0 && do_lock >= 0)
do_lock = 1;
@ -391,6 +394,8 @@ int main(int argc, char *argv[])
res = state_daemon_kill(pidfile, cardname);
} else if (!strcmp(cmd, "monitor")) {
res = monitor(cardname);
} else if (!strcmp(cmd, "clean")) {
res = clean(cardname, extra_args);
} else {
fprintf(stderr, "alsactl: Unknown command '%s'...\n", cmd);
res = -ENODEV;

View file

@ -40,6 +40,7 @@ int monitor(const char *name);
int state_daemon(const char *file, const char *cardname, int period,
const char *pidfile);
int state_daemon_kill(const char *pidfile, const char *cmd);
int clean(const char *cardname, char *const *extra_args);
/* utils */

228
alsactl/clean.c Normal file
View file

@ -0,0 +1,228 @@
/*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "aconfig.h"
#include "version.h"
#include <getopt.h>
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <alsa/asoundlib.h>
#include "alsactl.h"
static int clean_one_control(snd_ctl_t *handle, snd_ctl_elem_id_t *elem_id,
snd_ctl_elem_id_t **filter)
{
snd_ctl_elem_info_t *info;
char *s;
int err;
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_info_set_id(info, elem_id);
err = snd_ctl_elem_info(handle, info);
if (err < 0) {
s = snd_ctl_ascii_elem_id_get(elem_id);
error("Cannot read control info '%s': %s", s, snd_strerror(err));
free(s);
return err;
}
if (!snd_ctl_elem_info_is_user(info))
return 0;
s = snd_ctl_ascii_elem_id_get(elem_id);
dbg("Application control \"%s\" found.", s);
if (filter) {
for (; *filter; filter++) {
if (snd_ctl_elem_id_compare(elem_id, *filter) == 0)
break;
}
if (*filter == NULL) {
free(s);
return 0;
}
}
err = snd_ctl_elem_remove(handle, elem_id);
if (err < 0) {
error("Cannot remove control '%s': %s", s, snd_strerror(err));
free(s);
return err;
}
dbg("Application control \"%s\" removed.", s);
free(s);
return 0;
}
static void filter_controls_free(snd_ctl_elem_id_t **_filter)
{
snd_ctl_elem_id_t **filter;
for (filter = _filter; filter; filter++)
free(*filter);
free(_filter);
}
static int filter_controls_parse(char *const *controls, snd_ctl_elem_id_t ***_filter)
{
snd_ctl_elem_id_t **filter = NULL;
char *const *c;
char *s;
unsigned int count, idx;
int err;
if (!controls)
goto fin;
for (count = 0, c = controls; *c; c++, count++);
if (count == 0)
goto fin;
filter = calloc(count + 1, sizeof(snd_ctl_elem_id_t *));
if (filter == NULL) {
nomem:
error("No enough memory...");
return -ENOMEM;
}
filter[count] = NULL;
for (idx = 0; idx < count; idx++) {
err = snd_ctl_elem_id_malloc(&filter[idx]);
if (err < 0) {
filter_controls_free(filter);
goto nomem;
}
err = snd_ctl_ascii_elem_id_parse(filter[idx], controls[idx]);
if (err < 0) {
error("Cannot parse id '%s': %s", controls[idx], snd_strerror(err));
filter_controls_free(filter);
return err;
}
s = snd_ctl_ascii_elem_id_get(filter[idx]);
dbg("Add to filter: \"%s\"", s);
free(s);
}
fin:
*_filter = filter;
return 0;
}
static int clean_controls(int cardno, char *const *controls)
{
snd_ctl_t *handle;
snd_ctl_elem_list_t *list;
snd_ctl_elem_id_t *elem_id;
snd_ctl_elem_id_t **filter;
char name[32];
unsigned int idx, count;
int err;
snd_ctl_elem_id_alloca(&elem_id);
snd_ctl_elem_list_alloca(&list);
err = filter_controls_parse(controls, &filter);
if (err < 0)
return err;
sprintf(name, "hw:%d", cardno);
err = snd_ctl_open(&handle, name, 0);
if (err < 0) {
error("snd_ctl_open error: %s", snd_strerror(err));
filter_controls_free(filter);
return err;
}
dbg("Control device '%s' opened.", name);
err = snd_ctl_elem_list(handle, list);
if (err < 0) {
error("Cannot determine controls: %s", snd_strerror(err));
goto fin_err;
}
count = snd_ctl_elem_list_get_count(list);
if (count == 0)
goto fin_ok;
snd_ctl_elem_list_set_offset(list, 0);
if ((err = snd_ctl_elem_list_alloc_space(list, count)) < 0) {
error("No enough memory...");
goto fin_err;
}
if ((err = snd_ctl_elem_list(handle, list)) < 0) {
error("Cannot determine controls (2): %s", snd_strerror(err));
goto fin_err;
}
for (idx = 0; idx < count; idx++) {
snd_ctl_elem_list_get_id(list, idx, elem_id);
err = clean_one_control(handle, elem_id, filter);
if (err < 0)
goto fin_err;
}
fin_ok:
filter_controls_free(filter);
snd_ctl_close(handle);
return 0;
fin_err:
filter_controls_free(filter);
snd_ctl_close(handle);
return err;
}
int clean(const char *cardname, char *const *extra_args)
{
int err;
if (!cardname) {
int card, first = 1;
card = -1;
/* find each installed soundcards */
while (1) {
if (snd_card_next(&card) < 0)
break;
if (card < 0) {
if (first) {
if (ignore_nocards) {
err = 0;
goto out;
} else {
error("No soundcards found...");
err = -ENODEV;
goto out;
}
}
break;
}
first = 0;
if ((err = clean_controls(card, extra_args)))
goto out;
}
} else {
int cardno;
cardno = snd_card_get_index(cardname);
if (cardno < 0) {
error("Cannot find soundcard '%s'...", cardname);
err = cardno;
goto out;
}
if ((err = clean_controls(cardno, extra_args))) {
goto out;
}
}
out:
return err;
}

View file

@ -19,7 +19,7 @@ AC_PROG_MKDIR_P
AC_PROG_LN_S
AC_PROG_SED
PKG_PROG_PKG_CONFIG
AM_PATH_ALSA(1.0.27)
AM_PATH_ALSA(1.2.5)
if test "x$enable_alsatest" = "xyes"; then
AC_CHECK_FUNC([snd_ctl_elem_add_enumerated],
, [AC_ERROR([No user enum control support in alsa-lib])])