gnome-control-center/panels/sound/gvc-mixer-dialog.c
Matthias Clasen 3d27d8caaf shell: Fittsify panels
Make it possible for panels to go all the way to the edge of the
shell. This is particularly important for panels that scroll, such
as the new power panel. All other panels are changed to compensate
for the loss of external padding.

https://bugzilla.gnome.org/show_bug.cgi?id=691229
2013-01-07 10:51:29 +01:00

1982 lines
76 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2008 William Jon McCann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <math.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <pulse/pulseaudio.h>
#include "gvc-channel-bar.h"
#include "gvc-balance-bar.h"
#include "gvc-combo-box.h"
#include "gvc-mixer-control.h"
#include "gvc-mixer-card.h"
#include "gvc-mixer-sink.h"
#include "gvc-mixer-source.h"
#include "gvc-mixer-source-output.h"
#include "gvc-mixer-dialog.h"
#include "gvc-sound-theme-chooser.h"
#include "gvc-level-bar.h"
#include "gvc-speaker-test.h"
#include "gvc-mixer-control-private.h"
#define SCALE_SIZE 128
#define GVC_MIXER_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_DIALOG, GvcMixerDialogPrivate))
struct GvcMixerDialogPrivate
{
GvcMixerControl *mixer_control;
GHashTable *bars; /* Application and event bars only */
GtkWidget *notebook;
GtkWidget *output_bar;
GtkWidget *input_bar;
GtkWidget *input_level_bar;
GtkWidget *effects_bar;
GtkWidget *output_stream_box;
GtkWidget *sound_effects_box;
GtkWidget *input_box;
GtkWidget *output_box;
GtkWidget *applications_box;
GtkWidget *no_apps_label;
GtkWidget *output_treeview;
GtkWidget *output_settings_box;
GtkWidget *output_balance_bar;
GtkWidget *output_fade_bar;
GtkWidget *output_lfe_bar;
GtkWidget *output_profile_combo;
GtkWidget *input_treeview;
GtkWidget *input_profile_combo;
GtkWidget *input_settings_box;
GtkWidget *sound_theme_chooser;
GtkWidget *click_feedback_button;
GtkWidget *audible_bell_button;
GtkSizeGroup *size_group;
gdouble last_input_peak;
guint num_apps;
};
enum {
NAME_COLUMN,
DEVICE_COLUMN,
ACTIVE_COLUMN,
ID_COLUMN,
ICON_COLUMN,
NUM_COLUMNS
};
enum {
HW_ID_COLUMN,
HW_ICON_COLUMN,
HW_NAME_COLUMN,
HW_STATUS_COLUMN,
HW_PROFILE_COLUMN,
HW_PROFILE_HUMAN_COLUMN,
HW_SENSITIVE_COLUMN,
HW_NUM_COLUMNS
};
enum
{
PROP_0,
PROP_MIXER_CONTROL
};
static void gvc_mixer_dialog_class_init (GvcMixerDialogClass *klass);
static void gvc_mixer_dialog_init (GvcMixerDialog *mixer_dialog);
static void gvc_mixer_dialog_finalize (GObject *object);
static void bar_set_stream (GvcMixerDialog *dialog,
GtkWidget *bar,
GvcMixerStream *stream);
static void on_adjustment_value_changed (GtkAdjustment *adjustment,
GvcMixerDialog *dialog);
static void on_control_active_output_update (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog);
static void on_control_active_input_update (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog);
static void on_test_speakers_clicked (GvcComboBox *widget,
gpointer user_data);
G_DEFINE_TYPE (GvcMixerDialog, gvc_mixer_dialog, GTK_TYPE_VBOX)
static void
profile_selection_changed (GvcComboBox *combo_box,
const char *profile,
GvcMixerDialog *dialog)
{
GvcMixerUIDevice *output;
g_debug ("profile_selection_changed() to %s", profile);
output = g_object_get_data (G_OBJECT (combo_box), "uidevice");
if (output == NULL) {
g_warning ("Could not find Output for profile combo box");
return;
}
g_debug ("on profile selection changed on output '%s' (origin: %s, id: %i)",
gvc_mixer_ui_device_get_description (output),
gvc_mixer_ui_device_get_origin (output),
gvc_mixer_ui_device_get_id (output));
if (gvc_mixer_control_change_profile_on_selected_device (dialog->priv->mixer_control, output, profile) == FALSE) {
g_warning ("Could not change profile on device %s",
gvc_mixer_ui_device_get_description (output));
}
}
static void
update_output_settings (GvcMixerDialog *dialog,
GvcMixerUIDevice *device)
{
GvcMixerStream *stream;
const GvcChannelMap *map;
const GList *profiles;
GtkAdjustment *adj;
g_debug ("Updating output settings");
if (dialog->priv->output_balance_bar != NULL) {
gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box),
dialog->priv->output_balance_bar);
dialog->priv->output_balance_bar = NULL;
}
if (dialog->priv->output_fade_bar != NULL) {
gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box),
dialog->priv->output_fade_bar);
dialog->priv->output_fade_bar = NULL;
}
if (dialog->priv->output_lfe_bar != NULL) {
gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box),
dialog->priv->output_lfe_bar);
dialog->priv->output_lfe_bar = NULL;
}
if (dialog->priv->output_profile_combo != NULL) {
gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box),
dialog->priv->output_profile_combo);
dialog->priv->output_profile_combo = NULL;
}
stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control,
device);
if (stream == NULL) {
g_warning ("Default sink stream not found");
return;
}
gvc_channel_bar_set_base_volume (GVC_CHANNEL_BAR (dialog->priv->output_bar),
gvc_mixer_stream_get_base_volume (stream));
gvc_channel_bar_set_is_amplified (GVC_CHANNEL_BAR (dialog->priv->output_bar),
gvc_mixer_stream_get_can_decibel (stream));
/* Update the adjustment in case the previous bar wasn't decibel
* capable, and we clipped it */
adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (dialog->priv->output_bar)));
gtk_adjustment_set_value (adj,
gvc_mixer_stream_get_volume (stream));
map = gvc_mixer_stream_get_channel_map (stream);
if (map == NULL) {
g_warning ("Default sink stream has no channel map");
return;
}
dialog->priv->output_balance_bar = gvc_balance_bar_new (map, BALANCE_TYPE_RL);
if (dialog->priv->size_group != NULL) {
gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_balance_bar),
dialog->priv->size_group,
TRUE);
}
gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box),
dialog->priv->output_balance_bar,
FALSE, FALSE, 6);
gtk_widget_show (dialog->priv->output_balance_bar);
if (gvc_channel_map_can_fade (map)) {
dialog->priv->output_fade_bar = gvc_balance_bar_new (map, BALANCE_TYPE_FR);
if (dialog->priv->size_group != NULL) {
gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_fade_bar),
dialog->priv->size_group,
TRUE);
}
gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box),
dialog->priv->output_fade_bar,
FALSE, FALSE, 6);
gtk_widget_show (dialog->priv->output_fade_bar);
}
if (gvc_channel_map_has_lfe (map)) {
dialog->priv->output_lfe_bar = gvc_balance_bar_new (map, BALANCE_TYPE_LFE);
if (dialog->priv->size_group != NULL) {
gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_lfe_bar),
dialog->priv->size_group,
TRUE);
}
gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box),
dialog->priv->output_lfe_bar,
FALSE, FALSE, 6);
gtk_widget_show (dialog->priv->output_lfe_bar);
}
profiles = gvc_mixer_ui_device_get_profiles (device);
/* FIXME: How do we make sure the "Test speakers" button is shown
* even when there are no profiles to choose between? */
if (TRUE /*g_list_length((GList *) profiles) >= 2 */) {
const gchar *active_profile;
dialog->priv->output_profile_combo = gvc_combo_box_new (_("_Profile:"));
g_object_set (G_OBJECT (dialog->priv->output_profile_combo), "button-label", _("_Test Speakers"), NULL);
g_object_set (G_OBJECT (dialog->priv->output_profile_combo),
"show-button", TRUE, NULL);
g_signal_connect (G_OBJECT (dialog->priv->output_profile_combo), "button-clicked",
G_CALLBACK (on_test_speakers_clicked), dialog);
if (profiles)
gvc_combo_box_set_profiles (GVC_COMBO_BOX (dialog->priv->output_profile_combo),
profiles);
gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box),
dialog->priv->output_profile_combo,
TRUE, FALSE, 6);
if (dialog->priv->size_group != NULL) {
gvc_combo_box_set_size_group (GVC_COMBO_BOX (dialog->priv->output_profile_combo),
dialog->priv->size_group, FALSE);
}
active_profile = gvc_mixer_ui_device_get_active_profile (device);
if (active_profile)
gvc_combo_box_set_active (GVC_COMBO_BOX (dialog->priv->output_profile_combo), active_profile);
g_object_set_data (G_OBJECT (dialog->priv->output_profile_combo),
"uidevice",
device);
if (g_list_length((GList *) profiles))
g_signal_connect (G_OBJECT (dialog->priv->output_profile_combo), "changed",
G_CALLBACK (profile_selection_changed), dialog);
gtk_widget_show (dialog->priv->output_profile_combo);
}
/* FIXME: We could make this into a "No settings" label instead */
gtk_widget_set_sensitive (dialog->priv->output_balance_bar, gvc_channel_map_can_balance (map));
}
#define DECAY_STEP .15
static void
update_input_peak (GvcMixerDialog *dialog,
gdouble v)
{
GtkAdjustment *adj;
if (dialog->priv->last_input_peak >= DECAY_STEP) {
if (v < dialog->priv->last_input_peak - DECAY_STEP) {
v = dialog->priv->last_input_peak - DECAY_STEP;
}
}
dialog->priv->last_input_peak = v;
adj = gvc_level_bar_get_peak_adjustment (GVC_LEVEL_BAR (dialog->priv->input_level_bar));
if (v >= 0) {
gtk_adjustment_set_value (adj, v);
} else {
gtk_adjustment_set_value (adj, 0.0);
}
}
static void
update_input_meter (GvcMixerDialog *dialog,
uint32_t source_index,
uint32_t sink_input_idx,
double v)
{
update_input_peak (dialog, v);
}
static void
on_monitor_suspended_callback (pa_stream *s,
void *userdata)
{
GvcMixerDialog *dialog;
dialog = userdata;
if (pa_stream_is_suspended (s)) {
g_debug ("Stream suspended");
update_input_meter (dialog,
pa_stream_get_device_index (s),
PA_INVALID_INDEX,
-1);
}
}
static void
on_monitor_read_callback (pa_stream *s,
size_t length,
void *userdata)
{
GvcMixerDialog *dialog;
const void *data;
double v;
dialog = userdata;
if (pa_stream_peek (s, &data, &length) < 0) {
g_warning ("Failed to read data from stream");
return;
}
if (!data) {
pa_stream_drop (s);
return;
}
assert (length > 0);
assert (length % sizeof (float) == 0);
v = ((const float *) data)[length / sizeof (float) -1];
pa_stream_drop (s);
if (v < 0) {
v = 0;
}
if (v > 1) {
v = 1;
}
update_input_meter (dialog,
pa_stream_get_device_index (s),
pa_stream_get_monitor_stream (s),
v);
}
static void
create_monitor_stream_for_source (GvcMixerDialog *dialog,
GvcMixerStream *stream)
{
pa_stream *s;
char t[16];
pa_buffer_attr attr;
pa_sample_spec ss;
pa_context *context;
int res;
pa_proplist *proplist;
gboolean has_monitor;
if (stream == NULL) {
return;
}
has_monitor = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (stream), "has-monitor"));
if (has_monitor != FALSE) {
return;
}
g_debug ("Create monitor for %u",
gvc_mixer_stream_get_index (stream));
context = gvc_mixer_control_get_pa_context (dialog->priv->mixer_control);
if (pa_context_get_server_protocol_version (context) < 13) {
return;
}
ss.channels = 1;
ss.format = PA_SAMPLE_FLOAT32;
ss.rate = 25;
memset (&attr, 0, sizeof (attr));
attr.fragsize = sizeof (float);
attr.maxlength = (uint32_t) -1;
snprintf (t, sizeof (t), "%u", gvc_mixer_stream_get_index (stream));
proplist = pa_proplist_new ();
pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "org.gnome.VolumeControl");
s = pa_stream_new_with_proplist (context, _("Peak detect"), &ss, NULL, proplist);
pa_proplist_free (proplist);
if (s == NULL) {
g_warning ("Failed to create monitoring stream");
return;
}
pa_stream_set_read_callback (s, on_monitor_read_callback, dialog);
pa_stream_set_suspended_callback (s, on_monitor_suspended_callback, dialog);
res = pa_stream_connect_record (s,
t,
&attr,
(pa_stream_flags_t) (PA_STREAM_DONT_MOVE
|PA_STREAM_PEAK_DETECT
|PA_STREAM_ADJUST_LATENCY));
if (res < 0) {
g_warning ("Failed to connect monitoring stream");
pa_stream_unref (s);
} else {
g_object_set_data (G_OBJECT (stream), "has-monitor", GINT_TO_POINTER (TRUE));
g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "pa_stream", s);
g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "stream", stream);
}
}
static void
stop_monitor_stream_for_source (GvcMixerDialog *dialog)
{
pa_stream *s;
pa_context *context;
int res;
GvcMixerStream *stream;
s = g_object_get_data (G_OBJECT (dialog->priv->input_level_bar), "pa_stream");
if (s == NULL)
return;
stream = g_object_get_data (G_OBJECT (dialog->priv->input_level_bar), "stream");
g_assert (stream != NULL);
g_debug ("Stopping monitor for %u", pa_stream_get_index (s));
context = gvc_mixer_control_get_pa_context (dialog->priv->mixer_control);
if (pa_context_get_server_protocol_version (context) < 13) {
return;
}
res = pa_stream_disconnect (s);
if (res == 0)
g_object_set_data (G_OBJECT (stream), "has-monitor", GINT_TO_POINTER (FALSE));
g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "pa_stream", NULL);
g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "stream", NULL);
}
static void
update_input_settings (GvcMixerDialog *dialog,
GvcMixerUIDevice *device)
{
GvcMixerStream *stream;
const GList *profiles;
GtkAdjustment *adj;
g_debug ("Updating input settings");
stop_monitor_stream_for_source (dialog);
if (dialog->priv->input_profile_combo != NULL) {
gtk_container_remove (GTK_CONTAINER (dialog->priv->input_settings_box),
dialog->priv->input_profile_combo);
dialog->priv->input_profile_combo = NULL;
}
stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control,
device);
if (stream == NULL) {
g_debug ("Default source stream not found");
return;
}
gvc_channel_bar_set_base_volume (GVC_CHANNEL_BAR (dialog->priv->input_bar),
gvc_mixer_stream_get_base_volume (stream));
gvc_channel_bar_set_is_amplified (GVC_CHANNEL_BAR (dialog->priv->input_bar),
gvc_mixer_stream_get_can_decibel (stream));
/* Update the adjustment in case the previous bar wasn't decibel
* capable, and we clipped it */
adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (dialog->priv->input_bar)));
gtk_adjustment_set_value (adj,
gvc_mixer_stream_get_volume (stream));
profiles = gvc_mixer_ui_device_get_profiles (device);
if (g_list_length ((GList *) profiles) >= 2) {
const gchar *active_profile;
dialog->priv->input_profile_combo = gvc_combo_box_new (_("_Profile:"));
gvc_combo_box_set_profiles (GVC_COMBO_BOX (dialog->priv->input_profile_combo),
profiles);
gtk_box_pack_start (GTK_BOX (dialog->priv->input_settings_box),
dialog->priv->input_profile_combo,
TRUE, TRUE, 0);
if (dialog->priv->size_group != NULL) {
gvc_combo_box_set_size_group (GVC_COMBO_BOX (dialog->priv->input_profile_combo),
dialog->priv->size_group, FALSE);
}
active_profile = gvc_mixer_ui_device_get_active_profile (device);
if (active_profile)
gvc_combo_box_set_active (GVC_COMBO_BOX (dialog->priv->input_profile_combo), active_profile);
g_object_set_data (G_OBJECT (dialog->priv->input_profile_combo),
"uidevice",
device);
g_signal_connect (G_OBJECT (dialog->priv->input_profile_combo), "changed",
G_CALLBACK (profile_selection_changed), dialog);
gtk_widget_show (dialog->priv->input_profile_combo);
}
create_monitor_stream_for_source (dialog, stream);
}
static void
gvc_mixer_dialog_set_mixer_control (GvcMixerDialog *dialog,
GvcMixerControl *control)
{
g_return_if_fail (GVC_MIXER_DIALOG (dialog));
g_return_if_fail (GVC_IS_MIXER_CONTROL (control));
g_object_ref (control);
if (dialog->priv->mixer_control != NULL) {
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
G_CALLBACK (on_control_active_input_update),
dialog);
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
G_CALLBACK (on_control_active_output_update),
dialog);
g_object_unref (dialog->priv->mixer_control);
}
dialog->priv->mixer_control = control;
/* FIXME: Why are some mixer_control signals connected here,
* and others in the dialog constructor? (And similar for disconnect) */
g_signal_connect (dialog->priv->mixer_control,
"active-input-update",
G_CALLBACK (on_control_active_input_update),
dialog);
g_signal_connect (dialog->priv->mixer_control,
"active-output-update",
G_CALLBACK (on_control_active_output_update),
dialog);
g_object_notify (G_OBJECT (dialog), "mixer-control");
}
static GvcMixerControl *
gvc_mixer_dialog_get_mixer_control (GvcMixerDialog *dialog)
{
g_return_val_if_fail (GVC_IS_MIXER_DIALOG (dialog), NULL);
return dialog->priv->mixer_control;
}
static void
gvc_mixer_dialog_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GvcMixerDialog *self = GVC_MIXER_DIALOG (object);
switch (prop_id) {
case PROP_MIXER_CONTROL:
gvc_mixer_dialog_set_mixer_control (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gvc_mixer_dialog_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GvcMixerDialog *self = GVC_MIXER_DIALOG (object);
switch (prop_id) {
case PROP_MIXER_CONTROL:
g_value_set_object (value, gvc_mixer_dialog_get_mixer_control (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
on_adjustment_value_changed (GtkAdjustment *adjustment,
GvcMixerDialog *dialog)
{
GvcMixerStream *stream;
stream = g_object_get_data (G_OBJECT (adjustment), "gvc-mixer-dialog-stream");
if (stream != NULL) {
GObject *bar;
gdouble volume, rounded;
char *name;
volume = gtk_adjustment_get_value (adjustment);
rounded = round (volume);
bar = g_object_get_data (G_OBJECT (adjustment), "gvc-mixer-dialog-bar");
g_object_get (bar, "name", &name, NULL);
g_debug ("Setting stream volume %lf (rounded: %lf) for bar '%s'", volume, rounded, name);
g_free (name);
/* FIXME would need to do that in the balance bar really... */
/* Make sure we do not unmute muted streams, there's a button for that */
if (volume == 0.0)
gvc_mixer_stream_set_is_muted (stream, TRUE);
/* Only push the volume if it's actually changed */
if (gvc_mixer_stream_set_volume (stream, (pa_volume_t) rounded) != FALSE)
gvc_mixer_stream_push_volume (stream);
}
}
static void
on_bar_is_muted_notify (GObject *object,
GParamSpec *pspec,
GvcMixerDialog *dialog)
{
gboolean is_muted;
GvcMixerStream *stream;
is_muted = gvc_channel_bar_get_is_muted (GVC_CHANNEL_BAR (object));
stream = g_object_get_data (object, "gvc-mixer-dialog-stream");
if (stream != NULL) {
gvc_mixer_stream_change_is_muted (stream, is_muted);
} else {
char *name;
g_object_get (object, "name", &name, NULL);
g_warning ("Unable to find stream for bar '%s'", name);
g_free (name);
}
}
static GtkWidget *
lookup_bar_for_stream (GvcMixerDialog *dialog,
GvcMixerStream *stream)
{
GtkWidget *bar;
bar = g_hash_table_lookup (dialog->priv->bars, GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)));
return bar;
}
static void
on_stream_volume_notify (GObject *object,
GParamSpec *pspec,
GvcMixerDialog *dialog)
{
GvcMixerStream *stream;
GtkWidget *bar;
GtkAdjustment *adj;
stream = GVC_MIXER_STREAM (object);
bar = lookup_bar_for_stream (dialog, stream);
if (bar == NULL) {
g_warning ("Unable to find bar for stream %s in on_stream_volume_notify()",
gvc_mixer_stream_get_name (stream));
return;
}
adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (bar)));
g_signal_handlers_block_by_func (adj,
on_adjustment_value_changed,
dialog);
gtk_adjustment_set_value (adj,
gvc_mixer_stream_get_volume (stream));
g_signal_handlers_unblock_by_func (adj,
on_adjustment_value_changed,
dialog);
}
static void
on_stream_is_muted_notify (GObject *object,
GParamSpec *pspec,
GvcMixerDialog *dialog)
{
GvcMixerStream *stream;
GtkWidget *bar;
gboolean is_muted;
stream = GVC_MIXER_STREAM (object);
bar = lookup_bar_for_stream (dialog, stream);
if (bar == NULL) {
g_warning ("Unable to find bar for stream %s in on_stream_is_muted_notify()",
gvc_mixer_stream_get_name (stream));
return;
}
is_muted = gvc_mixer_stream_get_is_muted (stream);
gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (bar),
is_muted);
if (stream == gvc_mixer_control_get_default_sink (dialog->priv->mixer_control)) {
gtk_widget_set_sensitive (dialog->priv->applications_box,
!is_muted);
}
}
static void
save_bar_for_stream (GvcMixerDialog *dialog,
GvcMixerStream *stream,
GtkWidget *bar)
{
g_hash_table_insert (dialog->priv->bars,
GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)),
bar);
}
static GtkWidget *
create_bar (GvcMixerDialog *dialog,
gboolean add_to_size_group,
gboolean symmetric)
{
GtkWidget *bar;
bar = gvc_channel_bar_new ();
gtk_widget_set_sensitive (bar, FALSE);
if (add_to_size_group && dialog->priv->size_group != NULL) {
gvc_channel_bar_set_size_group (GVC_CHANNEL_BAR (bar),
dialog->priv->size_group,
symmetric);
}
gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (bar),
GTK_ORIENTATION_HORIZONTAL);
gvc_channel_bar_set_show_mute (GVC_CHANNEL_BAR (bar),
TRUE);
g_signal_connect (bar,
"notify::is-muted",
G_CALLBACK (on_bar_is_muted_notify),
dialog);
return bar;
}
static GtkWidget *
create_app_bar (GvcMixerDialog *dialog,
const char *name,
const char *icon_name)
{
GtkWidget *bar;
bar = create_bar (dialog, FALSE, FALSE);
gvc_channel_bar_set_ellipsize (GVC_CHANNEL_BAR (bar), TRUE);
gvc_channel_bar_set_icon_name (GVC_CHANNEL_BAR (bar), icon_name);
if (name == NULL || strchr (name, '_') == NULL) {
gvc_channel_bar_set_name (GVC_CHANNEL_BAR (bar), name);
} else {
char **tokens, *escaped;
tokens = g_strsplit (name, "_", -1);
escaped = g_strjoinv ("__", tokens);
g_strfreev (tokens);
gvc_channel_bar_set_name (GVC_CHANNEL_BAR (bar), escaped);
g_free (escaped);
}
return bar;
}
/* active_input_update
* Handle input update change from the backend (control).
* Trust the backend whole-heartedly to deliver the correct input. */
static void
active_input_update (GvcMixerDialog *dialog,
GvcMixerUIDevice *active_input)
{
/* First make sure the correct UI device is selected. */
GtkTreeModel *model;
GtkTreeIter iter;
GvcMixerStream *stream;
g_debug ("active_input_update device id = %i",
gvc_mixer_ui_device_get_id (active_input));
model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
if (gtk_tree_model_get_iter_first (model, &iter) == FALSE) {
g_warning ("No devices in the tree, so cannot set the active output");
return;
}
do {
gboolean is_selected = FALSE;
gint id;
gtk_tree_model_get (model, &iter,
ID_COLUMN, &id,
-1);
is_selected = id == gvc_mixer_ui_device_get_id (active_input);
gtk_list_store_set (GTK_LIST_STORE (model),
&iter,
ACTIVE_COLUMN, is_selected,
-1);
if (is_selected) {
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->input_treeview));
gtk_tree_selection_select_iter (selection, &iter);
}
} while (gtk_tree_model_iter_next (model, &iter));
stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control,
active_input);
if (stream == NULL) {
g_warning ("Couldn't find a stream from the active input");
gtk_widget_set_sensitive (dialog->priv->input_bar, FALSE);
return;
}
bar_set_stream (dialog, dialog->priv->input_bar, stream);
update_input_settings (dialog, active_input);
}
/* active_output_update
* Handle output update change from the backend (control).
* Trust the backend whole heartedly to deliver the correct output. */
static void
active_output_update (GvcMixerDialog *dialog,
GvcMixerUIDevice *active_output)
{
/* First make sure the correct UI device is selected. */
GvcMixerStream *stream;
GtkTreeModel *model;
GtkTreeIter iter;
g_debug ("active output update device id = %i",
gvc_mixer_ui_device_get_id (active_output));
model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
if (gtk_tree_model_get_iter_first (model, &iter) == FALSE){
g_warning ("No devices in the tree, so cannot set the active output");
return;
}
do {
gboolean is_selected;
gint id;
gtk_tree_model_get (model, &iter,
ID_COLUMN, &id,
ACTIVE_COLUMN, &is_selected,
-1);
if (is_selected && id == gvc_mixer_ui_device_get_id (active_output)) {
/* XXX: profile change on the same device? */
g_debug ("Unneccessary active output update");
}
is_selected = id == gvc_mixer_ui_device_get_id (active_output);
gtk_list_store_set (GTK_LIST_STORE (model),
&iter,
ACTIVE_COLUMN, is_selected,
-1);
if (is_selected) {
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->output_treeview));
gtk_tree_selection_select_iter (selection, &iter);
}
} while (gtk_tree_model_iter_next (model, &iter));
stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control,
active_output);
if (stream == NULL) {
g_warning ("Couldn't find a stream from the active output");
return;
}
bar_set_stream (dialog, dialog->priv->output_bar, stream);
update_output_settings (dialog, active_output);
}
static void
bar_set_stream (GvcMixerDialog *dialog,
GtkWidget *bar,
GvcMixerStream *stream)
{
GtkAdjustment *adj;
GvcMixerStream *old_stream;
g_assert (bar != NULL);
old_stream = g_object_get_data (G_OBJECT (bar), "gvc-mixer-dialog-stream");
if (old_stream != NULL) {
char *name;
g_object_get (bar, "name", &name, NULL);
g_debug ("Disconnecting old stream '%s' from bar '%s'",
gvc_mixer_stream_get_name (old_stream), name);
g_free (name);
g_signal_handlers_disconnect_by_func (old_stream, on_stream_is_muted_notify, dialog);
g_signal_handlers_disconnect_by_func (old_stream, on_stream_volume_notify, dialog);
g_hash_table_remove (dialog->priv->bars, GUINT_TO_POINTER (gvc_mixer_stream_get_id (old_stream)));
}
gtk_widget_set_sensitive (bar, (stream != NULL));
adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (bar)));
g_signal_handlers_disconnect_by_func (adj, on_adjustment_value_changed, dialog);
g_object_set_data (G_OBJECT (bar), "gvc-mixer-dialog-stream", stream);
g_object_set_data (G_OBJECT (bar), "gvc-mixer-dialog-stream-id",
GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)));
g_object_set_data (G_OBJECT (adj), "gvc-mixer-dialog-stream", stream);
g_object_set_data (G_OBJECT (adj), "gvc-mixer-dialog-bar", bar);
if (stream != NULL) {
gboolean is_muted;
is_muted = gvc_mixer_stream_get_is_muted (stream);
gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (bar), is_muted);
gtk_adjustment_set_value (adj,
gvc_mixer_stream_get_volume (stream));
g_signal_connect (stream,
"notify::is-muted",
G_CALLBACK (on_stream_is_muted_notify),
dialog);
g_signal_connect (stream,
"notify::volume",
G_CALLBACK (on_stream_volume_notify),
dialog);
g_signal_connect (adj,
"value-changed",
G_CALLBACK (on_adjustment_value_changed),
dialog);
}
}
static void
add_stream (GvcMixerDialog *dialog,
GvcMixerStream *stream)
{
GtkWidget *bar;
GvcMixerStream *old_stream;
bar = NULL;
if (GVC_IS_MIXER_SOURCE (stream) || GVC_IS_MIXER_SINK (stream))
return;
else if (stream == gvc_mixer_control_get_event_sink_input (dialog->priv->mixer_control)) {
bar = dialog->priv->effects_bar;
g_debug ("Adding effects stream");
} else {
/* Must be an application stream */
const char *name;
name = gvc_mixer_stream_get_name (stream);
g_debug ("Add bar for application stream : %s", name);
bar = create_app_bar (dialog, name,
gvc_mixer_stream_get_icon_name (stream));
gtk_box_pack_start (GTK_BOX (dialog->priv->applications_box), bar, FALSE, FALSE, 12);
dialog->priv->num_apps++;
gtk_widget_hide (dialog->priv->no_apps_label);
}
/* We should have a bar by now. */
g_assert (bar != NULL);
if (bar != NULL) {
old_stream = g_object_get_data (G_OBJECT (bar), "gvc-mixer-dialog-stream");
if (old_stream != NULL) {
char *name;
g_object_get (bar, "name", &name, NULL);
g_debug ("Disconnecting old stream '%s' from bar '%s'",
gvc_mixer_stream_get_name (old_stream), name);
g_free (name);
g_signal_handlers_disconnect_by_func (old_stream, on_stream_is_muted_notify, dialog);
g_signal_handlers_disconnect_by_func (old_stream, on_stream_volume_notify, dialog);
g_hash_table_remove (dialog->priv->bars, GUINT_TO_POINTER (gvc_mixer_stream_get_id (old_stream)));
}
save_bar_for_stream (dialog, stream, bar);
bar_set_stream (dialog, bar, stream);
gtk_widget_show (bar);
}
}
static void
on_control_stream_added (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog)
{
GvcMixerStream *stream;
const char *app_id;
stream = gvc_mixer_control_lookup_stream_id (control, id);
if (stream == NULL)
return;
app_id = gvc_mixer_stream_get_application_id (stream);
if (stream == gvc_mixer_control_get_event_sink_input (dialog->priv->mixer_control) ||
(GVC_IS_MIXER_SOURCE (stream) == FALSE &&
GVC_IS_MIXER_SINK (stream) == FALSE &&
gvc_mixer_stream_is_virtual (stream) == FALSE &&
g_strcmp0 (app_id, "org.gnome.VolumeControl") != 0 &&
g_strcmp0 (app_id, "org.PulseAudio.pavucontrol") != 0)) {
GtkWidget *bar;
bar = g_hash_table_lookup (dialog->priv->bars, GUINT_TO_POINTER (id));
if (bar != NULL) {
g_debug ("GvcMixerDialog: Stream %u already added", id);
return;
}
add_stream (dialog, stream);
}
}
static gboolean
find_item_by_id (GtkTreeModel *model,
guint id,
guint column,
GtkTreeIter *iter)
{
gboolean found_item;
found_item = FALSE;
if (!gtk_tree_model_get_iter_first (model, iter)) {
return FALSE;
}
do {
guint t_id;
gtk_tree_model_get (model, iter,
column, &t_id, -1);
if (id == t_id) {
found_item = TRUE;
}
} while (!found_item && gtk_tree_model_iter_next (model, iter));
return found_item;
}
static void
add_input_ui_entry (GvcMixerDialog *dialog,
GvcMixerUIDevice *input)
{
gchar *final_name;
gchar *port_name;
gchar *origin;
gchar *description;
gboolean available;
gint stream_id;
GtkTreeModel *model;
GtkTreeIter iter;
GIcon *icon;
GvcMixerCard *card;
g_debug ("Add input ui entry with id :%u",
gvc_mixer_ui_device_get_id (input));
g_object_get (G_OBJECT (input),
"stream-id", &stream_id,
"card", &card,
"origin", &origin,
"description", &description,
"port-name", &port_name,
"port-available", &available,
NULL);
if (origin && origin[0] != '\0')
final_name = g_strdup_printf ("%s - %s", description, origin);
else
final_name = g_strdup (description);
g_free (port_name);
g_free (origin);
g_free (description);
if (card == NULL) {
GvcMixerStream *stream;
g_debug ("just detected a network source");
stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control, input);
if (stream == NULL) {
g_warning ("tried to add the network source but the stream was null - fail ?!");
g_free (final_name);
return;
}
icon = gvc_mixer_stream_get_gicon (stream);
} else
icon = gvc_mixer_card_get_gicon (card);
model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model),
&iter,
NAME_COLUMN, final_name,
DEVICE_COLUMN, "",
ACTIVE_COLUMN, FALSE,
ICON_COLUMN, icon,
ID_COLUMN, gvc_mixer_ui_device_get_id (input),
-1);
if (icon != NULL)
g_object_unref (icon);
g_free (final_name);
}
static void
add_output_ui_entry (GvcMixerDialog *dialog,
GvcMixerUIDevice *output)
{
gchar *sink_port_name;
gchar *origin;
gchar *description;
gchar *final_name;
gboolean available;
gint sink_stream_id;
GtkTreeModel *model;
GtkTreeIter iter;
GIcon *icon;
GvcMixerCard *card;
g_debug ("Add output ui entry with id :%u",
gvc_mixer_ui_device_get_id (output));
g_object_get (G_OBJECT (output),
"stream-id", &sink_stream_id,
"card", &card,
"origin", &origin,
"description", &description,
"port-name", &sink_port_name,
"port-available", &available,
NULL);
if (origin && origin[0] != '\0')
final_name = g_strdup_printf ("%s - %s", description, origin);
else
final_name = g_strdup (description);
g_free (sink_port_name);
g_free (origin);
g_free (description);
if (card == NULL) {
GvcMixerStream *stream;
g_debug ("just detected a network sink");
stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control, output);
if (stream == NULL) {
g_warning ("tried to add the network sink but the stream was null - fail ?!");
g_free (final_name);
return;
}
icon = gvc_mixer_stream_get_gicon (stream);
} else
icon = gvc_mixer_card_get_gicon (card);
model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model),
&iter,
NAME_COLUMN, final_name,
DEVICE_COLUMN, "",
ACTIVE_COLUMN, FALSE,
ICON_COLUMN, icon,
ID_COLUMN, gvc_mixer_ui_device_get_id (output),
-1);
if (icon != NULL)
g_object_unref (icon);
g_free (final_name);
}
static void
on_control_active_input_update (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog)
{
GvcMixerUIDevice* in = NULL;
in = gvc_mixer_control_lookup_input_id (control, id);
if (in == NULL) {
g_warning ("on_control_active_input_update - tried to fetch an input of id %u but got nothing", id);
return;
}
active_input_update (dialog, in);
}
static void
on_control_active_output_update (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog)
{
GvcMixerUIDevice* out = NULL;
out = gvc_mixer_control_lookup_output_id (control, id);
if (out == NULL) {
g_warning ("on_control_active_output_update - tried to fetch an output of id %u but got nothing", id);
return;
}
active_output_update (dialog, out);
}
static void
on_control_input_added (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog)
{
GvcMixerUIDevice* in = NULL;
in = gvc_mixer_control_lookup_input_id (control, id);
if (in == NULL) {
g_warning ("on_control_input_added - tried to fetch an input of id %u but got nothing", id);
return;
}
add_input_ui_entry (dialog, in);
}
static void
on_control_input_removed (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog)
{
gboolean found;
GtkTreeIter iter;
GtkTreeModel *model;
gint stream_id;
GvcMixerUIDevice *in;
in = gvc_mixer_control_lookup_input_id (control, id);
g_object_get (G_OBJECT (in),
"stream-id", &stream_id,
NULL);
g_debug ("Remove input from dialog, id: %u, stream id: %i",
id,
stream_id);
/* remove from any models */
model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
found = find_item_by_id (GTK_TREE_MODEL (model), id, ID_COLUMN, &iter);
if (found) {
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
}
}
static void
on_control_output_added (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog)
{
GvcMixerUIDevice* out = NULL;
out = gvc_mixer_control_lookup_output_id (control, id);
if (out == NULL) {
g_warning ("on_control_output_added - tried to fetch an output of id %u but got nothing", id);
return;
}
add_output_ui_entry (dialog, out);
}
static void
on_control_output_removed (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog)
{
gboolean found;
GtkTreeIter iter;
GtkTreeModel *model;
gint sink_stream_id;
GvcMixerUIDevice* out = NULL;
out = gvc_mixer_control_lookup_output_id (control, id);
g_object_get (G_OBJECT (out),
"stream-id", &sink_stream_id,
NULL);
g_debug ("Remove output from dialog \n id : %u \n sink stream id : %i \n",
id,
sink_stream_id);
/* remove from any models */
model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
found = find_item_by_id (GTK_TREE_MODEL (model), id, ID_COLUMN, &iter);
if (found) {
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
}
}
static void
remove_stream (GvcMixerDialog *dialog,
guint id)
{
GtkWidget *bar;
guint output_id, input_id;
bar = g_hash_table_lookup (dialog->priv->bars, GUINT_TO_POINTER (id));
if (bar != NULL) {
g_hash_table_remove (dialog->priv->bars, GUINT_TO_POINTER (id));
gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (bar)),
bar);
dialog->priv->num_apps--;
if (dialog->priv->num_apps == 0) {
gtk_widget_show (dialog->priv->no_apps_label);
}
return;
}
output_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (dialog->priv->output_bar), "gvc-mixer-dialog-stream-id"));
input_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (dialog->priv->input_bar), "gvc-mixer-dialog-stream-id"));
if (output_id == id)
bar = dialog->priv->output_bar;
else if (input_id == id)
bar = dialog->priv->input_bar;
else
return;
g_object_set_data (G_OBJECT (bar), "gvc-mixer-dialog-stream-id", NULL);
g_object_set_data (G_OBJECT (bar), "gvc-mixer-dialog-stream", NULL);
}
static void
on_control_stream_removed (GvcMixerControl *control,
guint id,
GvcMixerDialog *dialog)
{
remove_stream (dialog, id);
}
static void
_gtk_label_make_bold (GtkLabel *label)
{
PangoFontDescription *font_desc;
font_desc = pango_font_description_new ();
pango_font_description_set_weight (font_desc,
PANGO_WEIGHT_BOLD);
/* This will only affect the weight of the font, the rest is
* from the current state of the widget, which comes from the
* theme or user prefs, since the font desc only has the
* weight flag turned on.
*/
gtk_widget_modify_font (GTK_WIDGET (label), font_desc);
pango_font_description_free (font_desc);
}
static void
on_input_selection_changed (GtkTreeSelection *selection,
GvcMixerDialog *dialog)
{
GtkTreeModel *model;
GtkTreeIter iter;
gboolean active;
guint id;
GvcMixerUIDevice *input;
if (gtk_tree_selection_get_selected (selection, &model, &iter) == FALSE) {
g_debug ("Could not get default input from selection");
return;
}
gtk_tree_model_get (model, &iter,
ID_COLUMN, &id,
ACTIVE_COLUMN, &active,
-1);
input = gvc_mixer_control_lookup_input_id (dialog->priv->mixer_control, id);
if (input == NULL) {
g_warning ("on_input_selection_changed - Unable to find input with id: %u", id);
return;
}
gvc_mixer_control_change_input (dialog->priv->mixer_control, input);
}
static void
on_output_selection_changed (GtkTreeSelection *selection,
GvcMixerDialog *dialog)
{
GtkTreeModel *model;
GtkTreeIter iter;
gboolean active;
guint id;
GvcMixerUIDevice *output;
if (gtk_tree_selection_get_selected (selection, &model, &iter) == FALSE) {
g_debug ("Could not get default output from selection");
return;
}
gtk_tree_model_get (model, &iter,
ID_COLUMN, &id,
ACTIVE_COLUMN, &active,
-1);
g_debug ("on_output_selection_changed() stream id: %u, active %i", id, active);
if (active)
return;
output = gvc_mixer_control_lookup_output_id (dialog->priv->mixer_control, id);
if (output == NULL) {
g_warning ("Unable to find output with id: %u", id);
return;
}
gvc_mixer_control_change_output (dialog->priv->mixer_control, output);
}
static GtkWidget *
create_ui_device_treeview (GvcMixerDialog *dialog,
GCallback on_selection_changed)
{
GtkWidget *treeview;
GtkListStore *store;
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
GtkTreeSelection *selection;
treeview = gtk_tree_view_new ();
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
store = gtk_list_store_new (NUM_COLUMNS,
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_BOOLEAN,
G_TYPE_UINT,
G_TYPE_ICON);
gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
GTK_TREE_MODEL (store));
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
column = gtk_tree_view_column_new ();
gtk_tree_view_column_set_title (column, _("Name"));
renderer = gtk_cell_renderer_pixbuf_new ();
gtk_tree_view_column_pack_start (column, renderer, FALSE);
g_object_set (G_OBJECT (renderer), "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
gtk_tree_view_column_set_attributes (column, renderer,
"gicon", ICON_COLUMN,
NULL);
renderer = gtk_cell_renderer_text_new ();
gtk_tree_view_column_pack_start (column, renderer, TRUE);
gtk_tree_view_column_set_attributes (column, renderer,
"text", NAME_COLUMN,
NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
g_signal_connect (G_OBJECT (selection), "changed",
on_selection_changed, dialog);
#if 0
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes (_("Device"),
renderer,
"text", DEVICE_COLUMN,
NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
#endif
return treeview;
}
static void
on_test_speakers_clicked (GvcComboBox *widget,
gpointer user_data)
{
GvcMixerDialog *dialog = GVC_MIXER_DIALOG (user_data);
GtkTreeModel *model;
GtkTreeIter iter;
gint stream_id;
gint active_output = GVC_MIXER_UI_DEVICE_INVALID;
GvcMixerUIDevice *output;
GvcMixerStream *stream;
GtkWidget *d, *speaker_test, *container;
char *title;
model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
if (gtk_tree_model_get_iter_first (model, &iter) == FALSE) {
g_warning ("The tree is empty => we have no device to test speakers with return");
return;
}
do {
gboolean is_selected = FALSE;
gint id;
gtk_tree_model_get (model, &iter,
ID_COLUMN, &id,
ACTIVE_COLUMN, &is_selected,
-1);
if (is_selected) {
active_output = id;
break;
}
} while (gtk_tree_model_iter_next (model, &iter));
if (active_output == GVC_MIXER_UI_DEVICE_INVALID) {
g_warning ("Can't find the active output from the UI");
return;
}
output = gvc_mixer_control_lookup_output_id (dialog->priv->mixer_control, (guint)active_output);
stream_id = gvc_mixer_ui_device_get_stream_id (output);
if (stream_id == GVC_MIXER_UI_DEVICE_INVALID)
return;
g_debug ("Test speakers on '%s'", gvc_mixer_ui_device_get_description (output));
stream = gvc_mixer_control_lookup_stream_id (dialog->priv->mixer_control, stream_id);
if (stream == NULL) {
g_debug ("Stream/sink not found");
return;
}
title = g_strdup_printf (_("Speaker Testing for %s"), gvc_mixer_ui_device_get_description (output));
d = gtk_dialog_new_with_buttons (title,
GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (widget))),
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
NULL);
gtk_window_set_has_resize_grip (GTK_WINDOW (d), FALSE);
g_free (title);
speaker_test = gvc_speaker_test_new (dialog->priv->mixer_control,
stream);
gtk_widget_show (speaker_test);
container = gtk_dialog_get_content_area (GTK_DIALOG (d));
gtk_container_add (GTK_CONTAINER (container), speaker_test);
gtk_dialog_run (GTK_DIALOG (d));
gtk_widget_destroy (d);
}
static GObject *
gvc_mixer_dialog_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_params)
{
GObject *object;
GvcMixerDialog *self;
GtkWidget *main_vbox;
GtkWidget *label;
GtkWidget *alignment;
GtkWidget *box;
GtkWidget *sbox;
GtkWidget *ebox;
GSList *streams;
GSList *l;
GvcMixerStream *stream;
object = G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->constructor (type, n_construct_properties, construct_params);
self = GVC_MIXER_DIALOG (object);
main_vbox = GTK_WIDGET (self);
gtk_box_set_spacing (GTK_BOX (main_vbox), 2);
gtk_container_set_border_width (GTK_CONTAINER (self), 12);
self->priv->output_stream_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
alignment = gtk_alignment_new (0, 0, 1, 1);
gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 12, 0, 0, 0);
gtk_container_add (GTK_CONTAINER (alignment), self->priv->output_stream_box);
gtk_box_pack_start (GTK_BOX (main_vbox),
alignment,
FALSE, FALSE, 0);
self->priv->output_bar = create_bar (self, TRUE, TRUE);
gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->output_bar),
_("_Output volume:"));
gtk_widget_set_sensitive (self->priv->output_bar, FALSE);
gtk_box_pack_start (GTK_BOX (self->priv->output_stream_box),
self->priv->output_bar, TRUE, TRUE, 12);
self->priv->notebook = gtk_notebook_new ();
gtk_box_pack_start (GTK_BOX (main_vbox),
self->priv->notebook,
TRUE, TRUE, 0);
gtk_container_set_border_width (GTK_CONTAINER (self->priv->notebook), 5);
/* Output page */
self->priv->output_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
gtk_container_set_border_width (GTK_CONTAINER (self->priv->output_box), 12);
label = gtk_label_new (_("Output"));
gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
self->priv->output_box,
label);
box = gtk_frame_new (_("C_hoose a device for sound output:"));
label = gtk_frame_get_label_widget (GTK_FRAME (box));
_gtk_label_make_bold (GTK_LABEL (label));
gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
gtk_box_pack_start (GTK_BOX (self->priv->output_box), box, TRUE, TRUE, 0);
alignment = gtk_alignment_new (0, 0, 1, 1);
gtk_container_add (GTK_CONTAINER (box), alignment);
gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
self->priv->output_treeview = create_ui_device_treeview (self,
G_CALLBACK (on_output_selection_changed));
gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->output_treeview);
box = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box),
GTK_SHADOW_IN);
gtk_container_add (GTK_CONTAINER (box), self->priv->output_treeview);
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (box), 150);
gtk_container_add (GTK_CONTAINER (alignment), box);
box = gtk_frame_new (_("Settings for the selected device:"));
label = gtk_frame_get_label_widget (GTK_FRAME (box));
_gtk_label_make_bold (GTK_LABEL (label));
gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
gtk_box_pack_start (GTK_BOX (self->priv->output_box), box, FALSE, FALSE, 12);
self->priv->output_settings_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (box), self->priv->output_settings_box);
/* Input page */
self->priv->input_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
gtk_container_set_border_width (GTK_CONTAINER (self->priv->input_box), 12);
label = gtk_label_new (_("Input"));
gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
self->priv->input_box,
label);
self->priv->input_bar = create_bar (self, TRUE, TRUE);
gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->input_bar),
_("_Input volume:"));
gvc_channel_bar_set_low_icon_name (GVC_CHANNEL_BAR (self->priv->input_bar),
"audio-input-microphone-low-symbolic");
gvc_channel_bar_set_high_icon_name (GVC_CHANNEL_BAR (self->priv->input_bar),
"audio-input-microphone-high-symbolic");
gtk_widget_set_sensitive (self->priv->input_bar, FALSE);
alignment = gtk_alignment_new (0, 0, 1, 1);
gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
gtk_container_add (GTK_CONTAINER (alignment), self->priv->input_bar);
gtk_box_pack_start (GTK_BOX (self->priv->input_box),
alignment,
FALSE, FALSE, 0);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start (GTK_BOX (self->priv->input_box),
box,
FALSE, FALSE, 6);
sbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start (GTK_BOX (box),
sbox,
FALSE, FALSE, 0);
label = gtk_label_new (_("Input level:"));
gtk_box_pack_start (GTK_BOX (sbox),
label,
FALSE, FALSE, 0);
if (self->priv->size_group != NULL)
gtk_size_group_add_widget (self->priv->size_group, sbox);
self->priv->input_level_bar = gvc_level_bar_new ();
gvc_level_bar_set_orientation (GVC_LEVEL_BAR (self->priv->input_level_bar),
GTK_ORIENTATION_HORIZONTAL);
gvc_level_bar_set_scale (GVC_LEVEL_BAR (self->priv->input_level_bar),
GVC_LEVEL_SCALE_LINEAR);
gtk_box_pack_start (GTK_BOX (box),
self->priv->input_level_bar,
TRUE, TRUE, 6);
ebox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start (GTK_BOX (box),
ebox,
FALSE, FALSE, 0);
if (self->priv->size_group != NULL)
gtk_size_group_add_widget (self->priv->size_group, ebox);
self->priv->input_settings_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start (GTK_BOX (self->priv->input_box),
self->priv->input_settings_box,
FALSE, FALSE, 0);
box = gtk_frame_new (_("C_hoose a device for sound input:"));
label = gtk_frame_get_label_widget (GTK_FRAME (box));
_gtk_label_make_bold (GTK_LABEL (label));
gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
gtk_box_pack_start (GTK_BOX (self->priv->input_box), box, TRUE, TRUE, 0);
alignment = gtk_alignment_new (0, 0, 1, 1);
gtk_container_add (GTK_CONTAINER (box), alignment);
gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
self->priv->input_treeview = create_ui_device_treeview (self,
G_CALLBACK (on_input_selection_changed));
gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->input_treeview);
box = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box),
GTK_SHADOW_IN);
gtk_container_add (GTK_CONTAINER (box), self->priv->input_treeview);
gtk_container_add (GTK_CONTAINER (alignment), box);
/* Effects page */
self->priv->sound_effects_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_set_border_width (GTK_CONTAINER (self->priv->sound_effects_box), 12);
label = gtk_label_new (_("Sound Effects"));
gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
self->priv->sound_effects_box,
label);
self->priv->effects_bar = create_bar (self, TRUE, TRUE);
gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->effects_bar),
_("_Alert volume:"));
gtk_widget_set_sensitive (self->priv->effects_bar, FALSE);
gtk_box_pack_start (GTK_BOX (self->priv->sound_effects_box),
self->priv->effects_bar, FALSE, FALSE, 0);
self->priv->sound_theme_chooser = gvc_sound_theme_chooser_new ();
gtk_box_pack_start (GTK_BOX (self->priv->sound_effects_box),
self->priv->sound_theme_chooser,
TRUE, TRUE, 6);
/* Applications */
self->priv->applications_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
gtk_container_set_border_width (GTK_CONTAINER (self->priv->applications_box), 12);
label = gtk_label_new (_("Applications"));
gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
self->priv->applications_box,
label);
self->priv->no_apps_label = gtk_label_new (_("No application is currently playing or recording audio."));
gtk_box_pack_start (GTK_BOX (self->priv->applications_box),
self->priv->no_apps_label,
TRUE, TRUE, 0);
g_signal_connect (self->priv->mixer_control,
"output-added",
G_CALLBACK (on_control_output_added),
self);
g_signal_connect (self->priv->mixer_control,
"output-removed",
G_CALLBACK (on_control_output_removed),
self);
g_signal_connect (self->priv->mixer_control,
"input-added",
G_CALLBACK (on_control_input_added),
self);
g_signal_connect (self->priv->mixer_control,
"input-removed",
G_CALLBACK (on_control_input_removed),
self);
g_signal_connect (self->priv->mixer_control,
"stream-added",
G_CALLBACK (on_control_stream_added),
self);
g_signal_connect (self->priv->mixer_control,
"stream-removed",
G_CALLBACK (on_control_stream_removed),
self);
gtk_widget_show_all (main_vbox);
streams = gvc_mixer_control_get_streams (self->priv->mixer_control);
for (l = streams; l != NULL; l = l->next) {
stream = l->data;
add_stream (self, stream);
}
g_slist_free (streams);
return object;
}
static void
gvc_mixer_dialog_dispose (GObject *object)
{
GvcMixerDialog *dialog = GVC_MIXER_DIALOG (object);
if (dialog->priv->mixer_control != NULL) {
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
on_control_output_added,
dialog);
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
on_control_output_removed,
dialog);
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
on_control_input_added,
dialog);
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
on_control_input_removed,
dialog);
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
on_control_active_input_update,
dialog);
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
on_control_active_output_update,
dialog);
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
on_control_stream_added,
dialog);
g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
on_control_stream_removed,
dialog);
g_object_unref (dialog->priv->mixer_control);
dialog->priv->mixer_control = NULL;
}
if (dialog->priv->bars != NULL) {
g_hash_table_destroy (dialog->priv->bars);
dialog->priv->bars = NULL;
}
G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->dispose (object);
}
static void
gvc_mixer_dialog_class_init (GvcMixerDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructor = gvc_mixer_dialog_constructor;
object_class->dispose = gvc_mixer_dialog_dispose;
object_class->finalize = gvc_mixer_dialog_finalize;
object_class->set_property = gvc_mixer_dialog_set_property;
object_class->get_property = gvc_mixer_dialog_get_property;
g_object_class_install_property (object_class,
PROP_MIXER_CONTROL,
g_param_spec_object ("mixer-control",
"mixer control",
"mixer control",
GVC_TYPE_MIXER_CONTROL,
G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
g_type_class_add_private (klass, sizeof (GvcMixerDialogPrivate));
}
static void
gvc_mixer_dialog_init (GvcMixerDialog *dialog)
{
dialog->priv = GVC_MIXER_DIALOG_GET_PRIVATE (dialog);
dialog->priv->bars = g_hash_table_new (NULL, NULL);
dialog->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
}
static void
gvc_mixer_dialog_finalize (GObject *object)
{
GvcMixerDialog *mixer_dialog;
g_return_if_fail (object != NULL);
g_return_if_fail (GVC_IS_MIXER_DIALOG (object));
mixer_dialog = GVC_MIXER_DIALOG (object);
g_return_if_fail (mixer_dialog->priv != NULL);
G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->finalize (object);
}
GvcMixerDialog *
gvc_mixer_dialog_new (GvcMixerControl *control)
{
GObject *dialog;
dialog = g_object_new (GVC_TYPE_MIXER_DIALOG,
"mixer-control", control,
NULL);
return GVC_MIXER_DIALOG (dialog);
}
enum {
PAGE_OUTPUT,
PAGE_INPUT,
PAGE_EVENTS,
PAGE_APPLICATIONS
};
gboolean
gvc_mixer_dialog_set_page (GvcMixerDialog *self,
const char *page)
{
guint num;
g_return_val_if_fail (self != NULL, FALSE);
num = PAGE_OUTPUT;
if (g_str_equal (page, "effects"))
num = PAGE_EVENTS;
else if (g_str_equal (page, "input"))
num = PAGE_INPUT;
else if (g_str_equal (page, "output"))
num = PAGE_OUTPUT;
else if (g_str_equal (page, "applications"))
num = PAGE_APPLICATIONS;
gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
return TRUE;
}