diff options
Diffstat (limited to '')
| -rw-r--r-- | galsause.c | 431 |
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; +} |
