aboutsummaryrefslogtreecommitdiffstats
path: root/galsause.c
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 /galsause.c
parentFix open_tree support for when missing in libc (diff)
downloadgalsause-fad53404f45b2e0daeda733687a41dd8e600aa57.tar.gz
galsause-fad53404f45b2e0daeda733687a41dd8e600aa57.tar.bz2
galsause-fad53404f45b2e0daeda733687a41dd8e600aa57.tar.xz
Fork as galsauseHEAD1.0master
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'galsause.c')
-rw-r--r--galsause.c431
1 files changed, 431 insertions, 0 deletions
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;
+}