diff options
| author | Mattias Andrée <m@maandree.se> | 2025-12-26 09:48:09 +0100 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2025-12-26 10:05:39 +0100 |
| commit | fad53404f45b2e0daeda733687a41dd8e600aa57 (patch) | |
| tree | f8dbd73a1e59cb8df53c7cb899c7d09ab5d74845 | |
| parent | Fix open_tree support for when missing in libc (diff) | |
| download | galsause-fad53404f45b2e0daeda733687a41dd8e600aa57.tar.gz galsause-fad53404f45b2e0daeda733687a41dd8e600aa57.tar.bz2 galsause-fad53404f45b2e0daeda733687a41dd8e600aa57.tar.xz | |
Signed-off-by: Mattias Andrée <m@maandree.se>
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Makefile | 28 | ||||
| -rw-r--r-- | README | 41 | ||||
| -rw-r--r-- | alsause.c | 330 | ||||
| -rw-r--r-- | config.mk | 6 | ||||
| -rw-r--r-- | galsause.1 (renamed from alsause.1) | 81 | ||||
| -rw-r--r-- | galsause.c | 431 |
7 files changed, 484 insertions, 435 deletions
@@ -12,4 +12,4 @@ *.gcov *.gcno *.gcda -/alsause +/galsause @@ -4,38 +4,34 @@ CONFIGFILE = config.mk include $(CONFIGFILE) OBJ =\ - alsause.o + galsause.o HDR = -all: alsause +all: galsause $(OBJ): $(HDR) .c.o: - $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) $(CFLAGS_GTK2) -D'ALSAUSE_PATH="$(ALSAUSE_PATH)"' -alsause: $(OBJ) - $(CC) -o $@ $(OBJ) $(LDFLAGS) +galsause: $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) $(LDFLAGS_GTK2) -install: alsause +install: galsause mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" - cp -- alsause "$(DESTDIR)$(PREFIX)/bin/" - cp -- alsause.1 "$(DESTDIR)$(MANPREFIX)/man1/" - -post-install: - chown -- '0:0' "$(DESTDIR)$(PREFIX)/bin/alsause" - chmod -- 4755 "$(DESTDIR)$(PREFIX)/bin/alsause" + cp -- galsause "$(DESTDIR)$(PREFIX)/bin/" + cp -- galsause.1 "$(DESTDIR)$(MANPREFIX)/man1/" uninstall: - -rm -f -- "$(DESTDIR)$(PREFIX)/bin/alsause" - -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/alsause.1" + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/galsause" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/galsause.1" clean: -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda - -rm -f -- alsause + -rm -f -- galsause .SUFFIXES: .SUFFIXES: .o .c -.PHONY: all install post-install uninstall clean +.PHONY: all install uninstall clean @@ -1,39 +1,24 @@ NAME - alsause - Set default ALSA PCM device + galsause - Set default ALSA PCM device SYNPOSIS - alsause [(-r | name | card:device) [command [argument] ...]] + galsause [command [argument] ...] DESCRIPTION - The alsause utility lists all ALSA PCM devices or sets, either for the - user or for a program it starts, the default ALSA PCM device. + The galsause utility is a graphical utility for setting the default + ALSA PCM device for the user or a program it starts. - No options or operands are specified, alsause will list all ALSA PCM - devices, otherwise it will set the default ALSA PCM device. - - If command is specified, alsause will run that command with the default + If command is specified, galsause will run that command with the default ALSA PCM device set only for that process and it's child processes. OPTIONS - The alsause utility conforms to the Base Definitions volume of + The galsause utility conforms to the Base Definitions volume of POSIX.1-2017, Section 12.2, Utility Syntax Guidelines. - The following option is supported: - - -r Remove the user-specified default ALSA PCM device, falling back - to the system-wide default ALSA PCM device. + No options are supported. OPERANDS - The following operands shall be supported: - - card:device - The card number and device number to use as the default ALSA PCM - device. - - name - The card and device, by name, to use as the default ALSA PCM - device. This string is on the form output by the alsause - utility itself: "%s - %s", <card name>, <device name>. + The following operands are supported: command The name of the utility to be invoked. @@ -42,17 +27,17 @@ OPERANDS A string to pass as an argument for the invoked utility. EXIT STATUS - If command is invoked, the exit status of alsause is the exit status of - command; otherwise, the alsause utility exits with one of the following + If command is invoked, the exit status of galsause is the exit status of + command; otherwise, the galsause utility exits with one of the following values. - 0 The alsause utility completed successfully. + 0 The galsause utility completed successfully. - 125 An error occurred in the alsause utility. + 125 An error occurred in the galsause utility. 126 The utility specified by command was found but could not be invoked. 127 The utility specified by command could not be found. SEE ALSO - None. + alsause(1) diff --git a/alsause.c b/alsause.c deleted file mode 100644 index 5cb923d..0000000 --- a/alsause.c +++ /dev/null @@ -1,330 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include <sys/mount.h> -#ifndef OPEN_TREE_CLONE -# define MISSING_OPEN_TREE -#endif -#ifndef MOVE_MOUNT_F_EMPTY_PATH -# define MISSING_MOVE_MOUNT -#endif -#if defined(MISSING_OPEN_TREE) || defined(MISSING_MOVE_MOUNT) -# include <linux/mount.h> -# include <sys/syscall.h> -#endif -#include <sched.h> -#include <alsa/asoundlib.h> -#include <libsimple.h> -#include <libsimple-arg.h> - -NUSAGE(125, "[(-r | name | card:device) [command [argument] ...]]"); - - -#ifdef MISSING_OPEN_TREE -static int -open_tree(int dirfd, const char *path, int flags) -{ - return syscall(SYS_open_tree, dirfd, path, flags); -} -#endif - - -#ifdef MISSING_MOVE_MOUNT -static int -move_mount(int dirfd1, const char *path1, int dirfd2, const char *path2, int flags) -{ - return syscall(SYS_move_mount, dirfd1, path1, dirfd2, path2, flags); -} -#endif - - -static char * -get_config_path(void) -{ - const char *home = getenv("HOME"); - char *ret; - if (!home) { - struct passwd *pw; - errno = 0; - pw = getpwuid(getuid()); - if (!pw || !pw->pw_dir) - eprintf("pwgetuid <real user>: %s", (!errno || errno == EIO) ? "User does not exist" : strerror(errno)); - home = pw->pw_dir; - } - if (strlen(home) > SIZE_MAX - sizeof("/.asoundrc") || - !(ret = malloc(strlen(home) + sizeof("/.asoundrc")))) { - errno = ENOMEM; - eprintf("malloc:"); - } - stpcpy(stpcpy(ret, home), "/.asoundrc"); - return ret; -} - - -#if defined(__GNUC__) -__attribute__((__pure__)) -#endif -static int -is_name(const char *s) -{ - if (!isdigit(*s)) - return 1; - if (*s == '0' && s[1] != ':') - return 1; - while (isdigit(*s)) - s++; - if (*s != ':') - return 1; - s++; - - if (!isdigit(*s)) - return 1; - if (*s == '0' && s[1]) - return 1; - while (isdigit(*s)) - s++; - if (*s) - return 1; - return 0; -} - - -static int -list_outputs(const char *find, int *card_out, int *device_out) -{ - snd_ctl_t *ctl; - snd_ctl_card_info_t *info; - snd_pcm_info_t *pcminfo; - char dev[sizeof("hw:") + 3 * sizeof(int)]; - int r, card, device, failed = 0; - - snd_ctl_card_info_alloca(&info); - snd_pcm_info_alloca(&pcminfo); - - for (card = -1;;) { - r = snd_card_next(&card); - if (r < 0) { - weprintf("snd_card_next: %s", snd_strerror(r)); - return -1; - } - if (card < 0) - break; - - sprintf(dev, "hw:%i", card); - r = snd_ctl_open(&ctl, dev, 0); - if (r < 0) { - weprintf("snd_ctl_open %s 0: %s", dev, snd_strerror(r)); - failed = 1; - continue; - } - - r = snd_ctl_card_info(ctl, info); - if (r < 0) { - weprintf("snd_ctl_card_info <%s>: %s", dev, snd_strerror(r)); - failed = 1; - goto close_card; - } - - for (device = -1;;) { - r = snd_ctl_pcm_next_device(ctl, &device); - if (r < 0) { - weprintf("snd_ctl_pcm_next_device <%s>: %s", dev, snd_strerror(r)); - failed = 1; - goto close_card; - } - if (device < 0) - break; - - snd_pcm_info_set_device(pcminfo, (unsigned int)device); - snd_pcm_info_set_subdevice(pcminfo, 0); - snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); - r = snd_ctl_pcm_info(ctl, pcminfo); - if (r < 0) { - if (r == -ENOENT) - continue; - weprintf("snd_ctl_pcm_info <%s>: %s (%i)", dev, snd_strerror(r), r); - failed = 1; - goto close_card; - } - - if (find) { - const char *s, *n = find; - s = snd_ctl_card_info_get_name(info); - if (strncmp(n, s, strlen(s))) - continue; - n = &n[strlen(s)]; - if (strncmp(n, " - ", 3)) - continue; - n = &n[3]; - s = snd_pcm_info_get_name(pcminfo); - if (strcmp(n, s)) - continue; - *card_out = snd_ctl_card_info_get_card(info); - *device_out = device; - snd_ctl_close(ctl); - return 0; - } else { - printf("%i:%i\t%s - %s\n", - snd_ctl_card_info_get_card(info), device, - snd_ctl_card_info_get_name(info), - snd_pcm_info_get_name(pcminfo)); - } - } - - close_card: - snd_ctl_close(ctl); - } - - if (find) { - *card_out = -1; - *device_out = -1; - } - return -failed; -} - - -static int -writeall(int fd, void *data, size_t len) -{ - char *text = data; - ssize_t r; - while (len) { - r = write(fd, text, len); - if (r < 0) { - if (errno == EINTR) - continue; - return -1; - } - text = &text[r]; - len -= (size_t)r; - } - return 0; -} - - -int -main(int argc, char *argv[]) -{ - int reset = 0; - - libsimple_default_failure_exit = 125; - - ARGBEGIN { - case 'r': - reset = 1; - break; - default: - usage(); - } ARGEND; - - if (!argc) { - if (reset) { - char *path = get_config_path(); - if (unlink(path) && errno != ENOENT) - eprintf("unlink %s:", path); - free(path); - } else { - if (list_outputs(NULL, NULL, NULL)) - return libsimple_default_failure_exit; - } - } else { - char *path = get_config_path(); - char *text, temppath[sizeof("/tmp/alsause.") + 3 * sizeof(uintmax_t)]; - int len, fd, treefd; - if (reset) { - text = NULL; - len = 0; - } else if (is_name(argv[0])) { - int card, device; - if (list_outputs(argv[0], &card, &device)) - return 1; - if (card == -1 || device == -1) - eprintf("audio output \"%s\" not found", argv[0]); - len = snprintf(NULL, 0, "defaults.pcm.card %i\ndefaults.pcm.device %i\n", card, device); - if (len < 0) - eprintf("snprintf NULL 0:"); - text = malloc((size_t)len + 1U); - if (!text) - eprintf("malloc:"); - if (sprintf(text, "defaults.pcm.card %i\ndefaults.pcm.device %i\n", card, device) != len) - abort(); - argc--; - argv++; - } else { - char *card = argv[0]; - char *device = strchr(card, ':'); - if (!device) - usage(); - *device++ = '\0'; - if (strchr(device, ':')) - usage(); - len = snprintf(NULL, 0, "defaults.pcm.card %s\ndefaults.pcm.device %s\n", card, device); - if (len < 0) - eprintf("snprintf NULL 0:"); - text = malloc((size_t)len + 1U); - if (!text) - eprintf("malloc:"); - if (sprintf(text, "defaults.pcm.card %s\ndefaults.pcm.device %s\n", card, device) != len) - abort(); - argc--; - argv++; - } - - if (!argc) { - fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0666); - if (fd < 0) - eprintf("open %s O_CREAT|O_TRUNC|O_WRONLY 0666:", path); - if (fchown(fd, getuid(), getgid())) - weprintf("fchown %s <read user> <real path>:", path); - if (writeall(fd, text, (size_t)len) || (close(fd) && (errno != EINTR))) - eprintf("write %s:", path); - free(text); - free(path); - return 0; - } - - if (unshare(CLONE_NEWNS)) - eprintf("unshare CLONE_NEWNS:"); - if (mount("none", "/", NULL, MS_REC | MS_SLAVE, NULL)) - eprintf("mount none / NULL MS_REC|MS_SLAVE NULL:"); - - fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd >= 0) { - fchown(fd, getuid(), getgid()); - close(fd); - } - - sprintf(temppath, "/tmp/alsause.%ju", (uintmax_t)getpid()); - unlink(temppath); - fd = open(temppath, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd < 0) - eprintf("open %s O_CREAT|O_EXCL|O_WRONLY 0666:", temppath); - if (fchown(fd, getuid(), getgid())) - weprintf("fchown %s <read user> <real path>:", temppath); - if (writeall(fd, text, (size_t)len) || (close(fd) && (errno != EINTR))) - eprintf("write %s:", temppath); - free(text); - - treefd = open_tree(AT_FDCWD, temppath, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); - if (treefd < 0) { - weprintf("open_tree AT_FDCWD %s OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC:", temppath); - unlink(temppath); - exit(libsimple_default_failure_exit); - } - if (move_mount(treefd, "", AT_FDCWD, path, MOVE_MOUNT_F_EMPTY_PATH)) { - weprintf("move_mount %s \"\" AT_FDCWD %s MOVE_MOUNT_F_EMPTY_PATH:", temppath, path); - unlink(temppath); - exit(libsimple_default_failure_exit); - } - - if (unlink(temppath)) - weprintf("unlink %s:", temppath); - if (setegid(getgid())) - eprintf("setegid <real group>:"); - if (seteuid(getuid())) - eprintf("seteuid <real user>:"); - - execvp(argv[0], argv); - enprintf(errno == ENOENT ? 127 : 126, "execvp %s:", argv[0]); - } - - return 0; -} @@ -1,8 +1,14 @@ PREFIX = /usr MANPREFIX = $(PREFIX)/share/man +ALSAUSE_PREFIX = $(PREFIX) +ALSAUSE_PATH = $(ALSAUSE_PREFIX)/bin/alsause + CC = c99 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE CFLAGS = LDFLAGS = -lasound -lsimple + +CFLAGS_GTK2 = $$(pkg-config --cflags gtk+-2.0) +LDFLAGS_GTK2 = $$(pkg-config --libs gtk+-2.0) @@ -1,60 +1,29 @@ -.TH ALSAUSE 1 ALSAUSE +.TH GALSAUSE 1 GALSAUSE .SH NAME -alsause - Set default ALSA PCM device +galsause - Set default ALSA PCM device .SH SYNPOSIS -.B alsause -.RB [( \-r -| -.I name -| -.IR card \fB:\fP device ) +.B galsause .RI [ command -.RI [ argument "] ...]]" +.RI [ argument "] ...]" .SH DESCRIPTION The -.B alsause -utility lists all ALSA PCM devices or -sets, either for the user or for a program -it starts, the default ALSA PCM device. -.PP -No options or operands are specified, -.B alsause -will list all ALSA PCM devices, otherwise -it will set the default ALSA PCM device. +.B galsause +utility is a graphical utility for setting the default +ALSA PCM device for the user or a program it starts. .PP If .I command -is specified, -.B alsause -will run that command with the default -ALSA PCM device set only for that process -and it's child processes. +is specified, galsause will run that command with the default +ALSA PCM device set only for that process and it's child processes. .SH OPTIONS The -.B alsause +.B galsause utility conforms to the Base Definitions volume of POSIX.1-2017, .IR "Section 12.2" ", " "Utility Syntax Guidelines" . .PP -The following option is supported: -.TP -.B \-r -Remove the user-specified default ALSA PCM device, -falling back to the system-wide default ALSA PCM device. +No options are supported. .SH OPERANDS -The following operands shall be supported: -.TP -.IR card : device -The card number and device number to use as the default -ALSA PCM device. -.TP -.I name -The card and device, by name, to use as the default -ALSA PCM device. This string is on the form output by the -.B alsause -utility itself: -.BR "\(dq%s - %s\(dq" , -.RI < "card name" >, -.RI < "device name" >. +The following operands are supported: .TP .I command The name of the utility to be invoked. @@ -79,25 +48,17 @@ as described in the Base Definitions volume of POSIX.1-2017, .PP Additionally, environment variables that affect ALSA takes precedence over the default device set by the -.B alsause +.B galsause utility. .SH ASYNCHRONOUS EVENTS Default. .SH STDOUT -If no option or operand is specified, each ALSA PCM device is the form: -.RS - -\fB\(dq%u:%u\et%s - %s\en\(dq,\fP <\fIcard number\fP>\fB,\fP <\fIdevice number\fP>\fB,\fP <\fIcard name\fP>\fB,\fP <\fIdevice name\fP> - -.RE -Otherwise, the -.B alsause -utility does not write to standard output. +Not used. .SH STDERR -The standard error shall be used only for diagnostic messages. +The standard error is only used for diagnostic messages. .SH OUTPUT FILES If the the default ALSA PCM device is set, the -.B alsause +.B galsause utility may create, modify, replace, or unlink the file .I .asoundrc placed in the user's home directory. @@ -107,21 +68,21 @@ None. If .I command is invoked, the exit status of -.B alsause +.B galsause is the exit status of .IR command ; otherwise, the -.B alsause +.B galsause utility exits with one of the following values. .TP 0 The -.I alsause +.I galsause utility completed successfully. .TP 125 An error occurred in the -.I alsause +.I galsause utility. .TP 126 @@ -134,4 +95,4 @@ The utility specified by .I command could not be found. .SH SEE ALSO -None. +.BR alsause (1) diff --git a/galsause.c b/galsause.c new file mode 100644 index 0000000..f1e2b31 --- /dev/null +++ b/galsause.c @@ -0,0 +1,431 @@ +/* See LICENSE file for copyright and license details. */ +#include <alsa/asoundlib.h> +#include <libsimple.h> +#include <libsimple-arg.h> +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +# pragma GCC diagnostic ignored "-Wstrict-prototypes" +#endif +#include <gtk/gtk.h> +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +NUSAGE(125, "[command [argument] ...]"); + + +static GtkWidget *dialog; +static GtkWidget *cardcombo; +static GtkListStore *cardcombo_store; +static const char **command; + + +static void +appendn(char **bufp, size_t *lenp, size_t *sizep, const char *text, size_t len) +{ + if (!sizep || *lenp + len + 1U > *sizep) { + *bufp = erealloc(*bufp, *lenp + len + 1U); + if (sizep) + *sizep = *lenp + len + 1U; + } + memcpy(&(*bufp)[*lenp], text, len + 1U); + *lenp += len; + (*bufp)[*lenp] = '\0'; +} + + +static void +append(char **bufp, size_t *lenp, size_t *sizep, const char *text) +{ + appendn(bufp, lenp, sizep, text, strlen(text)); +} + + +#if defined(__GNUC__) +__attribute__((__pure__)) +#endif +static size_t +valid_utf8_len(const char *text) +{ + const unsigned char *s = (const unsigned char *)text; + unsigned long cp, min, max; + size_t n = 0U, ret; + unsigned char c; + + c = *s++; + while (c & 0x80U) { + c <<= 1; + n += 1U; + } + c = (c & 0xFFU) >> n; + cp = (unsigned long)c; + + switch (n) { + case 0: + return 1U; + case 1: + return 0U; + case 2: + min = 0x80UL; + max = 0x7FFUL; + break; + case 3: + min = 0x800UL; + max = 0xFFFFUL; + break; + case 4: + min = 0x10000UL; + max = 0x10FFFFUL; + break; + default: + return 0U; + } + + ret = n--; + + for (; n; n--, s++) { + if ((*s & 0xC0U) != 0x80U) + return 0U; + cp <<= 6; + cp |= (unsigned long)(*s ^ 0x80U); + } + + if (cp < min || cp > max) + return 0U; + if (0xD800UL <= cp && cp <= 0xDFFFUL) + return 0U; + + return ret; +} + + +static const char * +escape(const char *text, char **bufp, size_t *sizep) +{ + const char *s; + size_t len = 0; + size_t clen; + char quote = 0, hex[2]; + + if (!*text) + return "''"; + + for (s = text; *s; s++) { + switch (*s) { + case '!': case '?': + case '$': case '*': + case '&': case '|': + case '(': case ')': + case ';': case '~': + case '[': case ']': + case '<': case '>': + case '{': case '}': + case ' ': case '#': + case '\"': case '\\': + goto unsafe; + + case '\'': + goto unsafe; + + default: + if ((*s > '\0' && *s < ' ') || *s == '\x7F' || (*s & 0x80)) + goto unsafe; + break; + } + } + + return text; + +unsafe: + for (s = text; *s; s++) { + switch (*s) { + case '!': case '?': + case '$': case '*': + case '&': case '|': + case '(': case ')': + case ';': case '~': + case '[': case ']': + case '<': case '>': + case '{': case '}': + case ' ': case '#': + case '\"': case '\\': + if (quote != '\'') { + append(bufp, &len, sizep, quote ? "''" : "'"); + quote = '\''; + } + appendn(bufp, &len, sizep, s, 1); + break; + + case '\'': + append(bufp, &len, sizep, quote ? "'\\'" : "\\'"); + quote = 0; + break; + + default: + if ((*s > '\0' && *s < ' ') || *s == '\x7F') { + use_hex: + if (quote != '$') { + append(bufp, &len, sizep, quote ? "'$'" : "$'"); + quote = '$'; + } + append(bufp, &len, sizep, "\\x"); + hex[0] = "0123456789ABCDEF"[(*s >> 4) & 15]; + hex[1] = "0123456789ABCDEF"[(*s >> 0) & 15]; + appendn(bufp, &len, sizep, hex, 2); + } else if (*s & 0x80) { + clen = valid_utf8_len(s); + if (!clen) + goto use_hex; + if (quote != '\'') { + append(bufp, &len, sizep, quote ? "''" : "'"); + quote = '\''; + } + appendn(bufp, &len, sizep, s, clen); + s = &s[clen - 1U]; + } else { + if (quote != '\'') { + append(bufp, &len, sizep, quote ? "''" : "'"); + quote = '\''; + } + appendn(bufp, &len, sizep, s, 1); + } + break; + } + } + if (quote) + append(bufp, &len, sizep, "'"); + + return *bufp; +} + + +static void +on_response(GtkDialog *dialog_, int response_id, void *user_data) +{ + char *s; + + (void) user_data; + (void) dialog_; + + if (response_id != GTK_RESPONSE_OK) { + gtk_widget_destroy(GTK_WIDGET(dialog)); + exit(libsimple_default_failure_exit); + } + + command[1] = s = estrdup(gtk_combo_box_get_active_text(GTK_COMBO_BOX(cardcombo))); + *strchr(s, ' ') = '\0'; + gtk_widget_destroy(GTK_WIDGET(dialog)); + execv(ALSAUSE_PATH, (void *)command); + exit(errno == ENOENT ? 127 : 126); +} + + +static void +populate_outputs(void) +{ + snd_ctl_t *ctl; + snd_ctl_card_info_t *info; + snd_pcm_info_t *pcminfo; + char dev[sizeof("hw:") + 3 * sizeof(int)]; + int r, card, device; + int found_any = 0; + GtkTreeIter iter; + char *buf = NULL; + const char *name1, *name2; + size_t buf_size = 0; + int len, cardno; + + snd_ctl_card_info_alloca(&info); + snd_pcm_info_alloca(&pcminfo); + + for (card = -1;;) { + r = snd_card_next(&card); + if (r < 0) + eprintf("snd_card_next: %s", snd_strerror(r)); + if (card < 0) + break; + + sprintf(dev, "hw:%i", card); + r = snd_ctl_open(&ctl, dev, 0); + if (r < 0) { + weprintf("snd_ctl_open %s 0: %s", dev, snd_strerror(r)); + continue; + } + + r = snd_ctl_card_info(ctl, info); + if (r < 0) { + weprintf("snd_ctl_card_info <%s>: %s", dev, snd_strerror(r)); + goto close_card; + } + + for (device = -1;;) { + r = snd_ctl_pcm_next_device(ctl, &device); + if (r < 0) { + weprintf("snd_ctl_pcm_next_device <%s>: %s", dev, snd_strerror(r)); + goto close_card; + } + if (device < 0) + break; + + snd_pcm_info_set_device(pcminfo, (unsigned int)device); + snd_pcm_info_set_subdevice(pcminfo, 0); + snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); + r = snd_ctl_pcm_info(ctl, pcminfo); + if (r < 0) { + if (r == -ENOENT) + continue; + weprintf("snd_ctl_pcm_info <%s>: %s (%i)", dev, snd_strerror(r), r); + goto close_card; + } + + found_any = 1; + + cardno = snd_ctl_card_info_get_card(info); + name1 = snd_ctl_card_info_get_name(info); + name2 = snd_pcm_info_get_name(pcminfo); + len = snprintf(NULL, 0, "%i:%i (%s - %s)", cardno, device, name1, name2); + if (len < 0) + abort(); + if ((size_t)len >= buf_size) + buf = erealloc(buf, buf_size = (size_t)len + 1U); + len = sprintf(buf, "%i:%i (%s - %s)", cardno, device, name1, name2); + if (len < 0 || (size_t)len >= buf_size) + abort(); + gtk_list_store_append(cardcombo_store, &iter); + gtk_list_store_set(cardcombo_store, &iter, 0, buf, -1); + } + + close_card: + snd_ctl_close(ctl); + } + + if (!found_any) + eprintf("no ALSA PCM devices found"); + + free(buf); +} + + +static char * +create_command_string(char **argv) +{ + char *buf = NULL, *ret = NULL; + size_t len = 0, size = 0; + append(&ret, &len, NULL, escape(*argv++, &buf, &size)); + while (*argv) { + append(&ret, &len, NULL, " "); + append(&ret, &len, NULL, escape(*argv++, &buf, &size)); + } + free(buf); + return ret; +} + + +int +main(int argc, char *argv[]) +{ + int xargc = 1; + char *xargva[] = {argv[0], NULL}; + char **xargv = xargva; + const char *icon; + GtkIconTheme *theme; + GtkWidget *content, *hbox, *vbox, *label, *image; + GtkCellRenderer *renderer; + char *command_str; + + libsimple_default_failure_exit = 125; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + command = ecalloc(3U + (size_t)argc, sizeof(*command)); + memcpy(&command[2], argv, ((size_t)argc + 1U) * sizeof(*command)); + command[0] = "alsause"; + + g_set_prgname(argv0); + g_set_application_name("galsause"); + + gtk_init(&xargc, &xargv); + + dialog = gtk_dialog_new_with_buttons("Select audio card - galsause", NULL, 0, + argc ? GTK_STOCK_EXECUTE : GTK_STOCK_OK, GTK_RESPONSE_OK, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); + + theme = gtk_icon_theme_get_default(); + if (!gtk_icon_theme_has_icon(theme, icon = "apps/galsause") && + !gtk_icon_theme_has_icon(theme, icon = "galsause") && + !gtk_icon_theme_has_icon(theme, icon = "devices/audio-card") && + !gtk_icon_theme_has_icon(theme, icon = "audio-card")) + icon = "galsause"; + gtk_window_set_default_icon_name(icon); + + gtk_window_set_wmclass(GTK_WINDOW(dialog), "galsause", "galsause"); + gtk_window_set_role(GTK_WINDOW(dialog), "audio-card-selection"); + + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); + + gtk_container_set_border_width(GTK_CONTAINER(dialog), 8); + content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + + if (icon) { + hbox = gtk_hbox_new(FALSE, 16); + gtk_box_pack_start(GTK_BOX(content), hbox, FALSE, FALSE, 4); + + image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment(GTK_MISC(image), 0.0f, 0.0f); + gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0); + } else { + vbox = content; + } + + label = gtk_label_new_with_mnemonic("Select default audio card:"); + gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.5f); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2); + + cardcombo = gtk_combo_box_new(); + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(cardcombo), renderer, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(cardcombo), renderer, "text", 0, NULL); + cardcombo_store = gtk_list_store_new(1, G_TYPE_STRING); + gtk_combo_box_set_model(GTK_COMBO_BOX(cardcombo), GTK_TREE_MODEL(cardcombo_store)); + populate_outputs(); + gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(cardcombo), FALSE); + gtk_box_pack_start(GTK_BOX(vbox), cardcombo, FALSE, FALSE, 0); + gtk_combo_box_set_active(GTK_COMBO_BOX(cardcombo), 0); + + gtk_label_set_mnemonic_widget(GTK_LABEL(label), cardcombo); + + if (argc) { + command_str = create_command_string(argv); + + label = gtk_label_new(NULL); + gtk_widget_set_size_request(label, 8, 8); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + label = gtk_label_new("About to execute:"); + gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.5f); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2); + + label = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(label), command_str); + gtk_entry_set_editable(GTK_ENTRY(label), FALSE); + gtk_entry_set_has_frame(GTK_ENTRY(label), TRUE); + gtk_entry_set_max_length(GTK_ENTRY(label), 0); + gtk_entry_set_width_chars(GTK_ENTRY(label), 60); + gtk_widget_set_tooltip_text(label, command_str); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + } + + g_signal_connect(dialog, "response", G_CALLBACK(&on_response), NULL); + + gtk_widget_show_all(dialog); + gtk_main(); + + return libsimple_default_failure_exit; +} |
