In order to standardise various separate volume UIs, upstream PulseAudio has made a new define available for the recommended maximum volume to show in UIs. This volume was chosen to be similar to the value currently employed in gvc but also be based on dB values rather than a multiple of PA_VOLUME_NORM. The 150% value previously used mapped to +10.57dB with the current cubic volume mapping. In the interests of sanity, and with a tip of the hat to Spinal Tap, we decreed that the value should be +11dB. This is approx 153% which is doesn't really change the visual look of GVC. I will also be recommending other volume controls use this new value so everyone can use the mixer they want. This one goes up to 11. :) References: https://tango.0pointer.de/pipermail/pulseaudio-discuss/2010-April/006945.html https://tango.0pointer.de/pipermail/pulseaudio-discuss/2010-April/006950.html http://en.wikipedia.org/wiki/Up_to_eleven https://bugzilla.gnome.org/show_bug.cgi?id=644292
2258 lines
74 KiB
C
2258 lines
74 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2006-2008 Lennart Poettering
|
|
* Copyright (C) 2008 Sjoerd Simons <sjoerd@luon.net>
|
|
* 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 <glib.h>
|
|
#include <glib/gi18n-lib.h>
|
|
|
|
#include <pulse/pulseaudio.h>
|
|
#include <pulse/glib-mainloop.h>
|
|
#include <pulse/ext-stream-restore.h>
|
|
|
|
#include "gvc-mixer-control.h"
|
|
#include "gvc-mixer-sink.h"
|
|
#include "gvc-mixer-source.h"
|
|
#include "gvc-mixer-sink-input.h"
|
|
#include "gvc-mixer-source-output.h"
|
|
#include "gvc-mixer-event-role.h"
|
|
#include "gvc-mixer-card.h"
|
|
#include "gvc-mixer-card-private.h"
|
|
#include "gvc-channel-map-private.h"
|
|
#include "gvc-mixer-control-private.h"
|
|
|
|
#define GVC_MIXER_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlPrivate))
|
|
|
|
#define RECONNECT_DELAY 5
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_NAME
|
|
};
|
|
|
|
struct GvcMixerControlPrivate
|
|
{
|
|
pa_glib_mainloop *pa_mainloop;
|
|
pa_mainloop_api *pa_api;
|
|
pa_context *pa_context;
|
|
int n_outstanding;
|
|
guint reconnect_id;
|
|
char *name;
|
|
|
|
gboolean default_sink_is_set;
|
|
guint default_sink_id;
|
|
char *default_sink_name;
|
|
gboolean default_source_is_set;
|
|
guint default_source_id;
|
|
char *default_source_name;
|
|
|
|
gboolean event_sink_input_is_set;
|
|
guint event_sink_input_id;
|
|
|
|
GHashTable *all_streams;
|
|
GHashTable *sinks; /* fixed outputs */
|
|
GHashTable *sources; /* fixed inputs */
|
|
GHashTable *sink_inputs; /* routable output streams */
|
|
GHashTable *source_outputs; /* routable input streams */
|
|
GHashTable *clients;
|
|
GHashTable *cards;
|
|
|
|
GvcMixerStream *new_default_stream; /* new default stream, used in gvc_mixer_control_set_default_sink () */
|
|
};
|
|
|
|
enum {
|
|
CONNECTING,
|
|
READY,
|
|
STREAM_ADDED,
|
|
STREAM_REMOVED,
|
|
CARD_ADDED,
|
|
CARD_REMOVED,
|
|
DEFAULT_SINK_CHANGED,
|
|
DEFAULT_SOURCE_CHANGED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals [LAST_SIGNAL] = { 0, };
|
|
|
|
static void gvc_mixer_control_class_init (GvcMixerControlClass *klass);
|
|
static void gvc_mixer_control_init (GvcMixerControl *mixer_control);
|
|
static void gvc_mixer_control_finalize (GObject *object);
|
|
|
|
G_DEFINE_TYPE (GvcMixerControl, gvc_mixer_control, G_TYPE_OBJECT)
|
|
|
|
pa_context *
|
|
gvc_mixer_control_get_pa_context (GvcMixerControl *control)
|
|
{
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
return control->priv->pa_context;
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_event_sink_input:
|
|
*
|
|
* @control:
|
|
*
|
|
* Returns: (transfer none):
|
|
*/
|
|
GvcMixerStream *
|
|
gvc_mixer_control_get_event_sink_input (GvcMixerControl *control)
|
|
{
|
|
GvcMixerStream *stream;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
stream = g_hash_table_lookup (control->priv->all_streams,
|
|
GUINT_TO_POINTER (control->priv->event_sink_input_id));
|
|
|
|
return stream;
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_stream_restore_cb (pa_context *c,
|
|
const pa_ext_stream_restore_info *info,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
pa_operation *o;
|
|
GvcMixerControl *control = (GvcMixerControl *) userdata;
|
|
pa_ext_stream_restore_info new_info;
|
|
|
|
if (eol || control->priv->new_default_stream == NULL)
|
|
return;
|
|
|
|
new_info.name = info->name;
|
|
new_info.channel_map = info->channel_map;
|
|
new_info.volume = info->volume;
|
|
new_info.mute = info->mute;
|
|
|
|
new_info.device = gvc_mixer_stream_get_name (control->priv->new_default_stream);
|
|
|
|
o = pa_ext_stream_restore_write (control->priv->pa_context,
|
|
PA_UPDATE_REPLACE,
|
|
&new_info, 1,
|
|
TRUE, NULL, NULL);
|
|
|
|
if (o == NULL) {
|
|
g_warning ("pa_ext_stream_restore_write() failed: %s",
|
|
pa_strerror (pa_context_errno (control->priv->pa_context)));
|
|
return;
|
|
}
|
|
|
|
g_debug ("Changed default device for %s to %s", info->name, info->device);
|
|
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
gboolean
|
|
gvc_mixer_control_set_default_sink (GvcMixerControl *control,
|
|
GvcMixerStream *stream)
|
|
{
|
|
pa_operation *o;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
|
|
g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
|
|
|
|
o = pa_context_set_default_sink (control->priv->pa_context,
|
|
gvc_mixer_stream_get_name (stream),
|
|
NULL,
|
|
NULL);
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_set_default_sink() failed: %s",
|
|
pa_strerror (pa_context_errno (control->priv->pa_context)));
|
|
return FALSE;
|
|
}
|
|
|
|
pa_operation_unref (o);
|
|
|
|
control->priv->new_default_stream = stream;
|
|
g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_stream);
|
|
|
|
o = pa_ext_stream_restore_read (control->priv->pa_context,
|
|
gvc_mixer_control_stream_restore_cb,
|
|
control);
|
|
|
|
if (o == NULL) {
|
|
g_warning ("pa_ext_stream_restore_read() failed: %s",
|
|
pa_strerror (pa_context_errno (control->priv->pa_context)));
|
|
return FALSE;
|
|
}
|
|
|
|
pa_operation_unref (o);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gvc_mixer_control_set_default_source (GvcMixerControl *control,
|
|
GvcMixerStream *stream)
|
|
{
|
|
pa_operation *o;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
|
|
g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
|
|
|
|
o = pa_context_set_default_source (control->priv->pa_context,
|
|
gvc_mixer_stream_get_name (stream),
|
|
NULL,
|
|
NULL);
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_set_default_source() failed");
|
|
return FALSE;
|
|
}
|
|
|
|
pa_operation_unref (o);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_default_sink:
|
|
*
|
|
* @control:
|
|
*
|
|
* Returns: (transfer none):
|
|
*/
|
|
GvcMixerStream *
|
|
gvc_mixer_control_get_default_sink (GvcMixerControl *control)
|
|
{
|
|
GvcMixerStream *stream;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
if (control->priv->default_sink_is_set) {
|
|
stream = g_hash_table_lookup (control->priv->all_streams,
|
|
GUINT_TO_POINTER (control->priv->default_sink_id));
|
|
} else {
|
|
stream = NULL;
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_default_source:
|
|
*
|
|
* @control:
|
|
*
|
|
* Returns: (transfer none):
|
|
*/
|
|
GvcMixerStream *
|
|
gvc_mixer_control_get_default_source (GvcMixerControl *control)
|
|
{
|
|
GvcMixerStream *stream;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
if (control->priv->default_source_is_set) {
|
|
stream = g_hash_table_lookup (control->priv->all_streams,
|
|
GUINT_TO_POINTER (control->priv->default_source_id));
|
|
} else {
|
|
stream = NULL;
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
static gpointer
|
|
gvc_mixer_control_lookup_id (GHashTable *hash_table,
|
|
guint id)
|
|
{
|
|
return g_hash_table_lookup (hash_table,
|
|
GUINT_TO_POINTER (id));
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_lookup_stream_id:
|
|
*
|
|
* @control:
|
|
* @id:
|
|
*
|
|
* Returns: (transfer none):
|
|
*/
|
|
GvcMixerStream *
|
|
gvc_mixer_control_lookup_stream_id (GvcMixerControl *control,
|
|
guint id)
|
|
{
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
return gvc_mixer_control_lookup_id (control->priv->all_streams, id);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_lookup_card_id:
|
|
*
|
|
* @control:
|
|
* @id:
|
|
*
|
|
* Returns: (transfer none):
|
|
*/
|
|
GvcMixerCard *
|
|
gvc_mixer_control_lookup_card_id (GvcMixerControl *control,
|
|
guint id)
|
|
{
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
return gvc_mixer_control_lookup_id (control->priv->cards, id);
|
|
}
|
|
|
|
static void
|
|
listify_hash_values_hfunc (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
GSList **list = user_data;
|
|
|
|
*list = g_slist_prepend (*list, value);
|
|
}
|
|
|
|
static int
|
|
gvc_name_collate (const char *namea,
|
|
const char *nameb)
|
|
{
|
|
if (nameb == NULL && namea == NULL)
|
|
return 0;
|
|
if (nameb == NULL)
|
|
return 1;
|
|
if (namea == NULL)
|
|
return -1;
|
|
|
|
return g_utf8_collate (namea, nameb);
|
|
}
|
|
|
|
static int
|
|
gvc_card_collate (GvcMixerCard *a,
|
|
GvcMixerCard *b)
|
|
{
|
|
const char *namea;
|
|
const char *nameb;
|
|
|
|
g_return_val_if_fail (a == NULL || GVC_IS_MIXER_CARD (a), 0);
|
|
g_return_val_if_fail (b == NULL || GVC_IS_MIXER_CARD (b), 0);
|
|
|
|
namea = gvc_mixer_card_get_name (a);
|
|
nameb = gvc_mixer_card_get_name (b);
|
|
|
|
return gvc_name_collate (namea, nameb);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_cards:
|
|
*
|
|
* @control:
|
|
*
|
|
* Returns: (transfer container) (element-type Gvc.MixerCard):
|
|
*/
|
|
GSList *
|
|
gvc_mixer_control_get_cards (GvcMixerControl *control)
|
|
{
|
|
GSList *retval;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
retval = NULL;
|
|
g_hash_table_foreach (control->priv->cards,
|
|
listify_hash_values_hfunc,
|
|
&retval);
|
|
return g_slist_sort (retval, (GCompareFunc) gvc_card_collate);
|
|
}
|
|
|
|
static int
|
|
gvc_stream_collate (GvcMixerStream *a,
|
|
GvcMixerStream *b)
|
|
{
|
|
const char *namea;
|
|
const char *nameb;
|
|
|
|
g_return_val_if_fail (a == NULL || GVC_IS_MIXER_STREAM (a), 0);
|
|
g_return_val_if_fail (b == NULL || GVC_IS_MIXER_STREAM (b), 0);
|
|
|
|
namea = gvc_mixer_stream_get_name (a);
|
|
nameb = gvc_mixer_stream_get_name (b);
|
|
|
|
return gvc_name_collate (namea, nameb);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_streams:
|
|
*
|
|
* @control:
|
|
*
|
|
* Returns: (transfer container) (element-type Gvc.MixerStream):
|
|
*/
|
|
GSList *
|
|
gvc_mixer_control_get_streams (GvcMixerControl *control)
|
|
{
|
|
GSList *retval;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
retval = NULL;
|
|
g_hash_table_foreach (control->priv->all_streams,
|
|
listify_hash_values_hfunc,
|
|
&retval);
|
|
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_sinks:
|
|
*
|
|
* @control:
|
|
*
|
|
* Returns: (transfer container) (element-type Gvc.MixerSink):
|
|
*/
|
|
GSList *
|
|
gvc_mixer_control_get_sinks (GvcMixerControl *control)
|
|
{
|
|
GSList *retval;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
retval = NULL;
|
|
g_hash_table_foreach (control->priv->sinks,
|
|
listify_hash_values_hfunc,
|
|
&retval);
|
|
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_sources:
|
|
*
|
|
* @control:
|
|
*
|
|
* Returns: (transfer container) (element-type Gvc.MixerSource):
|
|
*/
|
|
GSList *
|
|
gvc_mixer_control_get_sources (GvcMixerControl *control)
|
|
{
|
|
GSList *retval;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
retval = NULL;
|
|
g_hash_table_foreach (control->priv->sources,
|
|
listify_hash_values_hfunc,
|
|
&retval);
|
|
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_sink_inputs:
|
|
*
|
|
* @control:
|
|
*
|
|
* Returns: (transfer container) (element-type Gvc.MixerSinkInput):
|
|
*/
|
|
GSList *
|
|
gvc_mixer_control_get_sink_inputs (GvcMixerControl *control)
|
|
{
|
|
GSList *retval;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
retval = NULL;
|
|
g_hash_table_foreach (control->priv->sink_inputs,
|
|
listify_hash_values_hfunc,
|
|
&retval);
|
|
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_source_outputs:
|
|
*
|
|
* @control:
|
|
*
|
|
* Returns: (transfer container) (element-type Gvc.MixerSourceOutput):
|
|
*/
|
|
GSList *
|
|
gvc_mixer_control_get_source_outputs (GvcMixerControl *control)
|
|
{
|
|
GSList *retval;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
retval = NULL;
|
|
g_hash_table_foreach (control->priv->source_outputs,
|
|
listify_hash_values_hfunc,
|
|
&retval);
|
|
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
|
|
}
|
|
|
|
static void
|
|
dec_outstanding (GvcMixerControl *control)
|
|
{
|
|
if (control->priv->n_outstanding <= 0) {
|
|
return;
|
|
}
|
|
|
|
if (--control->priv->n_outstanding <= 0) {
|
|
g_signal_emit (G_OBJECT (control), signals[READY], 0);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gvc_mixer_control_is_ready (GvcMixerControl *control)
|
|
{
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
|
|
|
|
return (control->priv->n_outstanding == 0);
|
|
}
|
|
|
|
|
|
static void
|
|
_set_default_source (GvcMixerControl *control,
|
|
GvcMixerStream *stream)
|
|
{
|
|
guint new_id;
|
|
|
|
if (stream == NULL) {
|
|
control->priv->default_source_id = 0;
|
|
control->priv->default_source_is_set = FALSE;
|
|
g_signal_emit (control,
|
|
signals[DEFAULT_SOURCE_CHANGED],
|
|
0,
|
|
PA_INVALID_INDEX);
|
|
return;
|
|
}
|
|
|
|
new_id = gvc_mixer_stream_get_id (stream);
|
|
|
|
if (control->priv->default_source_id != new_id) {
|
|
control->priv->default_source_id = new_id;
|
|
control->priv->default_source_is_set = TRUE;
|
|
g_signal_emit (control,
|
|
signals[DEFAULT_SOURCE_CHANGED],
|
|
0,
|
|
new_id);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_set_default_sink (GvcMixerControl *control,
|
|
GvcMixerStream *stream)
|
|
{
|
|
guint new_id;
|
|
|
|
if (stream == NULL) {
|
|
/* Don't tell front-ends about an unset default
|
|
* sink if it's already unset */
|
|
if (control->priv->default_sink_is_set == FALSE)
|
|
return;
|
|
control->priv->default_sink_id = 0;
|
|
control->priv->default_sink_is_set = FALSE;
|
|
g_signal_emit (control,
|
|
signals[DEFAULT_SINK_CHANGED],
|
|
0,
|
|
PA_INVALID_INDEX);
|
|
return;
|
|
}
|
|
|
|
new_id = gvc_mixer_stream_get_id (stream);
|
|
|
|
if (control->priv->default_sink_id != new_id) {
|
|
control->priv->default_sink_id = new_id;
|
|
control->priv->default_sink_is_set = TRUE;
|
|
g_signal_emit (control,
|
|
signals[DEFAULT_SINK_CHANGED],
|
|
0,
|
|
new_id);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
_stream_has_name (gpointer key,
|
|
GvcMixerStream *stream,
|
|
const char *name)
|
|
{
|
|
const char *t_name;
|
|
|
|
t_name = gvc_mixer_stream_get_name (stream);
|
|
|
|
if (t_name != NULL
|
|
&& name != NULL
|
|
&& strcmp (t_name, name) == 0) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GvcMixerStream *
|
|
find_stream_for_name (GvcMixerControl *control,
|
|
const char *name)
|
|
{
|
|
GvcMixerStream *stream;
|
|
|
|
stream = g_hash_table_find (control->priv->all_streams,
|
|
(GHRFunc)_stream_has_name,
|
|
(char *)name);
|
|
return stream;
|
|
}
|
|
|
|
static void
|
|
update_default_source_from_name (GvcMixerControl *control,
|
|
const char *name)
|
|
{
|
|
gboolean changed = FALSE;
|
|
|
|
if ((control->priv->default_source_name == NULL
|
|
&& name != NULL)
|
|
|| (control->priv->default_source_name != NULL
|
|
&& name == NULL)
|
|
|| (name != NULL && strcmp (control->priv->default_source_name, name) != 0)) {
|
|
changed = TRUE;
|
|
}
|
|
|
|
if (changed) {
|
|
GvcMixerStream *stream;
|
|
|
|
g_free (control->priv->default_source_name);
|
|
control->priv->default_source_name = g_strdup (name);
|
|
|
|
stream = find_stream_for_name (control, name);
|
|
_set_default_source (control, stream);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_default_sink_from_name (GvcMixerControl *control,
|
|
const char *name)
|
|
{
|
|
gboolean changed = FALSE;
|
|
|
|
if ((control->priv->default_sink_name == NULL
|
|
&& name != NULL)
|
|
|| (control->priv->default_sink_name != NULL
|
|
&& name == NULL)
|
|
|| (name != NULL && strcmp (control->priv->default_sink_name, name) != 0)) {
|
|
changed = TRUE;
|
|
}
|
|
|
|
if (changed) {
|
|
GvcMixerStream *stream;
|
|
g_free (control->priv->default_sink_name);
|
|
control->priv->default_sink_name = g_strdup (name);
|
|
|
|
stream = find_stream_for_name (control, name);
|
|
_set_default_sink (control, stream);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_server (GvcMixerControl *control,
|
|
const pa_server_info *info)
|
|
{
|
|
if (info->default_source_name != NULL) {
|
|
update_default_source_from_name (control, info->default_source_name);
|
|
}
|
|
if (info->default_sink_name != NULL) {
|
|
update_default_sink_from_name (control, info->default_sink_name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
remove_stream (GvcMixerControl *control,
|
|
GvcMixerStream *stream)
|
|
{
|
|
guint id;
|
|
|
|
g_object_ref (stream);
|
|
|
|
id = gvc_mixer_stream_get_id (stream);
|
|
|
|
if (id == control->priv->default_sink_id) {
|
|
_set_default_sink (control, NULL);
|
|
} else if (id == control->priv->default_source_id) {
|
|
_set_default_source (control, NULL);
|
|
}
|
|
|
|
g_hash_table_remove (control->priv->all_streams,
|
|
GUINT_TO_POINTER (id));
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[STREAM_REMOVED],
|
|
0,
|
|
gvc_mixer_stream_get_id (stream));
|
|
g_object_unref (stream);
|
|
}
|
|
|
|
static void
|
|
add_stream (GvcMixerControl *control,
|
|
GvcMixerStream *stream)
|
|
{
|
|
g_hash_table_insert (control->priv->all_streams,
|
|
GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)),
|
|
stream);
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[STREAM_ADDED],
|
|
0,
|
|
gvc_mixer_stream_get_id (stream));
|
|
}
|
|
|
|
static void
|
|
set_icon_name_from_proplist (GvcMixerStream *stream,
|
|
pa_proplist *l,
|
|
const char *default_icon_name)
|
|
{
|
|
const char *t;
|
|
|
|
if ((t = pa_proplist_gets (l, PA_PROP_DEVICE_ICON_NAME))) {
|
|
goto finish;
|
|
}
|
|
|
|
if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ICON_NAME))) {
|
|
goto finish;
|
|
}
|
|
|
|
if ((t = pa_proplist_gets (l, PA_PROP_WINDOW_ICON_NAME))) {
|
|
goto finish;
|
|
}
|
|
|
|
if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ICON_NAME))) {
|
|
goto finish;
|
|
}
|
|
|
|
if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
|
|
|
|
if (strcmp (t, "video") == 0 ||
|
|
strcmp (t, "phone") == 0) {
|
|
goto finish;
|
|
}
|
|
|
|
if (strcmp (t, "music") == 0) {
|
|
t = "audio";
|
|
goto finish;
|
|
}
|
|
|
|
if (strcmp (t, "game") == 0) {
|
|
t = "applications-games";
|
|
goto finish;
|
|
}
|
|
|
|
if (strcmp (t, "event") == 0) {
|
|
t = "dialog-information";
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
t = default_icon_name;
|
|
|
|
finish:
|
|
gvc_mixer_stream_set_icon_name (stream, t);
|
|
}
|
|
|
|
static void
|
|
update_sink (GvcMixerControl *control,
|
|
const pa_sink_info *info)
|
|
{
|
|
GvcMixerStream *stream;
|
|
gboolean is_new;
|
|
pa_volume_t max_volume;
|
|
GvcChannelMap *map;
|
|
char map_buff[PA_CHANNEL_MAP_SNPRINT_MAX];
|
|
|
|
pa_channel_map_snprint (map_buff, PA_CHANNEL_MAP_SNPRINT_MAX, &info->channel_map);
|
|
#if 1
|
|
g_debug ("Updating sink: index=%u name='%s' description='%s' map='%s'",
|
|
info->index,
|
|
info->name,
|
|
info->description,
|
|
map_buff);
|
|
#endif
|
|
|
|
map = NULL;
|
|
is_new = FALSE;
|
|
stream = g_hash_table_lookup (control->priv->sinks,
|
|
GUINT_TO_POINTER (info->index));
|
|
if (stream == NULL) {
|
|
#if PA_MICRO > 15
|
|
GList *list = NULL;
|
|
guint i;
|
|
#endif /* PA_MICRO > 15 */
|
|
|
|
map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
|
|
stream = gvc_mixer_sink_new (control->priv->pa_context,
|
|
info->index,
|
|
map);
|
|
#if PA_MICRO > 15
|
|
for (i = 0; i < info->n_ports; i++) {
|
|
GvcMixerStreamPort *port;
|
|
|
|
port = g_new0 (GvcMixerStreamPort, 1);
|
|
port->port = g_strdup (info->ports[i]->name);
|
|
port->human_port = g_strdup (info->ports[i]->description);
|
|
port->priority = info->ports[i]->priority;
|
|
list = g_list_prepend (list, port);
|
|
}
|
|
gvc_mixer_stream_set_ports (stream, list);
|
|
#endif /* PA_MICRO > 15 */
|
|
g_object_unref (map);
|
|
is_new = TRUE;
|
|
} else if (gvc_mixer_stream_is_running (stream)) {
|
|
/* Ignore events if volume changes are outstanding */
|
|
g_debug ("Ignoring event, volume changes are outstanding");
|
|
return;
|
|
}
|
|
|
|
max_volume = pa_cvolume_max (&info->volume);
|
|
gvc_mixer_stream_set_name (stream, info->name);
|
|
gvc_mixer_stream_set_card_index (stream, info->card);
|
|
gvc_mixer_stream_set_description (stream, info->description);
|
|
set_icon_name_from_proplist (stream, info->proplist, "audio-card");
|
|
gvc_mixer_stream_set_volume (stream, (guint)max_volume);
|
|
gvc_mixer_stream_set_is_muted (stream, info->mute);
|
|
gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME));
|
|
gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
|
|
#if PA_MICRO > 15
|
|
if (info->active_port != NULL)
|
|
gvc_mixer_stream_set_port (stream, info->active_port->name);
|
|
#endif /* PA_MICRO > 15 */
|
|
|
|
if (is_new) {
|
|
g_hash_table_insert (control->priv->sinks,
|
|
GUINT_TO_POINTER (info->index),
|
|
g_object_ref (stream));
|
|
add_stream (control, stream);
|
|
}
|
|
|
|
if (control->priv->default_sink_name != NULL
|
|
&& info->name != NULL
|
|
&& strcmp (control->priv->default_sink_name, info->name) == 0) {
|
|
_set_default_sink (control, stream);
|
|
}
|
|
|
|
if (map == NULL)
|
|
map = (GvcChannelMap *) gvc_mixer_stream_get_channel_map (stream);
|
|
gvc_channel_map_volume_changed (map, &info->volume, FALSE);
|
|
}
|
|
|
|
static void
|
|
update_source (GvcMixerControl *control,
|
|
const pa_source_info *info)
|
|
{
|
|
GvcMixerStream *stream;
|
|
gboolean is_new;
|
|
pa_volume_t max_volume;
|
|
|
|
#if 1
|
|
g_debug ("Updating source: index=%u name='%s' description='%s'",
|
|
info->index,
|
|
info->name,
|
|
info->description);
|
|
#endif
|
|
|
|
/* completely ignore monitors, they're not real sources */
|
|
if (info->monitor_of_sink != PA_INVALID_INDEX) {
|
|
return;
|
|
}
|
|
|
|
is_new = FALSE;
|
|
|
|
stream = g_hash_table_lookup (control->priv->sources,
|
|
GUINT_TO_POINTER (info->index));
|
|
if (stream == NULL) {
|
|
#if PA_MICRO > 15
|
|
GList *list = NULL;
|
|
guint i;
|
|
#endif /* PA_MICRO > 15 */
|
|
GvcChannelMap *map;
|
|
|
|
map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
|
|
stream = gvc_mixer_source_new (control->priv->pa_context,
|
|
info->index,
|
|
map);
|
|
#if PA_MICRO > 15
|
|
for (i = 0; i < info->n_ports; i++) {
|
|
GvcMixerStreamPort *port;
|
|
|
|
port = g_new0 (GvcMixerStreamPort, 1);
|
|
port->port = g_strdup (info->ports[i]->name);
|
|
port->human_port = g_strdup (info->ports[i]->description);
|
|
port->priority = info->ports[i]->priority;
|
|
list = g_list_prepend (list, port);
|
|
}
|
|
gvc_mixer_stream_set_ports (stream, list);
|
|
#endif /* PA_MICRO > 15 */
|
|
|
|
g_object_unref (map);
|
|
is_new = TRUE;
|
|
} else if (gvc_mixer_stream_is_running (stream)) {
|
|
/* Ignore events if volume changes are outstanding */
|
|
g_debug ("Ignoring event, volume changes are outstanding");
|
|
return;
|
|
}
|
|
|
|
max_volume = pa_cvolume_max (&info->volume);
|
|
|
|
gvc_mixer_stream_set_name (stream, info->name);
|
|
gvc_mixer_stream_set_card_index (stream, info->card);
|
|
gvc_mixer_stream_set_description (stream, info->description);
|
|
set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
|
|
gvc_mixer_stream_set_volume (stream, (guint)max_volume);
|
|
gvc_mixer_stream_set_is_muted (stream, info->mute);
|
|
gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME));
|
|
gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
|
|
#if PA_MICRO > 15
|
|
if (info->active_port != NULL)
|
|
gvc_mixer_stream_set_port (stream, info->active_port->name);
|
|
#endif /* PA_MICRO > 15 */
|
|
|
|
if (is_new) {
|
|
g_hash_table_insert (control->priv->sources,
|
|
GUINT_TO_POINTER (info->index),
|
|
g_object_ref (stream));
|
|
add_stream (control, stream);
|
|
}
|
|
|
|
if (control->priv->default_source_name != NULL
|
|
&& info->name != NULL
|
|
&& strcmp (control->priv->default_source_name, info->name) == 0) {
|
|
_set_default_source (control, stream);
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_is_event_stream_from_proplist (GvcMixerStream *stream,
|
|
pa_proplist *l)
|
|
{
|
|
const char *t;
|
|
gboolean is_event_stream;
|
|
|
|
is_event_stream = FALSE;
|
|
|
|
if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
|
|
if (g_str_equal (t, "event"))
|
|
is_event_stream = TRUE;
|
|
}
|
|
|
|
gvc_mixer_stream_set_is_event_stream (stream, is_event_stream);
|
|
}
|
|
|
|
static void
|
|
set_application_id_from_proplist (GvcMixerStream *stream,
|
|
pa_proplist *l)
|
|
{
|
|
const char *t;
|
|
|
|
if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ID))) {
|
|
gvc_mixer_stream_set_application_id (stream, t);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_sink_input (GvcMixerControl *control,
|
|
const pa_sink_input_info *info)
|
|
{
|
|
GvcMixerStream *stream;
|
|
gboolean is_new;
|
|
pa_volume_t max_volume;
|
|
const char *name;
|
|
|
|
#if 0
|
|
g_debug ("Updating sink input: index=%u name='%s' client=%u sink=%u",
|
|
info->index,
|
|
info->name,
|
|
info->client,
|
|
info->sink);
|
|
#endif
|
|
|
|
is_new = FALSE;
|
|
|
|
stream = g_hash_table_lookup (control->priv->sink_inputs,
|
|
GUINT_TO_POINTER (info->index));
|
|
if (stream == NULL) {
|
|
GvcChannelMap *map;
|
|
map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
|
|
stream = gvc_mixer_sink_input_new (control->priv->pa_context,
|
|
info->index,
|
|
map);
|
|
g_object_unref (map);
|
|
is_new = TRUE;
|
|
} else if (gvc_mixer_stream_is_running (stream)) {
|
|
/* Ignore events if volume changes are outstanding */
|
|
g_debug ("Ignoring event, volume changes are outstanding");
|
|
return;
|
|
}
|
|
|
|
max_volume = pa_cvolume_max (&info->volume);
|
|
|
|
name = (const char *)g_hash_table_lookup (control->priv->clients,
|
|
GUINT_TO_POINTER (info->client));
|
|
gvc_mixer_stream_set_name (stream, name);
|
|
gvc_mixer_stream_set_description (stream, info->name);
|
|
|
|
set_application_id_from_proplist (stream, info->proplist);
|
|
set_is_event_stream_from_proplist (stream, info->proplist);
|
|
set_icon_name_from_proplist (stream, info->proplist, "applications-multimedia");
|
|
gvc_mixer_stream_set_volume (stream, (guint)max_volume);
|
|
gvc_mixer_stream_set_is_muted (stream, info->mute);
|
|
gvc_mixer_stream_set_is_virtual (stream, info->client == PA_INVALID_INDEX);
|
|
|
|
if (is_new) {
|
|
g_hash_table_insert (control->priv->sink_inputs,
|
|
GUINT_TO_POINTER (info->index),
|
|
g_object_ref (stream));
|
|
add_stream (control, stream);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_source_output (GvcMixerControl *control,
|
|
const pa_source_output_info *info)
|
|
{
|
|
GvcMixerStream *stream;
|
|
gboolean is_new;
|
|
const char *name;
|
|
|
|
#if 1
|
|
g_debug ("Updating source output: index=%u name='%s' client=%u source=%u",
|
|
info->index,
|
|
info->name,
|
|
info->client,
|
|
info->source);
|
|
#endif
|
|
|
|
is_new = FALSE;
|
|
stream = g_hash_table_lookup (control->priv->source_outputs,
|
|
GUINT_TO_POINTER (info->index));
|
|
if (stream == NULL) {
|
|
GvcChannelMap *map;
|
|
map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
|
|
stream = gvc_mixer_source_output_new (control->priv->pa_context,
|
|
info->index,
|
|
map);
|
|
g_object_unref (map);
|
|
is_new = TRUE;
|
|
}
|
|
|
|
name = (const char *)g_hash_table_lookup (control->priv->clients,
|
|
GUINT_TO_POINTER (info->client));
|
|
|
|
gvc_mixer_stream_set_name (stream, name);
|
|
gvc_mixer_stream_set_description (stream, info->name);
|
|
set_application_id_from_proplist (stream, info->proplist);
|
|
set_is_event_stream_from_proplist (stream, info->proplist);
|
|
set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
|
|
|
|
if (is_new) {
|
|
g_hash_table_insert (control->priv->source_outputs,
|
|
GUINT_TO_POINTER (info->index),
|
|
g_object_ref (stream));
|
|
add_stream (control, stream);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_client (GvcMixerControl *control,
|
|
const pa_client_info *info)
|
|
{
|
|
#if 1
|
|
g_debug ("Updating client: index=%u name='%s'",
|
|
info->index,
|
|
info->name);
|
|
#endif
|
|
g_hash_table_insert (control->priv->clients,
|
|
GUINT_TO_POINTER (info->index),
|
|
g_strdup (info->name));
|
|
}
|
|
|
|
static char *
|
|
card_num_streams_to_status (guint sinks,
|
|
guint sources)
|
|
{
|
|
char *sinks_str;
|
|
char *sources_str;
|
|
char *ret;
|
|
|
|
if (sinks == 0 && sources == 0) {
|
|
/* translators:
|
|
* The device has been disabled */
|
|
return g_strdup (_("Disabled"));
|
|
}
|
|
if (sinks == 0) {
|
|
sinks_str = NULL;
|
|
} else {
|
|
/* translators:
|
|
* The number of sound outputs on a particular device */
|
|
sinks_str = g_strdup_printf (ngettext ("%u Output",
|
|
"%u Outputs",
|
|
sinks),
|
|
sinks);
|
|
}
|
|
if (sources == 0) {
|
|
sources_str = NULL;
|
|
} else {
|
|
/* translators:
|
|
* The number of sound inputs on a particular device */
|
|
sources_str = g_strdup_printf (ngettext ("%u Input",
|
|
"%u Inputs",
|
|
sources),
|
|
sources);
|
|
}
|
|
if (sources_str == NULL)
|
|
return sinks_str;
|
|
if (sinks_str == NULL)
|
|
return sources_str;
|
|
ret = g_strdup_printf ("%s / %s", sinks_str, sources_str);
|
|
g_free (sinks_str);
|
|
g_free (sources_str);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
update_card (GvcMixerControl *control,
|
|
const pa_card_info *info)
|
|
{
|
|
GvcMixerCard *card;
|
|
gboolean is_new = FALSE;
|
|
#if 1
|
|
guint i;
|
|
const char *key;
|
|
void *state;
|
|
|
|
g_debug ("Udpating card %s (index: %u driver: %s):",
|
|
info->name, info->index, info->driver);
|
|
|
|
for (i = 0; i < info->n_profiles; i++) {
|
|
struct pa_card_profile_info pi = info->profiles[i];
|
|
gboolean is_default;
|
|
|
|
is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0);
|
|
g_debug ("\tProfile '%s': %d sources %d sinks%s",
|
|
pi.name, pi.n_sources, pi.n_sinks,
|
|
is_default ? " (Current)" : "");
|
|
}
|
|
state = NULL;
|
|
key = pa_proplist_iterate (info->proplist, &state);
|
|
while (key != NULL) {
|
|
g_debug ("\tProperty: '%s' = '%s'",
|
|
key, pa_proplist_gets (info->proplist, key));
|
|
key = pa_proplist_iterate (info->proplist, &state);
|
|
}
|
|
#endif
|
|
card = g_hash_table_lookup (control->priv->cards,
|
|
GUINT_TO_POINTER (info->index));
|
|
if (card == NULL) {
|
|
GList *list = NULL;
|
|
|
|
for (i = 0; i < info->n_profiles; i++) {
|
|
struct pa_card_profile_info pi = info->profiles[i];
|
|
GvcMixerCardProfile *profile;
|
|
|
|
profile = g_new0 (GvcMixerCardProfile, 1);
|
|
profile->profile = g_strdup (pi.name);
|
|
profile->human_profile = g_strdup (pi.description);
|
|
profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources);
|
|
profile->n_sinks = pi.n_sinks;
|
|
profile->n_sources = pi.n_sources;
|
|
profile->priority = pi.priority;
|
|
list = g_list_prepend (list, profile);
|
|
}
|
|
card = gvc_mixer_card_new (control->priv->pa_context,
|
|
info->index);
|
|
gvc_mixer_card_set_profiles (card, list);
|
|
is_new = TRUE;
|
|
}
|
|
|
|
gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description"));
|
|
gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name"));
|
|
gvc_mixer_card_set_profile (card, info->active_profile->name);
|
|
|
|
if (is_new) {
|
|
g_hash_table_insert (control->priv->cards,
|
|
GUINT_TO_POINTER (info->index),
|
|
g_object_ref (card));
|
|
}
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[CARD_ADDED],
|
|
0,
|
|
info->index);
|
|
}
|
|
|
|
static void
|
|
_pa_context_get_sink_info_cb (pa_context *context,
|
|
const pa_sink_info *i,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
if (eol < 0) {
|
|
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
|
|
return;
|
|
}
|
|
|
|
g_warning ("Sink callback failure");
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
dec_outstanding (control);
|
|
return;
|
|
}
|
|
|
|
update_sink (control, i);
|
|
}
|
|
|
|
static void
|
|
_pa_context_get_source_info_cb (pa_context *context,
|
|
const pa_source_info *i,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
if (eol < 0) {
|
|
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
|
|
return;
|
|
}
|
|
|
|
g_warning ("Source callback failure");
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
dec_outstanding (control);
|
|
return;
|
|
}
|
|
|
|
update_source (control, i);
|
|
}
|
|
|
|
static void
|
|
_pa_context_get_sink_input_info_cb (pa_context *context,
|
|
const pa_sink_input_info *i,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
if (eol < 0) {
|
|
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
|
|
return;
|
|
}
|
|
|
|
g_warning ("Sink input callback failure");
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
dec_outstanding (control);
|
|
return;
|
|
}
|
|
|
|
update_sink_input (control, i);
|
|
}
|
|
|
|
static void
|
|
_pa_context_get_source_output_info_cb (pa_context *context,
|
|
const pa_source_output_info *i,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
if (eol < 0) {
|
|
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
|
|
return;
|
|
}
|
|
|
|
g_warning ("Source output callback failure");
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
dec_outstanding (control);
|
|
return;
|
|
}
|
|
|
|
update_source_output (control, i);
|
|
}
|
|
|
|
static void
|
|
_pa_context_get_client_info_cb (pa_context *context,
|
|
const pa_client_info *i,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
if (eol < 0) {
|
|
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
|
|
return;
|
|
}
|
|
|
|
g_warning ("Client callback failure");
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
dec_outstanding (control);
|
|
return;
|
|
}
|
|
|
|
update_client (control, i);
|
|
}
|
|
|
|
static void
|
|
_pa_context_get_card_info_by_index_cb (pa_context *context,
|
|
const pa_card_info *i,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
if (eol < 0) {
|
|
if (pa_context_errno (context) == PA_ERR_NOENTITY)
|
|
return;
|
|
|
|
g_warning ("Card callback failure");
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
dec_outstanding (control);
|
|
return;
|
|
}
|
|
|
|
update_card (control, i);
|
|
}
|
|
|
|
static void
|
|
_pa_context_get_server_info_cb (pa_context *context,
|
|
const pa_server_info *i,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
if (i == NULL) {
|
|
g_warning ("Server info callback failure");
|
|
return;
|
|
}
|
|
|
|
update_server (control, i);
|
|
dec_outstanding (control);
|
|
}
|
|
|
|
static void
|
|
remove_event_role_stream (GvcMixerControl *control)
|
|
{
|
|
g_debug ("Removing event role");
|
|
}
|
|
|
|
static void
|
|
update_event_role_stream (GvcMixerControl *control,
|
|
const pa_ext_stream_restore_info *info)
|
|
{
|
|
GvcMixerStream *stream;
|
|
gboolean is_new;
|
|
pa_volume_t max_volume;
|
|
|
|
if (strcmp (info->name, "sink-input-by-media-role:event") != 0) {
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
g_debug ("Updating event role: name='%s' device='%s'",
|
|
info->name,
|
|
info->device);
|
|
#endif
|
|
|
|
is_new = FALSE;
|
|
|
|
if (!control->priv->event_sink_input_is_set) {
|
|
pa_channel_map pa_map;
|
|
GvcChannelMap *map;
|
|
|
|
pa_map.channels = 1;
|
|
pa_map.map[0] = PA_CHANNEL_POSITION_MONO;
|
|
map = gvc_channel_map_new_from_pa_channel_map (&pa_map);
|
|
|
|
stream = gvc_mixer_event_role_new (control->priv->pa_context,
|
|
info->device,
|
|
map);
|
|
control->priv->event_sink_input_id = gvc_mixer_stream_get_id (stream);
|
|
control->priv->event_sink_input_is_set = TRUE;
|
|
|
|
is_new = TRUE;
|
|
} else {
|
|
stream = g_hash_table_lookup (control->priv->all_streams,
|
|
GUINT_TO_POINTER (control->priv->event_sink_input_id));
|
|
}
|
|
|
|
max_volume = pa_cvolume_max (&info->volume);
|
|
|
|
gvc_mixer_stream_set_name (stream, _("System Sounds"));
|
|
gvc_mixer_stream_set_icon_name (stream, "multimedia-volume-control");
|
|
gvc_mixer_stream_set_volume (stream, (guint)max_volume);
|
|
gvc_mixer_stream_set_is_muted (stream, info->mute);
|
|
|
|
if (is_new) {
|
|
add_stream (control, stream);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_pa_ext_stream_restore_read_cb (pa_context *context,
|
|
const pa_ext_stream_restore_info *i,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
if (eol < 0) {
|
|
g_debug ("Failed to initialized stream_restore extension: %s",
|
|
pa_strerror (pa_context_errno (context)));
|
|
remove_event_role_stream (control);
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
dec_outstanding (control);
|
|
/* If we don't have an event stream to restore, then
|
|
* set one up with a default 100% volume */
|
|
if (!control->priv->event_sink_input_is_set) {
|
|
pa_ext_stream_restore_info info;
|
|
|
|
memset (&info, 0, sizeof(info));
|
|
info.name = "sink-input-by-media-role:event";
|
|
info.volume.channels = 1;
|
|
info.volume.values[0] = PA_VOLUME_NORM;
|
|
update_event_role_stream (control, &info);
|
|
}
|
|
return;
|
|
}
|
|
|
|
update_event_role_stream (control, i);
|
|
}
|
|
|
|
static void
|
|
_pa_ext_stream_restore_subscribe_cb (pa_context *context,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
pa_operation *o;
|
|
|
|
o = pa_ext_stream_restore_read (context,
|
|
_pa_ext_stream_restore_read_cb,
|
|
control);
|
|
if (o == NULL) {
|
|
g_warning ("pa_ext_stream_restore_read() failed");
|
|
return;
|
|
}
|
|
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
static void
|
|
req_update_server_info (GvcMixerControl *control,
|
|
int index)
|
|
{
|
|
pa_operation *o;
|
|
|
|
o = pa_context_get_server_info (control->priv->pa_context,
|
|
_pa_context_get_server_info_cb,
|
|
control);
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_get_server_info() failed");
|
|
return;
|
|
}
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
static void
|
|
req_update_client_info (GvcMixerControl *control,
|
|
int index)
|
|
{
|
|
pa_operation *o;
|
|
|
|
if (index < 0) {
|
|
o = pa_context_get_client_info_list (control->priv->pa_context,
|
|
_pa_context_get_client_info_cb,
|
|
control);
|
|
} else {
|
|
o = pa_context_get_client_info (control->priv->pa_context,
|
|
index,
|
|
_pa_context_get_client_info_cb,
|
|
control);
|
|
}
|
|
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_client_info_list() failed");
|
|
return;
|
|
}
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
static void
|
|
req_update_card (GvcMixerControl *control,
|
|
int index)
|
|
{
|
|
pa_operation *o;
|
|
|
|
if (index < 0) {
|
|
o = pa_context_get_card_info_list (control->priv->pa_context,
|
|
_pa_context_get_card_info_by_index_cb,
|
|
control);
|
|
} else {
|
|
o = pa_context_get_card_info_by_index (control->priv->pa_context,
|
|
index,
|
|
_pa_context_get_card_info_by_index_cb,
|
|
control);
|
|
}
|
|
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_get_card_info_by_index() failed");
|
|
return;
|
|
}
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
static void
|
|
req_update_sink_info (GvcMixerControl *control,
|
|
int index)
|
|
{
|
|
pa_operation *o;
|
|
|
|
if (index < 0) {
|
|
o = pa_context_get_sink_info_list (control->priv->pa_context,
|
|
_pa_context_get_sink_info_cb,
|
|
control);
|
|
} else {
|
|
o = pa_context_get_sink_info_by_index (control->priv->pa_context,
|
|
index,
|
|
_pa_context_get_sink_info_cb,
|
|
control);
|
|
}
|
|
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_get_sink_info_list() failed");
|
|
return;
|
|
}
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
static void
|
|
req_update_source_info (GvcMixerControl *control,
|
|
int index)
|
|
{
|
|
pa_operation *o;
|
|
|
|
if (index < 0) {
|
|
o = pa_context_get_source_info_list (control->priv->pa_context,
|
|
_pa_context_get_source_info_cb,
|
|
control);
|
|
} else {
|
|
o = pa_context_get_source_info_by_index(control->priv->pa_context,
|
|
index,
|
|
_pa_context_get_source_info_cb,
|
|
control);
|
|
}
|
|
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_get_source_info_list() failed");
|
|
return;
|
|
}
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
static void
|
|
req_update_sink_input_info (GvcMixerControl *control,
|
|
int index)
|
|
{
|
|
pa_operation *o;
|
|
|
|
if (index < 0) {
|
|
o = pa_context_get_sink_input_info_list (control->priv->pa_context,
|
|
_pa_context_get_sink_input_info_cb,
|
|
control);
|
|
} else {
|
|
o = pa_context_get_sink_input_info (control->priv->pa_context,
|
|
index,
|
|
_pa_context_get_sink_input_info_cb,
|
|
control);
|
|
}
|
|
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_get_sink_input_info_list() failed");
|
|
return;
|
|
}
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
static void
|
|
req_update_source_output_info (GvcMixerControl *control,
|
|
int index)
|
|
{
|
|
pa_operation *o;
|
|
|
|
if (index < 0) {
|
|
o = pa_context_get_source_output_info_list (control->priv->pa_context,
|
|
_pa_context_get_source_output_info_cb,
|
|
control);
|
|
} else {
|
|
o = pa_context_get_source_output_info (control->priv->pa_context,
|
|
index,
|
|
_pa_context_get_source_output_info_cb,
|
|
control);
|
|
}
|
|
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_get_source_output_info_list() failed");
|
|
return;
|
|
}
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
static void
|
|
remove_client (GvcMixerControl *control,
|
|
guint index)
|
|
{
|
|
g_hash_table_remove (control->priv->clients,
|
|
GUINT_TO_POINTER (index));
|
|
}
|
|
|
|
static void
|
|
remove_card (GvcMixerControl *control,
|
|
guint index)
|
|
{
|
|
g_hash_table_remove (control->priv->cards,
|
|
GUINT_TO_POINTER (index));
|
|
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[CARD_REMOVED],
|
|
0,
|
|
index);
|
|
}
|
|
|
|
static void
|
|
remove_sink (GvcMixerControl *control,
|
|
guint index)
|
|
{
|
|
GvcMixerStream *stream;
|
|
|
|
#if 0
|
|
g_debug ("Removing sink: index=%u", index);
|
|
#endif
|
|
|
|
stream = g_hash_table_lookup (control->priv->sinks,
|
|
GUINT_TO_POINTER (index));
|
|
if (stream == NULL) {
|
|
return;
|
|
}
|
|
g_hash_table_remove (control->priv->sinks,
|
|
GUINT_TO_POINTER (index));
|
|
|
|
remove_stream (control, stream);
|
|
}
|
|
|
|
static void
|
|
remove_source (GvcMixerControl *control,
|
|
guint index)
|
|
{
|
|
GvcMixerStream *stream;
|
|
|
|
#if 0
|
|
g_debug ("Removing source: index=%u", index);
|
|
#endif
|
|
|
|
stream = g_hash_table_lookup (control->priv->sources,
|
|
GUINT_TO_POINTER (index));
|
|
if (stream == NULL) {
|
|
return;
|
|
}
|
|
g_hash_table_remove (control->priv->sources,
|
|
GUINT_TO_POINTER (index));
|
|
|
|
remove_stream (control, stream);
|
|
}
|
|
|
|
static void
|
|
remove_sink_input (GvcMixerControl *control,
|
|
guint index)
|
|
{
|
|
GvcMixerStream *stream;
|
|
|
|
#if 0
|
|
g_debug ("Removing sink input: index=%u", index);
|
|
#endif
|
|
stream = g_hash_table_lookup (control->priv->sink_inputs,
|
|
GUINT_TO_POINTER (index));
|
|
if (stream == NULL) {
|
|
return;
|
|
}
|
|
g_hash_table_remove (control->priv->sink_inputs,
|
|
GUINT_TO_POINTER (index));
|
|
|
|
remove_stream (control, stream);
|
|
}
|
|
|
|
static void
|
|
remove_source_output (GvcMixerControl *control,
|
|
guint index)
|
|
{
|
|
GvcMixerStream *stream;
|
|
|
|
#if 0
|
|
g_debug ("Removing source output: index=%u", index);
|
|
#endif
|
|
|
|
stream = g_hash_table_lookup (control->priv->source_outputs,
|
|
GUINT_TO_POINTER (index));
|
|
if (stream == NULL) {
|
|
return;
|
|
}
|
|
g_hash_table_remove (control->priv->source_outputs,
|
|
GUINT_TO_POINTER (index));
|
|
|
|
remove_stream (control, stream);
|
|
}
|
|
|
|
static void
|
|
_pa_context_subscribe_cb (pa_context *context,
|
|
pa_subscription_event_type_t t,
|
|
uint32_t index,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
|
|
case PA_SUBSCRIPTION_EVENT_SINK:
|
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
|
remove_sink (control, index);
|
|
} else {
|
|
req_update_sink_info (control, index);
|
|
}
|
|
break;
|
|
|
|
case PA_SUBSCRIPTION_EVENT_SOURCE:
|
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
|
remove_source (control, index);
|
|
} else {
|
|
req_update_source_info (control, index);
|
|
}
|
|
break;
|
|
|
|
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
|
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
|
remove_sink_input (control, index);
|
|
} else {
|
|
req_update_sink_input_info (control, index);
|
|
}
|
|
break;
|
|
|
|
case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
|
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
|
remove_source_output (control, index);
|
|
} else {
|
|
req_update_source_output_info (control, index);
|
|
}
|
|
break;
|
|
|
|
case PA_SUBSCRIPTION_EVENT_CLIENT:
|
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
|
remove_client (control, index);
|
|
} else {
|
|
req_update_client_info (control, index);
|
|
}
|
|
break;
|
|
|
|
case PA_SUBSCRIPTION_EVENT_SERVER:
|
|
req_update_server_info (control, index);
|
|
break;
|
|
|
|
case PA_SUBSCRIPTION_EVENT_CARD:
|
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
|
remove_card (control, index);
|
|
} else {
|
|
req_update_card (control, index);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_ready (GvcMixerControl *control)
|
|
{
|
|
pa_operation *o;
|
|
|
|
pa_context_set_subscribe_callback (control->priv->pa_context,
|
|
_pa_context_subscribe_cb,
|
|
control);
|
|
o = pa_context_subscribe (control->priv->pa_context,
|
|
(pa_subscription_mask_t)
|
|
(PA_SUBSCRIPTION_MASK_SINK|
|
|
PA_SUBSCRIPTION_MASK_SOURCE|
|
|
PA_SUBSCRIPTION_MASK_SINK_INPUT|
|
|
PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
|
|
PA_SUBSCRIPTION_MASK_CLIENT|
|
|
PA_SUBSCRIPTION_MASK_SERVER|
|
|
PA_SUBSCRIPTION_MASK_CARD),
|
|
NULL,
|
|
NULL);
|
|
|
|
if (o == NULL) {
|
|
g_warning ("pa_context_subscribe() failed");
|
|
return;
|
|
}
|
|
pa_operation_unref (o);
|
|
|
|
req_update_server_info (control, -1);
|
|
req_update_client_info (control, -1);
|
|
req_update_sink_info (control, -1);
|
|
req_update_source_info (control, -1);
|
|
req_update_sink_input_info (control, -1);
|
|
req_update_source_output_info (control, -1);
|
|
req_update_card (control, -1);
|
|
|
|
control->priv->n_outstanding = 6;
|
|
|
|
/* This call is not always supported */
|
|
o = pa_ext_stream_restore_read (control->priv->pa_context,
|
|
_pa_ext_stream_restore_read_cb,
|
|
control);
|
|
if (o != NULL) {
|
|
pa_operation_unref (o);
|
|
control->priv->n_outstanding++;
|
|
|
|
pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context,
|
|
_pa_ext_stream_restore_subscribe_cb,
|
|
control);
|
|
|
|
o = pa_ext_stream_restore_subscribe (control->priv->pa_context,
|
|
1,
|
|
NULL,
|
|
NULL);
|
|
if (o != NULL) {
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
} else {
|
|
g_debug ("Failed to initialized stream_restore extension: %s",
|
|
pa_strerror (pa_context_errno (control->priv->pa_context)));
|
|
}
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_new_pa_context (GvcMixerControl *self)
|
|
{
|
|
pa_proplist *proplist;
|
|
|
|
g_return_if_fail (self);
|
|
g_return_if_fail (!self->priv->pa_context);
|
|
|
|
proplist = pa_proplist_new ();
|
|
pa_proplist_sets (proplist,
|
|
PA_PROP_APPLICATION_NAME,
|
|
self->priv->name);
|
|
pa_proplist_sets (proplist,
|
|
PA_PROP_APPLICATION_ID,
|
|
"org.gnome.VolumeControl");
|
|
pa_proplist_sets (proplist,
|
|
PA_PROP_APPLICATION_ICON_NAME,
|
|
"multimedia-volume-control");
|
|
pa_proplist_sets (proplist,
|
|
PA_PROP_APPLICATION_VERSION,
|
|
PACKAGE_VERSION);
|
|
|
|
self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist);
|
|
|
|
pa_proplist_free (proplist);
|
|
g_assert (self->priv->pa_context);
|
|
}
|
|
|
|
static void
|
|
remove_all_streams (GvcMixerControl *control, GHashTable *hash_table)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
g_hash_table_iter_init (&iter, hash_table);
|
|
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
|
remove_stream (control, value);
|
|
g_hash_table_iter_remove (&iter);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
idle_reconnect (gpointer data)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (data);
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
g_return_val_if_fail (control, FALSE);
|
|
|
|
if (control->priv->pa_context) {
|
|
pa_context_unref (control->priv->pa_context);
|
|
control->priv->pa_context = NULL;
|
|
gvc_mixer_new_pa_context (control);
|
|
}
|
|
|
|
remove_all_streams (control, control->priv->sinks);
|
|
remove_all_streams (control, control->priv->sources);
|
|
remove_all_streams (control, control->priv->sink_inputs);
|
|
remove_all_streams (control, control->priv->source_outputs);
|
|
|
|
g_hash_table_iter_init (&iter, control->priv->clients);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
g_hash_table_iter_remove (&iter);
|
|
|
|
gvc_mixer_control_open (control); /* cannot fail */
|
|
|
|
control->priv->reconnect_id = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
_pa_context_state_cb (pa_context *context,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
|
|
|
|
switch (pa_context_get_state (context)) {
|
|
case PA_CONTEXT_UNCONNECTED:
|
|
case PA_CONTEXT_CONNECTING:
|
|
case PA_CONTEXT_AUTHORIZING:
|
|
case PA_CONTEXT_SETTING_NAME:
|
|
break;
|
|
|
|
case PA_CONTEXT_READY:
|
|
gvc_mixer_control_ready (control);
|
|
break;
|
|
|
|
case PA_CONTEXT_FAILED:
|
|
g_warning ("Connection failed, reconnecting...");
|
|
if (control->priv->reconnect_id == 0)
|
|
control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control);
|
|
break;
|
|
|
|
case PA_CONTEXT_TERMINATED:
|
|
default:
|
|
/* FIXME: */
|
|
break;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gvc_mixer_control_open (GvcMixerControl *control)
|
|
{
|
|
int res;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
|
|
g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
|
|
g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE);
|
|
|
|
pa_context_set_state_callback (control->priv->pa_context,
|
|
_pa_context_state_cb,
|
|
control);
|
|
|
|
g_signal_emit (G_OBJECT (control), signals[CONNECTING], 0);
|
|
res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, NULL);
|
|
if (res < 0) {
|
|
g_warning ("Failed to connect context: %s",
|
|
pa_strerror (pa_context_errno (control->priv->pa_context)));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
gboolean
|
|
gvc_mixer_control_close (GvcMixerControl *control)
|
|
{
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
|
|
g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
|
|
|
|
pa_context_disconnect (control->priv->pa_context);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_dispose (GObject *object)
|
|
{
|
|
GvcMixerControl *control = GVC_MIXER_CONTROL (object);
|
|
|
|
if (control->priv->reconnect_id != 0) {
|
|
g_source_remove (control->priv->reconnect_id);
|
|
control->priv->reconnect_id = 0;
|
|
}
|
|
|
|
if (control->priv->pa_context != NULL) {
|
|
pa_context_unref (control->priv->pa_context);
|
|
control->priv->pa_context = NULL;
|
|
}
|
|
|
|
if (control->priv->default_source_name != NULL) {
|
|
g_free (control->priv->default_source_name);
|
|
control->priv->default_source_name = NULL;
|
|
}
|
|
if (control->priv->default_sink_name != NULL) {
|
|
g_free (control->priv->default_sink_name);
|
|
control->priv->default_sink_name = NULL;
|
|
}
|
|
|
|
if (control->priv->pa_mainloop != NULL) {
|
|
pa_glib_mainloop_free (control->priv->pa_mainloop);
|
|
control->priv->pa_mainloop = NULL;
|
|
}
|
|
|
|
if (control->priv->all_streams != NULL) {
|
|
g_hash_table_destroy (control->priv->all_streams);
|
|
control->priv->all_streams = NULL;
|
|
}
|
|
|
|
if (control->priv->sinks != NULL) {
|
|
g_hash_table_destroy (control->priv->sinks);
|
|
control->priv->sinks = NULL;
|
|
}
|
|
if (control->priv->sources != NULL) {
|
|
g_hash_table_destroy (control->priv->sources);
|
|
control->priv->sources = NULL;
|
|
}
|
|
if (control->priv->sink_inputs != NULL) {
|
|
g_hash_table_destroy (control->priv->sink_inputs);
|
|
control->priv->sink_inputs = NULL;
|
|
}
|
|
if (control->priv->source_outputs != NULL) {
|
|
g_hash_table_destroy (control->priv->source_outputs);
|
|
control->priv->source_outputs = NULL;
|
|
}
|
|
if (control->priv->clients != NULL) {
|
|
g_hash_table_destroy (control->priv->clients);
|
|
control->priv->clients = NULL;
|
|
}
|
|
if (control->priv->cards != NULL) {
|
|
g_hash_table_destroy (control->priv->cards);
|
|
control->priv->cards = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GvcMixerControl *self = GVC_MIXER_CONTROL (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_NAME:
|
|
g_free (self->priv->name);
|
|
self->priv->name = g_value_dup_string (value);
|
|
g_object_notify (G_OBJECT (self), "name");
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GvcMixerControl *self = GVC_MIXER_CONTROL (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_NAME:
|
|
g_value_set_string (value, self->priv->name);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static GObject *
|
|
gvc_mixer_control_constructor (GType type,
|
|
guint n_construct_properties,
|
|
GObjectConstructParam *construct_params)
|
|
{
|
|
GObject *object;
|
|
GvcMixerControl *self;
|
|
|
|
object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params);
|
|
|
|
self = GVC_MIXER_CONTROL (object);
|
|
|
|
gvc_mixer_new_pa_context (self);
|
|
|
|
return object;
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_class_init (GvcMixerControlClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->constructor = gvc_mixer_control_constructor;
|
|
object_class->dispose = gvc_mixer_control_dispose;
|
|
object_class->finalize = gvc_mixer_control_finalize;
|
|
object_class->set_property = gvc_mixer_control_set_property;
|
|
object_class->get_property = gvc_mixer_control_get_property;
|
|
|
|
g_object_class_install_property (object_class,
|
|
PROP_NAME,
|
|
g_param_spec_string ("name",
|
|
"Name",
|
|
"Name to display for this mixer control",
|
|
NULL,
|
|
G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
signals [CONNECTING] =
|
|
g_signal_new ("connecting",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, connecting),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
signals [READY] =
|
|
g_signal_new ("ready",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, ready),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
signals [STREAM_ADDED] =
|
|
g_signal_new ("stream-added",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, stream_added),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [STREAM_REMOVED] =
|
|
g_signal_new ("stream-removed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, stream_removed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [CARD_ADDED] =
|
|
g_signal_new ("card-added",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, card_added),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [CARD_REMOVED] =
|
|
g_signal_new ("card-removed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, card_removed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [DEFAULT_SINK_CHANGED] =
|
|
g_signal_new ("default-sink-changed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, default_sink_changed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [DEFAULT_SOURCE_CHANGED] =
|
|
g_signal_new ("default-source-changed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, default_source_changed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
|
|
g_type_class_add_private (klass, sizeof (GvcMixerControlPrivate));
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_init (GvcMixerControl *control)
|
|
{
|
|
control->priv = GVC_MIXER_CONTROL_GET_PRIVATE (control);
|
|
|
|
control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ());
|
|
g_assert (control->priv->pa_mainloop);
|
|
|
|
control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop);
|
|
g_assert (control->priv->pa_api);
|
|
|
|
control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
|
|
control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
|
|
control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
|
|
control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
|
|
control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
|
|
control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
|
|
|
|
control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_finalize (GObject *object)
|
|
{
|
|
GvcMixerControl *mixer_control;
|
|
|
|
g_return_if_fail (object != NULL);
|
|
g_return_if_fail (GVC_IS_MIXER_CONTROL (object));
|
|
|
|
mixer_control = GVC_MIXER_CONTROL (object);
|
|
g_free (mixer_control->priv->name);
|
|
mixer_control->priv->name = NULL;
|
|
|
|
g_return_if_fail (mixer_control->priv != NULL);
|
|
G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object);
|
|
}
|
|
|
|
GvcMixerControl *
|
|
gvc_mixer_control_new (const char *name)
|
|
{
|
|
GObject *control;
|
|
control = g_object_new (GVC_TYPE_MIXER_CONTROL,
|
|
"name", name,
|
|
NULL);
|
|
return GVC_MIXER_CONTROL (control);
|
|
}
|
|
|
|
/* FIXME: Remove when PA 0.9.23 is used */
|
|
#ifndef PA_VOLUME_UI_MAX
|
|
#define PA_VOLUME_UI_MAX pa_sw_volume_from_dB(+11.0)
|
|
#endif
|
|
|
|
gdouble
|
|
gvc_mixer_control_get_vol_max_norm (GvcMixerControl *control)
|
|
{
|
|
return (gdouble) PA_VOLUME_NORM;
|
|
}
|
|
|
|
gdouble
|
|
gvc_mixer_control_get_vol_max_amplified (GvcMixerControl *control)
|
|
{
|
|
return (gdouble) PA_VOLUME_UI_MAX;
|
|
}
|