/* $Id$ */ /* Copyright (c) 2009-2020 Pierre Pronchery */ /* This file is part of DeforaOS Desktop Mixer */ /* All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* FIXME: * - on NetBSD sometimes the "mute" control is before the corresponding knob */ #if defined(__NetBSD__) # include # include #else # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "control.h" #include "common.h" #include "mixer.h" #include "../config.h" #define _(string) gettext(string) #define N_(string) (string) /* compatibility */ #if !GTK_CHECK_VERSION(2, 12, 0) # define GTK_ICON_LOOKUP_GENERIC_FALLBACK 0 #endif /* constants */ #ifndef PROGNAME_MIXER # define PROGNAME_MIXER "mixer" #endif /* Mixer */ /* private */ /* types */ #ifdef AUDIO_MIXER_DEVINFO typedef struct _MixerClass { int mixer_class; audio_mixer_name_t label; GtkWidget * hbox; int page; } MixerClass; #endif typedef struct _MixerLevel { uint8_t channels[8]; uint8_t delta; size_t channels_cnt; } MixerLevel; /* XXX rename this type */ typedef struct _MixerControl2 { int index; int type; union { int ord; int mask; MixerLevel level; } un; MixerControl * control; } MixerControl2; struct _Mixer { /* widgets */ GtkWidget * window; GtkWidget * widget; GtkWidget * notebook; GtkWidget * properties; PangoFontDescription * bold; /* internals */ String * device; #ifdef AUDIO_MIXER_DEVINFO int fd; MixerClass * classes; size_t classes_cnt; #else int fd; #endif MixerControl2 * controls; size_t controls_cnt; guint source; }; /* prototypes */ static int _mixer_error(Mixer * mixer, char const * message, int ret); /* accessors */ static int _mixer_get_control(Mixer * mixer, MixerControl2 * control); static int _mixer_set_control(Mixer * mixer, MixerControl2 * control); static int _mixer_set_control_widget(Mixer * mixer, MixerControl2 * control); static String const * _mixer_get_icon(String const * id); /* useful */ static int _mixer_refresh_control(Mixer * mixer, MixerControl2 * control); static void _mixer_scrolled_window_add(GtkWidget * window, GtkWidget * widget); static void _mixer_show_view(Mixer * mixer, int view); /* public */ /* mixer_new */ static GtkWidget * _new_frame_label(GdkPixbuf * pixbuf, char const * name, char const * label); #ifdef AUDIO_MIXER_DEVINFO static MixerControl * _new_enum(Mixer * mixer, int index, struct audio_mixer_enum * e, String const * id, String const * icon, String const * name); static MixerControl * _new_set(Mixer * mixer, int index, struct audio_mixer_set * s, String const * id, String const * icon, String const * name); #endif static MixerControl * _new_value(Mixer * mixer, int index, GtkSizeGroup * vgroup, String const * id, String const * icon, String const * name); /* callbacks */ static gboolean _new_on_refresh(gpointer data); Mixer * mixer_new(GtkWidget * window, String const * device, MixerLayout layout) { Mixer * mixer; GtkSizeGroup * hgroup; GtkSizeGroup * vgroup; GtkWidget * scrolled = NULL; GtkWidget * label; GtkWidget * widget; GtkWidget * hvbox = NULL; GtkWidget * hbox; MixerControl * control; MixerControl2 * q; int i; #ifdef AUDIO_MIXER_DEVINFO mixer_devinfo_t md; mixer_devinfo_t md2; MixerClass * p; size_t u; GtkWidget * vbox2; char * name; #else int value; char const * labels[] = SOUND_DEVICE_LABELS; char const * names[] = SOUND_DEVICE_NAMES; #endif if((mixer = malloc(sizeof(*mixer))) == NULL) return NULL; if(device == NULL) device = MIXER_DEFAULT_DEVICE; mixer->device = string_new(device); mixer->fd = open(device, O_RDWR); mixer->window = window; mixer->properties = NULL; mixer->bold = NULL; #ifdef AUDIO_MIXER_DEVINFO mixer->classes = NULL; mixer->classes_cnt = 0; #endif mixer->controls = NULL; mixer->controls_cnt = 0; mixer->source = 0; if(mixer->device == NULL || mixer->fd < 0) { _mixer_error(NULL, device, 0); mixer_delete(mixer); return NULL; } hgroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); vgroup = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL); /* widgets */ mixer->bold = pango_font_description_new(); pango_font_description_set_weight(mixer->bold, PANGO_WEIGHT_BOLD); /* classes */ mixer->notebook = NULL; if(layout == ML_TABBED) mixer->notebook = gtk_notebook_new(); else { scrolled = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, (layout == ML_VERTICAL) ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER); hvbox = gtk_box_new((layout == ML_VERTICAL) ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, 4); gtk_container_set_border_width(GTK_CONTAINER(hvbox), 2); if(layout == ML_VERTICAL) gtk_box_set_homogeneous(GTK_BOX(hvbox), TRUE); _mixer_scrolled_window_add(scrolled, hvbox); } for(i = 0;; i++) { #ifdef AUDIO_MIXER_DEVINFO md.index = i; if(ioctl(mixer->fd, AUDIO_MIXER_DEVINFO, &md) < 0) break; if(md.type != AUDIO_MIXER_CLASS) continue; if((p = realloc(mixer->classes, sizeof(*p) * (mixer->classes_cnt + 1))) == NULL) { _mixer_error(NULL, "realloc", 1); mixer_delete(mixer); return NULL; } mixer->classes = p; p = &mixer->classes[mixer->classes_cnt++]; p->mixer_class = md.mixer_class; memcpy(&p->label, &md.label, sizeof(md.label)); p->hbox = NULL; p->page = -1; #else hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); gtk_container_set_border_width(GTK_CONTAINER(hbox), 2); if(mixer->notebook != NULL) { label = _new_frame_label(NULL, _("All"), NULL); gtk_widget_show_all(label); scrolled = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW( scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); _mixer_scrolled_window_add(scrolled, hbox); gtk_notebook_append_page(GTK_NOTEBOOK(mixer->notebook), scrolled, label); } else gtk_box_pack_start(GTK_BOX(hvbox), hbox, FALSE, TRUE, 0); break; #endif } /* controls */ for(i = 0;; i++) { #ifdef AUDIO_MIXER_DEVINFO md.index = i; if(ioctl(mixer->fd, AUDIO_MIXER_DEVINFO, &md) < 0) break; if(md.type == AUDIO_MIXER_CLASS) continue; for(u = 0; u < mixer->classes_cnt; u++) if(mixer->classes[u].mixer_class == md.mixer_class) break; if(u == mixer->classes_cnt) continue; hbox = mixer->classes[u].hbox; control = NULL; switch(md.type) { case AUDIO_MIXER_ENUM: control = _new_enum(mixer, i, &md.un.e, md.label.name, _mixer_get_icon(md.label.name), md.label.name); break; case AUDIO_MIXER_SET: control = _new_set(mixer, i, &md.un.s, md.label.name, _mixer_get_icon(md.label.name), md.label.name); break; case AUDIO_MIXER_VALUE: control = _new_value(mixer, i, vgroup, md.label.name, _mixer_get_icon(md.label.name), md.label.name); break; } if(control == NULL) continue; if((q = realloc(mixer->controls, sizeof(*q) * (mixer->controls_cnt + 1))) == NULL) { mixercontrol_delete(control); /* FIXME report error */ continue; } mixer->controls = q; q = &mixer->controls[mixer->controls_cnt++]; q->index = md.index; q->type = md.type; q->control = control; widget = mixercontrol_get_widget(control); vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4); gtk_box_pack_start(GTK_BOX(vbox2), widget, TRUE, TRUE, 0); gtk_size_group_add_widget(hgroup, widget); if(hbox == NULL) { p = &mixer->classes[u]; p->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); hbox = p->hbox; gtk_container_set_border_width(GTK_CONTAINER(hbox), 2); if(mixer->notebook != NULL) { if((name = strdup(mixer->classes[u].label.name)) != NULL) name[0] = toupper( (unsigned char)name[0]); label = _new_frame_label(NULL, mixer->classes[u].label.name, name); free(name); gtk_widget_show_all(label); scrolled = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); _mixer_scrolled_window_add(scrolled, p->hbox); p->page = gtk_notebook_append_page( GTK_NOTEBOOK(mixer->notebook), scrolled, label); } else if(hvbox != NULL) gtk_box_pack_start(GTK_BOX(hvbox), p->hbox, FALSE, TRUE, 0); } gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, TRUE, 0); /* add a mute button if relevant */ if(md.type != AUDIO_MIXER_VALUE) continue; md2.index = md.index + 1; if(ioctl(mixer->fd, AUDIO_MIXER_DEVINFO, &md2) < 0) break; if(md2.type == AUDIO_MIXER_CLASS) continue; for(u = 0; u < mixer->classes_cnt; u++) if(mixer->classes[u].mixer_class == md2.mixer_class) break; if(u == mixer->classes_cnt) continue; u = strlen(md.label.name); if(md2.type != AUDIO_MIXER_ENUM || strncmp(md.label.name, md2.label.name, u) != 0 || (u = strlen(md2.label.name)) < 6 || strcmp(&md2.label.name[u - 5], ".mute") != 0) continue; /* XXX may fail */ mixercontrol_set(control, "show-mute", TRUE, NULL); i++; #else if(i == SOUND_MIXER_NONE) break; if(ioctl(mixer->fd, MIXER_READ(i), &value) != 0) continue; if((q = realloc(mixer->controls, sizeof(*q) * (mixer->controls_cnt + 1))) == NULL) /* FIXME report error */ continue; mixer->controls = q; q = &mixer->controls[mixer->controls_cnt]; if((control = _new_value(mixer, i, vgroup, names[i], _mixer_get_icon(names[i]), labels[i])) == NULL) continue; q->index = i; q->type = 0; q->control = control; mixer->controls_cnt++; widget = mixercontrol_get_widget(control); gtk_size_group_add_widget(hgroup, widget); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0); #endif } mixer->widget = (mixer->notebook != NULL) ? mixer->notebook : scrolled; #ifdef AUDIO_MIXER_DEVINFO mixer_show_class(mixer, AudioCoutputs); #endif gtk_widget_show_all(mixer->widget); mixer->source = g_timeout_add(500, _new_on_refresh, mixer); return mixer; } static GtkWidget * _new_frame_label(GdkPixbuf * pixbuf, char const * name, char const * label) { GtkIconTheme * icontheme; GtkWidget * hbox; GtkWidget * widget; const int size = 16; icontheme = gtk_icon_theme_get_default(); hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); if(pixbuf == NULL) pixbuf = gtk_icon_theme_load_icon(icontheme, _mixer_get_icon(name), size, GTK_ICON_LOOKUP_GENERIC_FALLBACK, NULL); if(pixbuf != NULL) { widget = gtk_image_new_from_pixbuf(pixbuf); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0); } if(label == NULL) label = name; widget = gtk_label_new(label); #if GTK_CHECK_VERSION(3, 0, 0) gtk_widget_set_halign(widget, GTK_ALIGN_START); #else gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5); #endif gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0); return hbox; } #ifdef AUDIO_MIXER_DEVINFO static MixerControl * _new_enum(Mixer * mixer, int index, struct audio_mixer_enum * e, String const * id, String const * icon, String const * name) { MixerControl2 mc; MixerControl * control; int i; char label[16]; char value[16]; if(e->num_mem <= 0) return NULL; mc.index = index; if(_mixer_get_control(mixer, &mc) != 0 || (control = mixercontrol_new(mixer, id, icon, name, "radio", "members", e->num_mem, NULL)) == NULL) return NULL; for(i = 0; i < e->num_mem; i++) { snprintf(label, sizeof(label), "label%d", i); snprintf(value, sizeof(value), "value%d", i); if(mixercontrol_set(control, label, e->member[i].label.name, value, e->member[i].ord, NULL) != 0) { mixercontrol_delete(control); return NULL; } } if(_mixer_set_control_widget(mixer, &mc) != 0) { mixercontrol_delete(control); return NULL; } return control; } static MixerControl * _new_set(Mixer * mixer, int index, struct audio_mixer_set * s, String const * id, String const * icon, String const * name) { MixerControl2 mc; MixerControl * control; int i; char label[16]; char value[16]; if(s->num_mem <= 0) return NULL; mc.index = index; if(_mixer_get_control(mixer, &mc) != 0 || (control = mixercontrol_new(mixer, id, icon, name, "set", "members", s->num_mem, NULL)) == NULL) return NULL; mc.control = control; for(i = 0; i < s->num_mem; i++) { snprintf(label, sizeof(label), "label%d", i); snprintf(value, sizeof(value), "value%d", i); if(mixercontrol_set(control, label, s->member[i].label.name, value, s->member[i].mask, NULL) != 0) { mixercontrol_delete(control); return NULL; } } if(_mixer_set_control_widget(mixer, &mc) != 0) { mixercontrol_delete(control); return NULL; } return control; } #endif /* AUDIO_MIXER_DEVINFO */ static MixerControl * _new_value(Mixer * mixer, int index, GtkSizeGroup * vgroup, String const * id, String const * icon, String const * name) { MixerControl2 mc; MixerControl * control; size_t i; gboolean bind = TRUE; mc.index = index; if(_mixer_get_control(mixer, &mc) != 0 || mc.un.level.channels_cnt <= 0 || (control = mixercontrol_new(mixer, id, icon, name, "channels", "channels", mc.un.level.channels_cnt, "delta", mc.un.level.delta, "vgroup", vgroup, NULL)) == NULL) return NULL; mc.control = control; /* detect if binding is in place */ for(i = 1; i < mc.un.level.channels_cnt; i++) if(mc.un.level.channels[i] != mc.un.level.channels[0]) { bind = FALSE; break; } if(mixercontrol_set(control, "bind", bind, NULL) != 0 || _mixer_set_control_widget(mixer, &mc) != 0) { mixercontrol_delete(control); return NULL; } return control; } /* callbacks */ static gboolean _new_on_refresh(gpointer data) { Mixer * mixer = data; mixer_refresh(mixer); return TRUE; } /* mixer_delete */ void mixer_delete(Mixer * mixer) { size_t i; if(mixer->source > 0) g_source_remove(mixer->source); for(i = 0; i < mixer->controls_cnt; i++) mixercontrol_delete(mixer->controls[i].control); free(mixer->controls); if(mixer->fd >= 0) close(mixer->fd); if(mixer->device != NULL) string_delete(mixer->device); if(mixer->bold != NULL) pango_font_description_free(mixer->bold); free(mixer); } /* accessors */ /* mixer_get_properties */ int mixer_get_properties(Mixer * mixer, MixerProperties * properties) { #ifdef AUDIO_MIXER_DEVINFO audio_device_t ad; if(ioctl(mixer->fd, AUDIO_GETDEV, &ad) != 0) return -_mixer_error(mixer, "AUDIO_GETDEV", 1); snprintf(properties->name, sizeof(properties->name), "%s", ad.name); snprintf(properties->version, sizeof(properties->version), "%s", ad.version); snprintf(properties->device, sizeof(properties->device), "%s", ad.config); #else struct mixer_info mi; int version; if(ioctl(mixer->fd, SOUND_MIXER_INFO, &mi) != 0) return -_mixer_error(mixer, "SOUND_MIXER_INFO", 1); if(ioctl(mixer->fd, OSS_GETVERSION, &version) != 0) return -_mixer_error(mixer, "OSS_GETVERSION", 1); snprintf(properties->name, sizeof(properties->name), "%s", mi.name); snprintf(properties->version, sizeof(properties->version), "%u.%u", (version >> 16) & 0xffff, version & 0xffff); snprintf(properties->device, sizeof(properties->device), "%s", mixer->device); #endif return 0; } /* mixer_get_widget */ GtkWidget * mixer_get_widget(Mixer * mixer) { return mixer->widget; } /* mixer_set */ static int _set_channels(Mixer * mixer, MixerControl * control); #if defined(AUDIO_MIXER_DEVINFO) static int _set_mute(Mixer * mixer, MixerControl * control); static int _set_radio(Mixer * mixer, MixerControl * control); static int _set_set(Mixer * mixer, MixerControl * control); #endif int mixer_set(Mixer * mixer, MixerControl * control) { String const * type; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif if((type = mixercontrol_get_type(control)) == NULL) return -1; else if(string_compare(type, "channels") == 0) return _set_channels(mixer, control); #if defined(AUDIO_MIXER_DEVINFO) else if(string_compare(type, "mute") == 0) return _set_mute(mixer, control); else if(string_compare(type, "radio") == 0) return _set_radio(mixer, control); else if(string_compare(type, "set") == 0) return _set_set(mixer, control); #endif return -1; } static int _set_channels(Mixer * mixer, MixerControl * control) { size_t i; double value; MixerControl2 * mc; char buf[16]; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%p, %p) fd=%d\n", __func__, (void *)mixer, (void *)control, mixer->fd); #endif for(i = 0; i < mixer->controls_cnt; i++) if(mixer->controls[i].control == control) break; if(i == mixer->controls_cnt) return -1; mc = &mixer->controls[i]; if(_mixer_get_control(mixer, mc) != 0) return -1; for(i = 0; i < mc->un.level.channels_cnt; i++) { snprintf(buf, sizeof(buf), "value%zu", i); if(mixercontrol_get(control, buf, &value, NULL) != 0) return -1; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() value%zu=%f\n", __func__, i, value); #endif mc->un.level.channels[i] = (value * 255.0) / 100.0; } return _mixer_set_control(mixer, mc); } #if defined(AUDIO_MIXER_DEVINFO) static int _set_mute(Mixer * mixer, MixerControl * control) { size_t i; gboolean value; MixerControl2 * mc; mixer_ctrl_t p; # ifdef DEBUG fprintf(stderr, "DEBUG: %s(%p, %p) fd=%d\n", __func__, (void *)mixer, (void *)control, mixer->fd); # endif for(i = 0; i < mixer->controls_cnt; i++) if(mixer->controls[i].control == control) break; if(i == mixer->controls_cnt) return -1; mc = &mixer->controls[i]; p.dev = mc->index; p.type = mc->type; if(mixercontrol_get(control, "value", &value, NULL) != 0) return -1; p.un.ord = value; # ifdef DEBUG fprintf(stderr, "DEBUG: %s() ord=%d\n", __func__, p.un.ord); # endif if(ioctl(mixer->fd, AUDIO_MIXER_WRITE, &p) != 0) return -_mixer_error(mixer, "AUDIO_MIXER_WRITE", 1); return 0; } static int _set_radio(Mixer * mixer, MixerControl * control) { size_t i; MixerControl2 * mc; mixer_ctrl_t p; unsigned int value; # ifdef DEBUG fprintf(stderr, "DEBUG: %s(%p) fd=%d\n", __func__, (void *)mixer, mixer->fd); # endif for(i = 0; i < mixer->controls_cnt; i++) if(mixer->controls[i].control == control) break; if(i == mixer->controls_cnt) return -1; mc = &mixer->controls[i]; p.dev = mc->index; p.type = mc->type; if(mixercontrol_get(control, "value", &value, NULL) != 0) return -1; p.un.ord = value; # ifdef DEBUG fprintf(stderr, "DEBUG: %s() ord=%d\n", __func__, p.un.ord); # endif if(ioctl(mixer->fd, AUDIO_MIXER_WRITE, &p) != 0) return -_mixer_error(mixer, "AUDIO_MIXER_WRITE", 1); return 0; } static int _set_set(Mixer * mixer, MixerControl * control) { size_t i; unsigned int value; MixerControl2 * mc; mixer_ctrl_t p; # ifdef DEBUG fprintf(stderr, "DEBUG: %s(%p) fd=%d\n", __func__, (void *)mixer, mixer->fd); # endif for(i = 0; i < mixer->controls_cnt; i++) if(mixer->controls[i].control == control) break; if(i == mixer->controls_cnt) return -1; mc = &mixer->controls[i]; p.dev = mc->index; p.type = mc->type; if(mixercontrol_get(control, "value", &value, NULL) != 0) return -1; p.un.mask = value; # ifdef DEBUG fprintf(stderr, "DEBUG: %s() mask=%d\n", __func__, p.un.mask); # endif if(ioctl(mixer->fd, AUDIO_MIXER_WRITE, &p) != 0) return -_mixer_error(mixer, "AUDIO_MIXER_WRITE", 1); return 0; } #endif /* useful */ /* mixer_properties */ static GtkWidget * _properties_label(Mixer * mixer, GtkSizeGroup * group, char const * label, char const * value); /* callbacks */ static gboolean _properties_on_closex(GtkWidget * widget); void mixer_properties(Mixer * mixer) { GtkSizeGroup * group; GtkWidget * vbox; GtkWidget * hbox; MixerProperties mp; if(mixer->properties != NULL) { gtk_widget_show(mixer->properties); return; } if(mixer_get_properties(mixer, &mp) != 0) return; mixer->properties = gtk_message_dialog_new(GTK_WINDOW(mixer->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, #if GTK_CHECK_VERSION(2, 6, 0) "%s", _("Properties")); gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG(mixer->properties), #endif ""); #if GTK_CHECK_VERSION(2, 10, 0) gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(mixer->properties), gtk_image_new_from_icon_name("gtk-properties", GTK_ICON_SIZE_DIALOG)); #endif gtk_window_set_title(GTK_WINDOW(mixer->properties), _("Properties")); g_signal_connect(mixer->properties, "delete-event", G_CALLBACK( _properties_on_closex), NULL); g_signal_connect(mixer->properties, "response", G_CALLBACK( gtk_widget_hide), NULL); #if GTK_CHECK_VERSION(2, 14, 0) vbox = gtk_dialog_get_content_area(GTK_DIALOG(mixer->properties)); #else vbox = GTK_DIALOG(mixer->properties)->vbox; #endif group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); hbox = _properties_label(mixer, group, _("Name: "), mp.name); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 2); hbox = _properties_label(mixer, group, _("Version: "), mp.version); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); hbox = _properties_label(mixer, group, _("Device: "), mp.device); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 2); gtk_widget_show_all(vbox); gtk_widget_show(mixer->properties); } static GtkWidget * _properties_label(Mixer * mixer, GtkSizeGroup * group, char const * label, char const * value) { GtkWidget * hbox; GtkWidget * widget; hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); widget = gtk_label_new(label); #if GTK_CHECK_VERSION(3, 0, 0) gtk_widget_override_font(widget, mixer->bold); gtk_widget_set_halign(widget, GTK_ALIGN_START); #else gtk_widget_modify_font(widget, mixer->bold); gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5); #endif gtk_size_group_add_widget(group, widget); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0); widget = gtk_label_new(value); #if GTK_CHECK_VERSION(3, 0, 0) gtk_widget_set_halign(widget, GTK_ALIGN_START); #else gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5); #endif gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0); return hbox; } /* callbacks */ static gboolean _properties_on_closex(GtkWidget * widget) { gtk_widget_hide(widget); return TRUE; } /* mixer_refresh */ int mixer_refresh(Mixer * mixer) { int ret = 0; size_t i; for(i = 0; i < mixer->controls_cnt; i++) ret |= _mixer_refresh_control(mixer, &mixer->controls[i]); return ret; } /* mixer_show */ void mixer_show(Mixer * mixer) { gtk_widget_show(mixer->window); } /* mixer_show_all */ void mixer_show_all(Mixer * mixer) { _mixer_show_view(mixer, -1); } /* mixer_show_class */ void mixer_show_class(Mixer * mixer, String const * name) { #ifdef AUDIO_MIXER_DEVINFO size_t u; if(mixer->notebook != NULL && name != NULL) { for(u = 0; u < mixer->classes_cnt; u++) { if(mixer->classes[u].hbox == NULL) continue; if(strcmp(mixer->classes[u].label.name, name) != 0) continue; gtk_notebook_set_current_page(GTK_NOTEBOOK( mixer->notebook), mixer->classes[u].page); } return; } for(u = 0; u < mixer->classes_cnt; u++) if(mixer->classes[u].hbox == NULL) continue; else if(name == NULL || strcmp(mixer->classes[u].label.name, name) == 0) gtk_widget_show(mixer->classes[u].hbox); else gtk_widget_hide(mixer->classes[u].hbox); #endif } /* private */ /* functions */ /* mixer_error */ static int _error_text(char const * message, int ret); static int _mixer_error(Mixer * mixer, char const * message, int ret) { GtkWidget * dialog; char const * error; if(mixer == NULL) return _error_text(message, ret); error = strerror(errno); dialog = gtk_message_dialog_new(GTK_WINDOW(mixer->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", #if GTK_CHECK_VERSION(2, 6, 0) _("Error")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s: %s", message, #endif error); gtk_window_set_title(GTK_WINDOW(dialog), _("Error")); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return ret; } static int _error_text(char const * message, int ret) { fputs(PROGNAME_MIXER ": ", stderr); perror(message); return ret; } /* accessors */ /* mixer_get_control */ static int _mixer_get_control(Mixer * mixer, MixerControl2 * control) { #ifdef AUDIO_MIXER_DEVINFO mixer_ctrl_t p; struct mixer_devinfo md; int i; uint16_t u16; # ifdef DEBUG size_t u; char * sep = ""; # endif md.index = control->index; if(ioctl(mixer->fd, AUDIO_MIXER_DEVINFO, &md) != 0) { if(errno == ENXIO) return -errno; return _mixer_error(mixer, "AUDIO_MIXER_DEVINFO", -errno); } p.dev = control->index; /* XXX this is necessary for some drivers and I don't like it */ if((p.type = md.type) == AUDIO_MIXER_VALUE) p.un.value.num_channels = md.un.v.num_channels; if(ioctl(mixer->fd, AUDIO_MIXER_READ, &p) != 0) return -_mixer_error(mixer, "AUDIO_MIXER_READ", 1); control->type = p.type; # ifdef DEBUG for(u = 0; u < mixer->classes_cnt; u++) if(mixer->classes[u].mixer_class == md.mixer_class) printf("%s", mixer->classes[u].label.name); printf(".%s=", md.label.name); # endif switch(p.type) { case AUDIO_MIXER_ENUM: control->un.ord = p.un.ord; # ifdef DEBUG for(i = 0; i < md.un.e.num_mem; i++) { if(md.un.e.member[i].ord != p.un.ord) continue; printf("%s%s", sep, md.un.e.member[i].label.name); break; } # endif break; case AUDIO_MIXER_SET: control->un.mask = p.un.mask; # ifdef DEBUG for(i = 0; i < md.un.s.num_mem; i++) { if((p.un.mask & (1 << i)) == 0) continue; printf("%s%s", sep, md.un.s.member[i].label.name); sep = ","; } printf("%s", " {"); for(i = 0; i < md.un.s.num_mem; i++) printf(" %s", md.un.s.member[i].label.name); printf("%s", " }"); # endif break; case AUDIO_MIXER_VALUE: u16 = md.un.v.delta; if((u16 = ceil((u16 * 100) / 255.0)) == 0) u16 = 1; control->un.level.delta = u16; control->un.level.channels_cnt = p.un.value.num_channels; for(i = 0; i < p.un.value.num_channels; i++) { # ifdef DEBUG printf("%s%u", sep, p.un.value.level[i]); sep = ","; # endif u16 = p.un.value.level[i]; u16 = ceil((u16 * 100) / 255.0); control->un.level.channels[i] = u16; } #ifdef DEBUG printf(" delta=%u", md.un.v.delta); #endif break; } # ifdef DEBUG putchar('\n'); # endif #else int value; uint16_t u16; if(ioctl(mixer->fd, MIXER_READ(control->index), &value) != 0) return _mixer_error(NULL, "MIXER_READ", -errno); control->type = 0; control->un.level.delta = 1; control->un.level.channels_cnt = 2; u16 = value & 0xff; control->un.level.channels[0] = u16; u16 = (value & 0xff00) >> 8; control->un.level.channels[1] = u16; # ifdef DEBUG fprintf(stderr, "DEBUG: %s() % 3d % 3d\n", __func__, control->un.level.channels[0], control->un.level.channels[1]); # endif #endif return 0; } /* mixer_get_icon */ static String const * _mixer_get_icon(String const * id) { struct { String const * name; String const * icon; } icons[] = { { "beep", "audio-volume-high" }, { "cd", "media-cdrom" }, { "dac", "audio-card" }, { "input", "stock_mic" }, { "line", "stock_volume" }, { "master", "audio-volume-high" }, { "mic", "audio-input-microphone" }, { "monitor", "utilities-system-monitor" }, { "output", "audio-volume-high" }, { "pcm", "audio-volume-high" }, { "rec", "gtk-media-record" }, { "source", "audio-card" }, { "vol", "audio-volume-high" } }; size_t len; size_t i; for(i = 0; i < sizeof(icons) / sizeof(*icons); i++) if(strncmp(icons[i].name, id, string_get_length(icons[i].name)) == 0) return icons[i].icon; len = string_get_length(id); if(string_find(id, "sel") != NULL) return "multimedia"; else if(len > 5 && string_compare(&id[len - 5], ".mute") == 0) return "audio-volume-muted"; return "audio-volume-high"; } /* mixer_set_control */ static int _mixer_set_control(Mixer * mixer, MixerControl2 * control) { #ifdef AUDIO_MIXER_DEVINFO mixer_ctrl_t p; int i; p.dev = control->index; p.type = control->type; p.un.value.num_channels = control->un.level.channels_cnt; for(i = 0; i < p.un.value.num_channels; i++) p.un.value.level[i] = control->un.level.channels[i]; if(ioctl(mixer->fd, AUDIO_MIXER_WRITE, &p) != 0) return -_mixer_error(mixer, "AUDIO_MIXER_WRITE", 1); #else int level; level = (control->un.level.channels[1] << 8) | control->un.level.channels[0]; # ifdef DEBUG fprintf(stderr, "DEBUG: %s() level=0x%04x\n", __func__, level); # endif if(ioctl(mixer->fd, MIXER_WRITE(control->index), &level) != 0) return -_mixer_error(mixer, "MIXER_WRITE", 1); #endif return 0; } /* mixer_set_control_widget */ static int _set_control_widget_channels(MixerControl2 * control); static int _set_control_widget_mute(MixerControl2 * control); static int _set_control_widget_radio(MixerControl2 * control); static int _set_control_widget_set(MixerControl2 * control); static int _mixer_set_control_widget(Mixer * mixer, MixerControl2 * control) { String const * type; (void) mixer; if((type = mixercontrol_get_type(control->control)) == NULL) /* XXX report error */ return -1; if(string_compare(type, "channels") == 0) return _set_control_widget_channels(control); if(string_compare(type, "mute") == 0) return _set_control_widget_mute(control); if(string_compare(type, "radio") == 0) return _set_control_widget_radio(control); if(string_compare(type, "set") == 0) return _set_control_widget_set(control); return -1; } static int _set_control_widget_channels(MixerControl2 * control) { gboolean bind = TRUE; gdouble value; size_t i; char buf[16]; /* unset bind if the channels are no longer synchronized */ for(i = 1; i < control->un.level.channels_cnt; i++) if(control->un.level.channels[i] != control->un.level.channels[i - 1]) { bind = FALSE; break; } if(bind == FALSE) if(mixercontrol_set(control->control, "bind", FALSE, NULL) != 0) return -1; /* set the individual channels */ for(i = 0; i < control->un.level.channels_cnt; i++) { snprintf(buf, sizeof(buf), "value%zu", i); value = control->un.level.channels[i]; if(mixercontrol_set(control->control, buf, value, NULL) != 0) return -1; } return 0; } static int _set_control_widget_mute(MixerControl2 * control) { /* FIXME implement */ return -1; } static int _set_control_widget_radio(MixerControl2 * control) { return mixercontrol_set(control->control, "value", control->un.mask, NULL); } static int _set_control_widget_set(MixerControl2 * control) { return mixercontrol_set(control->control, "value", control->un.mask, NULL); } /* useful */ /* mixer_refresh_control */ static int _mixer_refresh_control(Mixer * mixer, MixerControl2 * control) { int ret; if((ret = _mixer_get_control(mixer, control)) != 0) { if(ret == -ENXIO) mixercontrol_disable(control->control); return ret; } if((ret = _mixer_set_control_widget(mixer, control)) == 0) mixercontrol_enable(control->control); return ret; } /* mixer_scrolled_window_add */ static void _mixer_scrolled_window_add(GtkWidget * window, GtkWidget * widget) { GtkWidget * viewport; viewport = gtk_viewport_new(NULL, NULL); gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE); gtk_container_add(GTK_CONTAINER(viewport), widget); gtk_container_add(GTK_CONTAINER(window), viewport); } /* mixer_show_view */ static void _mixer_show_view(Mixer * mixer, int view) { #ifdef AUDIO_MIXER_DEVINFO size_t u; if(view < 0) { for(u = 0; u < mixer->classes_cnt; u++) if(mixer->classes[u].hbox != NULL) gtk_widget_show(mixer->classes[u].hbox); return; } u = view; if(u >= mixer->classes_cnt) return; for(u = 0; u < mixer->classes_cnt; u++) if(mixer->classes[u].hbox == NULL) continue; else if(u == (size_t)view) gtk_widget_show(mixer->classes[u].hbox); else gtk_widget_hide(mixer->classes[u].hbox); #endif }