/* See LICENSE file for copyright and license details. */ #include #include #include #if defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" # pragma GCC diagnostic ignored "-Wstrict-prototypes" #endif #include #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; }