aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-12-26 09:48:09 +0100
committerMattias Andrée <m@maandree.se>2025-12-26 10:05:39 +0100
commitfad53404f45b2e0daeda733687a41dd8e600aa57 (patch)
treef8dbd73a1e59cb8df53c7cb899c7d09ab5d74845
parentFix open_tree support for when missing in libc (diff)
downloadgalsause-master.tar.gz
galsause-master.tar.bz2
galsause-master.tar.xz
Fork as galsauseHEAD1.0master
Signed-off-by: Mattias Andrée <m@maandree.se>
-rw-r--r--.gitignore2
-rw-r--r--Makefile28
-rw-r--r--README41
-rw-r--r--alsause.c330
-rw-r--r--config.mk6
-rw-r--r--galsause.1 (renamed from alsause.1)81
-rw-r--r--galsause.c431
7 files changed, 484 insertions, 435 deletions
diff --git a/.gitignore b/.gitignore
index 603ea03..18d294b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,4 @@
*.gcov
*.gcno
*.gcda
-/alsause
+/galsause
diff --git a/Makefile b/Makefile
index 31cea4c..bf6470b 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README b/README
index 41f9516..71925ad 100644
--- a/README
+++ b/README
@@ -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;
-}
diff --git a/config.mk b/config.mk
index 9284edf..b6b0996 100644
--- a/config.mk
+++ b/config.mk
@@ -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)
diff --git a/alsause.1 b/galsause.1
index d433d58..5a8c551 100644
--- a/alsause.1
+++ b/galsause.1
@@ -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;
+}