mirror of
https://gitlab.gnome.org/GNOME/libgnome-volume-control.git
synced 2024-11-24 09:50:43 -05:00
1978 lines
76 KiB
C
1978 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;
|
||
|
}
|
||
|
|
||
|
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), 6);
|
||
|
|
||
|
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;
|
||
|
}
|