Mixer

/* $Id$ */
/* Copyright (c) 2009-2020 Pierre Pronchery <khorben@defora.org> */
/* 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 <sys/ioctl.h>
# include <sys/audioio.h>
#else
# include <sys/ioctl.h>
# include <sys/soundcard.h>
#endif
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <libintl.h>
#include <gtk/gtk.h>
#include <Desktop.h>
#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
}