mirror of
https://gitlab.gnome.org/GNOME/libgnome-volume-control.git
synced 2024-11-23 17:30:40 -05:00
74c08620b4
This will allow to have different icons for internal audio cards (which are flagged generically as "audio-card"), depending on which port is in use (ie. headphones or speakers). This requires the new icon information, which is only exported by PulseAudio 3.0. If it's not available, we fallback to card icons like before. https://bugzilla.gnome.org/show_bug.cgi?id=689931
3357 lines
126 KiB
C
3357 lines
126 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
|
|
* Copyright (C) 2012 Conor Curran
|
|
*
|
|
* 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"
|
|
#include "gvc-mixer-ui-device.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_sink_stream; /* new default sink stream, used in gvc_mixer_control_set_default_sink () */
|
|
GvcMixerStream *new_default_source_stream; /* new default source stream, used in gvc_mixer_control_set_default_source () */
|
|
|
|
GHashTable *ui_outputs; /* UI visible outputs */
|
|
GHashTable *ui_inputs; /* UI visible inputs */
|
|
|
|
/* When we change profile on a device that is not the server default sink,
|
|
* it will jump back to the default sink set by the server to prevent the
|
|
* audio setup from being 'outputless'.
|
|
*
|
|
* All well and good but then when we get the new stream created for the
|
|
* new profile how do we know that this is the intended default or selected
|
|
* device the user wishes to use. */
|
|
guint profile_swapping_device_id;
|
|
|
|
GvcMixerControlState state;
|
|
};
|
|
|
|
enum {
|
|
STATE_CHANGED,
|
|
STREAM_ADDED,
|
|
STREAM_REMOVED,
|
|
CARD_ADDED,
|
|
CARD_REMOVED,
|
|
DEFAULT_SINK_CHANGED,
|
|
DEFAULT_SOURCE_CHANGED,
|
|
ACTIVE_OUTPUT_UPDATE,
|
|
ACTIVE_INPUT_UPDATE,
|
|
OUTPUT_ADDED,
|
|
INPUT_ADDED,
|
|
OUTPUT_REMOVED,
|
|
INPUT_REMOVED,
|
|
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,
|
|
GvcMixerStream *new_stream,
|
|
const pa_ext_stream_restore_info *info,
|
|
GvcMixerControl *control)
|
|
{
|
|
pa_operation *o;
|
|
pa_ext_stream_restore_info new_info;
|
|
|
|
if (new_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 (new_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, new_info.device);
|
|
|
|
pa_operation_unref (o);
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_stream_restore_sink_cb (pa_context *c,
|
|
const pa_ext_stream_restore_info *info,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = (GvcMixerControl *) userdata;
|
|
if (eol || info == NULL || !g_str_has_prefix(info->name, "sink-input-by"))
|
|
return;
|
|
gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_sink_stream, info, control);
|
|
}
|
|
|
|
static void
|
|
gvc_mixer_control_stream_restore_source_cb (pa_context *c,
|
|
const pa_ext_stream_restore_info *info,
|
|
int eol,
|
|
void *userdata)
|
|
{
|
|
GvcMixerControl *control = (GvcMixerControl *) userdata;
|
|
if (eol || info == NULL || !g_str_has_prefix(info->name, "source-output-by"))
|
|
return;
|
|
gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_source_stream, info, control);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_lookup_device_from_stream:
|
|
* @control:
|
|
* @stream:
|
|
*
|
|
* Returns: (transfer none): a #GvcUIDevice or %NULL
|
|
*/
|
|
GvcMixerUIDevice *
|
|
gvc_mixer_control_lookup_device_from_stream (GvcMixerControl *control,
|
|
GvcMixerStream *stream)
|
|
{
|
|
GList *devices, *d;
|
|
gboolean is_network_stream;
|
|
const GList *ports;
|
|
GvcMixerUIDevice *ret;
|
|
|
|
if (GVC_IS_MIXER_SOURCE (stream))
|
|
devices = g_hash_table_get_values (control->priv->ui_inputs);
|
|
else
|
|
devices = g_hash_table_get_values (control->priv->ui_outputs);
|
|
|
|
ret = NULL;
|
|
ports = gvc_mixer_stream_get_ports (stream);
|
|
is_network_stream = (ports == NULL);
|
|
|
|
for (d = devices; d != NULL; d = d->next) {
|
|
GvcMixerUIDevice *device = d->data;
|
|
gint stream_id = G_MAXINT;
|
|
|
|
g_object_get (G_OBJECT (device),
|
|
"stream-id", &stream_id,
|
|
NULL);
|
|
|
|
if (is_network_stream &&
|
|
stream_id == gvc_mixer_stream_get_id (stream)) {
|
|
g_debug ("lookup device from stream - %s - it is a network_stream ",
|
|
gvc_mixer_ui_device_get_description (device));
|
|
ret = device;
|
|
break;
|
|
} else if (!is_network_stream) {
|
|
const GvcMixerStreamPort *port;
|
|
port = gvc_mixer_stream_get_port (stream);
|
|
|
|
if (stream_id == gvc_mixer_stream_get_id (stream) &&
|
|
g_strcmp0 (gvc_mixer_ui_device_get_port (device),
|
|
port->port) == 0) {
|
|
g_debug ("lookup-device-from-stream found device: device description '%s', device port = '%s', device stream id %i AND stream port = '%s' stream id '%u' and stream description '%s'",
|
|
gvc_mixer_ui_device_get_description (device),
|
|
gvc_mixer_ui_device_get_port (device),
|
|
stream_id,
|
|
port->port,
|
|
gvc_mixer_stream_get_id (stream),
|
|
gvc_mixer_stream_get_description (stream));
|
|
ret = device;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_debug ("gvc_mixer_control_lookup_device_from_stream - Could not find a device for stream '%s'",gvc_mixer_stream_get_description (stream));
|
|
|
|
g_list_free (devices);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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);
|
|
|
|
g_debug ("about to set default sink on server");
|
|
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_sink_stream = stream;
|
|
g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_sink_stream);
|
|
|
|
o = pa_ext_stream_restore_read (control->priv->pa_context,
|
|
gvc_mixer_control_stream_restore_sink_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)
|
|
{
|
|
GvcMixerUIDevice* input;
|
|
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);
|
|
|
|
control->priv->new_default_source_stream = stream;
|
|
g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_source_stream);
|
|
|
|
o = pa_ext_stream_restore_read (control->priv->pa_context,
|
|
gvc_mixer_control_stream_restore_source_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);
|
|
|
|
/* source change successful, update the UI. */
|
|
input = gvc_mixer_control_lookup_device_from_stream (control, stream);
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[ACTIVE_INPUT_UPDATE],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (input));
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_lookup_output_id:
|
|
* @control:
|
|
* @id:
|
|
*
|
|
* Returns: (transfer none):
|
|
*/
|
|
GvcMixerUIDevice *
|
|
gvc_mixer_control_lookup_output_id (GvcMixerControl *control,
|
|
guint id)
|
|
{
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
return gvc_mixer_control_lookup_id (control->priv->ui_outputs, id);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_lookup_input_id:
|
|
* @control:
|
|
* @id:
|
|
*
|
|
* Returns: (transfer none):
|
|
*/
|
|
GvcMixerUIDevice *
|
|
gvc_mixer_control_lookup_input_id (GvcMixerControl *control,
|
|
guint id)
|
|
{
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
|
|
return gvc_mixer_control_lookup_id (control->priv->ui_inputs, id);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_get_stream_from_device:
|
|
* @control:
|
|
* @device:
|
|
*
|
|
* Returns: (transfer none):
|
|
*/
|
|
GvcMixerStream *
|
|
gvc_mixer_control_get_stream_from_device (GvcMixerControl *control,
|
|
GvcMixerUIDevice *device)
|
|
{
|
|
gint stream_id;
|
|
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
|
|
g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
|
|
|
|
stream_id = gvc_mixer_ui_device_get_stream_id (device);
|
|
|
|
if (stream_id == GVC_MIXER_UI_DEVICE_INVALID) {
|
|
g_debug ("gvc_mixer_control_get_stream_from_device - device has a null stream");
|
|
return NULL;
|
|
}
|
|
return gvc_mixer_control_lookup_stream_id (control, stream_id);
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_change_profile_on_selected_device:
|
|
* @control:
|
|
* @device:
|
|
* @profile: Can be null if any profile present on this port is okay
|
|
*
|
|
* Returns: This method will attempt to swap the profile on the card of
|
|
* the device with given profile name. If successfull it will set the
|
|
* preferred profile on that device so as we know the next time the user
|
|
* moves to that device it should have this profile active.
|
|
*/
|
|
gboolean
|
|
gvc_mixer_control_change_profile_on_selected_device (GvcMixerControl *control,
|
|
GvcMixerUIDevice *device,
|
|
const gchar *profile)
|
|
{
|
|
const gchar *best_profile;
|
|
GvcMixerCardProfile *current_profile;
|
|
GvcMixerCard *card;
|
|
|
|
g_object_get (G_OBJECT (device), "card", &card, NULL);
|
|
current_profile = gvc_mixer_card_get_profile (card);
|
|
|
|
if (current_profile)
|
|
best_profile = gvc_mixer_ui_device_get_best_profile (device, profile, current_profile->profile);
|
|
else
|
|
best_profile = profile;
|
|
|
|
g_assert (best_profile);
|
|
|
|
g_debug ("Selected '%s', moving to profile '%s' on card '%s' on stream id %i",
|
|
profile ? profile : "(any)", best_profile,
|
|
gvc_mixer_card_get_name (card),
|
|
gvc_mixer_ui_device_get_stream_id (device));
|
|
|
|
g_debug ("default sink name = %s and default sink id %u",
|
|
control->priv->default_sink_name,
|
|
control->priv->default_sink_id);
|
|
|
|
control->priv->profile_swapping_device_id = gvc_mixer_ui_device_get_id (device);
|
|
|
|
if (gvc_mixer_card_change_profile (card, best_profile)) {
|
|
gvc_mixer_ui_device_set_user_preferred_profile (device, best_profile);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* gvc_mixer_control_change_output:
|
|
* @control:
|
|
* @output:
|
|
* This method is called from the UI when the user selects a previously unselected device.
|
|
* - Firstly it queries the stream from the device.
|
|
* - It assumes that if the stream is null that it cannot be a bluetooth or network stream (they never show unless they have valid sinks and sources)
|
|
* In the scenario of a NULL stream on the device
|
|
* - It fetches the device's preferred profile or if NUll the profile with the highest priority on that device.
|
|
* - It then caches this device in control->priv->cached_desired_output_id so that when the update_sink triggered
|
|
* from when we attempt to change profile we will know exactly what device to highlight on that stream.
|
|
* - It attempts to swap the profile on the card from that device and returns.
|
|
* - Next, it handles network or bluetooth streams that only require their stream to be made the default.
|
|
* - Next it deals with port changes so if the stream's active port is not the same as the port on the device
|
|
* it will attempt to change the port on that stream to be same as the device. If this fails it will return.
|
|
* - Finally it will set this new stream to be the default stream and emit a signal for the UI confirming the active output device.
|
|
*/
|
|
void
|
|
gvc_mixer_control_change_output (GvcMixerControl *control,
|
|
GvcMixerUIDevice* output)
|
|
{
|
|
GvcMixerStream *stream;
|
|
GvcMixerStream *default_stream;
|
|
const GvcMixerStreamPort *active_port;
|
|
const gchar *output_port;
|
|
|
|
g_debug ("control change output");
|
|
|
|
stream = gvc_mixer_control_get_stream_from_device (control, output);
|
|
if (stream == NULL) {
|
|
gvc_mixer_control_change_profile_on_selected_device (control,
|
|
output, NULL);
|
|
return;
|
|
}
|
|
|
|
/* Handle a network sink as a portless or cardless device */
|
|
if (!gvc_mixer_ui_device_has_ports (output)) {
|
|
g_debug ("Did we try to move to a software/bluetooth sink ?");
|
|
if (gvc_mixer_control_set_default_sink (control, stream)) {
|
|
/* sink change was successful, update the UI.*/
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[ACTIVE_OUTPUT_UPDATE],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (output));
|
|
}
|
|
else {
|
|
g_warning ("Failed to set default sink with stream from output %s",
|
|
gvc_mixer_ui_device_get_description (output));
|
|
}
|
|
return;
|
|
}
|
|
|
|
active_port = gvc_mixer_stream_get_port (stream);
|
|
output_port = gvc_mixer_ui_device_get_port (output);
|
|
/* First ensure the correct port is active on the sink */
|
|
if (g_strcmp0 (active_port->port, output_port) != 0) {
|
|
g_debug ("Port change, switch to = %s", output_port);
|
|
if (gvc_mixer_stream_change_port (stream, output_port) == FALSE) {
|
|
g_warning ("Could not change port !");
|
|
return;
|
|
}
|
|
}
|
|
|
|
default_stream = gvc_mixer_control_get_default_sink (control);
|
|
|
|
/* Finally if we are not on the correct stream, swap over. */
|
|
if (stream != default_stream) {
|
|
GvcMixerUIDevice* output;
|
|
|
|
g_debug ("Attempting to swap over to stream %s ",
|
|
gvc_mixer_stream_get_description (stream));
|
|
if (gvc_mixer_control_set_default_sink (control, stream)) {
|
|
output = gvc_mixer_control_lookup_device_from_stream (control, stream);
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[ACTIVE_OUTPUT_UPDATE],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (output));
|
|
} else {
|
|
/* If the move failed for some reason reset the UI. */
|
|
output = gvc_mixer_control_lookup_device_from_stream (control, default_stream);
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[ACTIVE_OUTPUT_UPDATE],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (output));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* gvc_mixer_control_change_input:
|
|
* @control:
|
|
* @input:
|
|
* This method is called from the UI when the user selects a previously unselected device.
|
|
* - Firstly it queries the stream from the device.
|
|
* - It assumes that if the stream is null that it cannot be a bluetooth or network stream (they never show unless they have valid sinks and sources)
|
|
* In the scenario of a NULL stream on the device
|
|
* - It fetches the device's preferred profile or if NUll the profile with the highest priority on that device.
|
|
* - It then caches this device in control->priv->cached_desired_input_id so that when the update_source triggered
|
|
* from when we attempt to change profile we will know exactly what device to highlight on that stream.
|
|
* - It attempts to swap the profile on the card from that device and returns.
|
|
* - Next, it handles network or bluetooth streams that only require their stream to be made the default.
|
|
* - Next it deals with port changes so if the stream's active port is not the same as the port on the device
|
|
* it will attempt to change the port on that stream to be same as the device. If this fails it will return.
|
|
* - Finally it will set this new stream to be the default stream and emit a signal for the UI confirming the active input device.
|
|
*/
|
|
void
|
|
gvc_mixer_control_change_input (GvcMixerControl *control,
|
|
GvcMixerUIDevice* input)
|
|
{
|
|
GvcMixerStream *stream;
|
|
GvcMixerStream *default_stream;
|
|
const GvcMixerStreamPort *active_port;
|
|
const gchar *input_port;
|
|
|
|
stream = gvc_mixer_control_get_stream_from_device (control, input);
|
|
if (stream == NULL) {
|
|
gvc_mixer_control_change_profile_on_selected_device (control,
|
|
input, NULL);
|
|
return;
|
|
}
|
|
|
|
/* Handle a network sink as a portless/cardless device */
|
|
if (!gvc_mixer_ui_device_has_ports (input)) {
|
|
g_debug ("Did we try to move to a software/bluetooth source ?");
|
|
if (! gvc_mixer_control_set_default_source (control, stream)) {
|
|
g_warning ("Failed to set default source with stream from input %s",
|
|
gvc_mixer_ui_device_get_description (input));
|
|
}
|
|
return;
|
|
}
|
|
|
|
active_port = gvc_mixer_stream_get_port (stream);
|
|
input_port = gvc_mixer_ui_device_get_port (input);
|
|
/* First ensure the correct port is active on the sink */
|
|
if (g_strcmp0 (active_port->port, input_port) != 0) {
|
|
g_debug ("Port change, switch to = %s", input_port);
|
|
if (gvc_mixer_stream_change_port (stream, input_port) == FALSE) {
|
|
g_warning ("Could not change port!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
default_stream = gvc_mixer_control_get_default_source (control);
|
|
|
|
/* Finally if we are not on the correct stream, swap over. */
|
|
if (stream != default_stream) {
|
|
g_debug ("change-input - attempting to swap over to stream %s",
|
|
gvc_mixer_stream_get_description (stream));
|
|
gvc_mixer_control_set_default_source (control, stream);
|
|
}
|
|
}
|
|
|
|
|
|
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) {
|
|
control->priv->state = GVC_STATE_READY;
|
|
g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_READY);
|
|
}
|
|
}
|
|
|
|
GvcMixerControlState
|
|
gvc_mixer_control_get_state (GvcMixerControl *control)
|
|
{
|
|
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
|
|
|
|
return control->priv->state;
|
|
}
|
|
|
|
static void
|
|
on_default_source_port_notify (GObject *object,
|
|
GParamSpec *pspec,
|
|
GvcMixerControl *control)
|
|
{
|
|
char *port;
|
|
GvcMixerUIDevice *input;
|
|
|
|
g_object_get (object, "port", &port, NULL);
|
|
input = gvc_mixer_control_lookup_device_from_stream (control,
|
|
GVC_MIXER_STREAM (object));
|
|
|
|
g_debug ("on_default_source_port_notify - moved to port '%s' which SHOULD ?? correspond to output '%s'",
|
|
port,
|
|
gvc_mixer_ui_device_get_description (input));
|
|
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[ACTIVE_INPUT_UPDATE],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (input));
|
|
|
|
g_free (port);
|
|
}
|
|
|
|
|
|
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) {
|
|
GvcMixerUIDevice *input;
|
|
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);
|
|
|
|
if (control->priv->default_source_is_set) {
|
|
g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_source (control),
|
|
on_default_source_port_notify,
|
|
control);
|
|
}
|
|
|
|
g_signal_connect (stream,
|
|
"notify::port",
|
|
G_CALLBACK (on_default_source_port_notify),
|
|
control);
|
|
|
|
input = gvc_mixer_control_lookup_device_from_stream (control, stream);
|
|
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[ACTIVE_INPUT_UPDATE],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (input));
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_default_sink_port_notify (GObject *object,
|
|
GParamSpec *pspec,
|
|
GvcMixerControl *control)
|
|
{
|
|
char *port;
|
|
GvcMixerUIDevice *output;
|
|
|
|
g_object_get (object, "port", &port, NULL);
|
|
|
|
output = gvc_mixer_control_lookup_device_from_stream (control,
|
|
GVC_MIXER_STREAM (object));
|
|
if (output != NULL) {
|
|
g_debug ("on_default_sink_port_notify - moved to port %s - which SHOULD correspond to output %s",
|
|
port,
|
|
gvc_mixer_ui_device_get_description (output));
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[ACTIVE_OUTPUT_UPDATE],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (output));
|
|
}
|
|
g_free (port);
|
|
}
|
|
|
|
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) {
|
|
GvcMixerUIDevice *output;
|
|
if (control->priv->default_sink_is_set) {
|
|
g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_sink (control),
|
|
on_default_sink_port_notify,
|
|
control);
|
|
}
|
|
|
|
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);
|
|
|
|
g_signal_connect (stream,
|
|
"notify::port",
|
|
G_CALLBACK (on_default_sink_port_notify),
|
|
control);
|
|
|
|
output = gvc_mixer_control_lookup_device_from_stream (control, stream);
|
|
|
|
g_debug ("active_sink change");
|
|
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[ACTIVE_OUTPUT_UPDATE],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (output));
|
|
}
|
|
}
|
|
|
|
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) {
|
|
g_debug ("update server");
|
|
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));
|
|
}
|
|
|
|
/* This method will match individual stream ports against its corresponding device
|
|
* It does this by:
|
|
* - iterates through our devices and finds the one where the card-id on the device is the same as the card-id on the stream
|
|
* and the port-name on the device is the same as the streamport-name.
|
|
* This should always find a match and is used exclusively by sync_devices().
|
|
*/
|
|
static gboolean
|
|
match_stream_with_devices (GvcMixerControl *control,
|
|
GvcMixerStreamPort *stream_port,
|
|
GvcMixerStream *stream)
|
|
{
|
|
GList *devices, *d;
|
|
guint stream_card_id;
|
|
guint stream_id;
|
|
gboolean in_possession = FALSE;
|
|
|
|
stream_id = gvc_mixer_stream_get_id (stream);
|
|
stream_card_id = gvc_mixer_stream_get_card_index (stream);
|
|
|
|
devices = g_hash_table_get_values (GVC_IS_MIXER_SOURCE (stream) ? control->priv->ui_inputs : control->priv->ui_outputs);
|
|
|
|
for (d = devices; d != NULL; d = d->next) {
|
|
GvcMixerUIDevice *device;
|
|
gint device_stream_id;
|
|
gchar *device_port_name;
|
|
gchar *origin;
|
|
gchar *description;
|
|
GvcMixerCard *card;
|
|
gint card_id;
|
|
|
|
device = d->data;
|
|
g_object_get (G_OBJECT (device),
|
|
"stream-id", &device_stream_id,
|
|
"card", &card,
|
|
"origin", &origin,
|
|
"description", &description,
|
|
"port-name", &device_port_name,
|
|
NULL);
|
|
|
|
card_id = gvc_mixer_card_get_index (card);
|
|
|
|
g_debug ("Attempt to match_stream update_with_existing_outputs - Try description : '%s', origin : '%s', device port name : '%s', card : %p, AGAINST stream port: '%s', sink card id %i",
|
|
description,
|
|
origin,
|
|
device_port_name,
|
|
card,
|
|
stream_port->port,
|
|
stream_card_id);
|
|
|
|
if (stream_card_id == card_id &&
|
|
g_strcmp0 (device_port_name, stream_port->port) == 0) {
|
|
g_debug ("Match device with stream: We have a match with description: '%s', origin: '%s', cached already with device id %u, so set stream id to %i",
|
|
description,
|
|
origin,
|
|
gvc_mixer_ui_device_get_id (device),
|
|
stream_id);
|
|
|
|
g_object_set (G_OBJECT (device),
|
|
"stream-id", (gint)stream_id,
|
|
NULL);
|
|
in_possession = TRUE;
|
|
}
|
|
|
|
g_free (device_port_name);
|
|
g_free (origin);
|
|
g_free (description);
|
|
|
|
if (in_possession == TRUE)
|
|
break;
|
|
}
|
|
|
|
g_list_free (devices);
|
|
return in_possession;
|
|
}
|
|
|
|
/*
|
|
* This method attempts to match a sink or source with its relevant UI device.
|
|
* GvcMixerStream can represent both a sink or source.
|
|
* Using static card port introspection implies that we know beforehand what
|
|
* outputs and inputs are available to the user.
|
|
* But that does not mean that all of these inputs and outputs are available to be used.
|
|
* For instance we might be able to see that there is a HDMI port available but if
|
|
* we are on the default analog stereo output profile there is no valid sink for
|
|
* that HDMI device. We first need to change profile and when update_sink() is called
|
|
* only then can we match the new hdmi sink with its corresponding device.
|
|
*
|
|
* Firstly it checks to see if the incoming stream has no ports.
|
|
* - If a stream has no ports but has a valid card ID (bluetooth), it will attempt
|
|
* to match the device with the stream using the card id.
|
|
* - If a stream has no ports and no valid card id, it goes ahead and makes a new
|
|
* device (software/network devices are only detectable at the sink/source level)
|
|
* If the stream has ports it will match each port against the stream using match_stream_with_devices().
|
|
*
|
|
* This method should always find a match.
|
|
*/
|
|
static void
|
|
sync_devices (GvcMixerControl *control,
|
|
GvcMixerStream* stream)
|
|
{
|
|
/* Go through ports to see what outputs can be created. */
|
|
const GList *stream_ports;
|
|
const GList *n = NULL;
|
|
gboolean is_output = !GVC_IS_MIXER_SOURCE (stream);
|
|
gint stream_port_count = 0;
|
|
|
|
stream_ports = gvc_mixer_stream_get_ports (stream);
|
|
|
|
if (stream_ports == NULL) {
|
|
GvcMixerUIDevice *device;
|
|
/* Bluetooth, no ports but a valid card */
|
|
if (gvc_mixer_stream_get_card_index (stream) != PA_INVALID_INDEX) {
|
|
GList *devices, *d;
|
|
gboolean in_possession = FALSE;
|
|
|
|
devices = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
|
|
|
|
for (d = devices; d != NULL; d = d->next) {
|
|
GvcMixerCard *card;
|
|
gint card_id;
|
|
|
|
device = d->data;
|
|
|
|
g_object_get (G_OBJECT (device),
|
|
"card", &card,
|
|
NULL);
|
|
card_id = gvc_mixer_card_get_index (card);
|
|
g_debug ("sync devices, device description - '%s', device card id - %i, stream description - %s, stream card id - %i",
|
|
gvc_mixer_ui_device_get_description (device),
|
|
card_id,
|
|
gvc_mixer_stream_get_description (stream),
|
|
gvc_mixer_stream_get_card_index (stream));
|
|
if (card_id == gvc_mixer_stream_get_card_index (stream)) {
|
|
in_possession = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
g_list_free (devices);
|
|
|
|
if (!in_possession) {
|
|
g_warning ("Couldn't match the portless stream (with card) - '%s' is it an input ? -> %i, streams card id -> %i",
|
|
gvc_mixer_stream_get_description (stream),
|
|
GVC_IS_MIXER_SOURCE (stream),
|
|
gvc_mixer_stream_get_card_index (stream));
|
|
return;
|
|
}
|
|
|
|
g_object_set (G_OBJECT (device),
|
|
"stream-id", (gint)gvc_mixer_stream_get_id (stream),
|
|
"description", gvc_mixer_stream_get_description (stream),
|
|
"origin", "", /*Leave it empty for these special cases*/
|
|
"port-name", NULL,
|
|
"port-available", TRUE,
|
|
NULL);
|
|
} else { /* Network sink/source has no ports and no card. */
|
|
GObject *object;
|
|
|
|
object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
|
|
"stream-id", (gint)gvc_mixer_stream_get_id (stream),
|
|
"description", gvc_mixer_stream_get_description (stream),
|
|
"origin", "", /* Leave it empty for these special cases */
|
|
"port-name", NULL,
|
|
"port-available", TRUE,
|
|
NULL);
|
|
device = GVC_MIXER_UI_DEVICE (object);
|
|
|
|
g_hash_table_insert (is_output ? control->priv->ui_outputs : control->priv->ui_inputs,
|
|
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)),
|
|
g_object_ref (device));
|
|
|
|
}
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[is_output ? OUTPUT_ADDED : INPUT_ADDED],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (device));
|
|
|
|
return;
|
|
}
|
|
|
|
/* Go ahead and make sure to match each port against a previously created device */
|
|
for (n = stream_ports; n != NULL; n = n->next) {
|
|
|
|
GvcMixerStreamPort *stream_port;
|
|
stream_port = n->data;
|
|
stream_port_count ++;
|
|
|
|
if (match_stream_with_devices (control, stream_port, stream))
|
|
continue;
|
|
|
|
g_warning ("Sync_devices: Failed to match stream id: %u, description: '%s', origin: '%s'",
|
|
gvc_mixer_stream_get_id (stream),
|
|
stream_port->human_port,
|
|
gvc_mixer_stream_get_description (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);
|
|
}
|
|
|
|
/*
|
|
* Called when anything changes with a sink.
|
|
*/
|
|
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) {
|
|
GList *list = NULL;
|
|
guint i;
|
|
|
|
map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
|
|
stream = gvc_mixer_sink_new (control->priv->pa_context,
|
|
info->index,
|
|
map);
|
|
|
|
for (i = 0; i < info->n_ports; i++) {
|
|
GvcMixerStreamPort *port;
|
|
|
|
port = g_slice_new0 (GvcMixerStreamPort);
|
|
port->port = g_strdup (info->ports[i]->name);
|
|
port->human_port = g_strdup (info->ports[i]->description);
|
|
port->priority = info->ports[i]->priority;
|
|
port->available = info->ports[i]->available != PA_PORT_AVAILABLE_NO;
|
|
|
|
list = g_list_prepend (list, port);
|
|
}
|
|
gvc_mixer_stream_set_ports (stream, list);
|
|
|
|
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_form_factor (stream, pa_proplist_gets (info->proplist, PA_PROP_DEVICE_FORM_FACTOR));
|
|
gvc_mixer_stream_set_sysfs_path (stream, pa_proplist_gets (info->proplist, "sysfs.path"));
|
|
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);
|
|
|
|
/* Messy I know but to set the port everytime regardless of whether it has changed will cost us a
|
|
* port change notify signal which causes the frontend to resync.
|
|
* Only update the UI when something has changed. */
|
|
if (info->active_port != NULL) {
|
|
if (is_new)
|
|
gvc_mixer_stream_set_port (stream, info->active_port->name);
|
|
else {
|
|
const GvcMixerStreamPort *active_port;
|
|
active_port = gvc_mixer_stream_get_port (stream);
|
|
if (active_port == NULL ||
|
|
g_strcmp0 (active_port->port, info->active_port->name) != 0) {
|
|
g_debug ("update sink - apparently a port update");
|
|
gvc_mixer_stream_set_port (stream, info->active_port->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_new) {
|
|
g_debug ("update sink - is new");
|
|
|
|
g_hash_table_insert (control->priv->sinks,
|
|
GUINT_TO_POINTER (info->index),
|
|
g_object_ref (stream));
|
|
add_stream (control, stream);
|
|
/* Always sink on a new stream to able to assign the right stream id
|
|
* to the appropriate outputs (multiple potential outputs per stream). */
|
|
sync_devices (control, stream);
|
|
}
|
|
|
|
/*
|
|
* When we change profile on a device that is not the server default sink,
|
|
* it will jump back to the default sink set by the server to prevent the audio setup from being 'outputless'.
|
|
* All well and good but then when we get the new stream created for the new profile how do we know
|
|
* that this is the intended default or selected device the user wishes to use.
|
|
* This is messy but it's the only reliable way that it can be done without ripping the whole thing apart.
|
|
*/
|
|
if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
|
|
GvcMixerUIDevice *dev = NULL;
|
|
dev = gvc_mixer_control_lookup_output_id (control, control->priv->profile_swapping_device_id);
|
|
if (dev != NULL) {
|
|
/* now check to make sure this new stream is the same stream just matched and set on the device object */
|
|
if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
|
|
g_debug ("Looks like we profile swapped on a non server default sink");
|
|
gvc_mixer_control_set_default_sink (control, stream);
|
|
}
|
|
}
|
|
control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
|
|
}
|
|
|
|
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) {
|
|
GList *list = NULL;
|
|
guint i;
|
|
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);
|
|
|
|
for (i = 0; i < info->n_ports; i++) {
|
|
GvcMixerStreamPort *port;
|
|
|
|
port = g_slice_new0 (GvcMixerStreamPort);
|
|
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);
|
|
|
|
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_form_factor (stream, pa_proplist_gets (info->proplist, PA_PROP_DEVICE_FORM_FACTOR));
|
|
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);
|
|
g_debug ("update source");
|
|
|
|
if (info->active_port != NULL) {
|
|
if (is_new)
|
|
gvc_mixer_stream_set_port (stream, info->active_port->name);
|
|
else {
|
|
const GvcMixerStreamPort *active_port;
|
|
active_port = gvc_mixer_stream_get_port (stream);
|
|
if (active_port == NULL ||
|
|
g_strcmp0 (active_port->port, info->active_port->name) != 0) {
|
|
g_debug ("update source - apparently a port update");
|
|
gvc_mixer_stream_set_port (stream, info->active_port->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_new) {
|
|
g_hash_table_insert (control->priv->sources,
|
|
GUINT_TO_POINTER (info->index),
|
|
g_object_ref (stream));
|
|
add_stream (control, stream);
|
|
sync_devices (control, stream);
|
|
}
|
|
|
|
if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
|
|
GvcMixerUIDevice *dev = NULL;
|
|
|
|
dev = gvc_mixer_control_lookup_input_id (control, control->priv->profile_swapping_device_id);
|
|
|
|
if (dev != NULL) {
|
|
/* now check to make sure this new stream is the same stream just matched and set on the device object */
|
|
if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
|
|
g_debug ("Looks like we profile swapped on a non server default sink");
|
|
gvc_mixer_control_set_default_source (control, stream);
|
|
}
|
|
}
|
|
control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
|
|
}
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* A utility method to gather which card profiles are relevant to the port .
|
|
*/
|
|
static GList *
|
|
determine_profiles_for_port (pa_card_port_info *port,
|
|
GList* card_profiles)
|
|
{
|
|
gint i;
|
|
GList *supported_profiles = NULL;
|
|
GList *p;
|
|
for (i = 0; i < port->n_profiles; i++) {
|
|
for (p = card_profiles; p != NULL; p = p->next) {
|
|
GvcMixerCardProfile *prof;
|
|
prof = p->data;
|
|
if (g_strcmp0 (port->profiles[i]->name, prof->profile) == 0)
|
|
supported_profiles = g_list_append (supported_profiles, prof);
|
|
}
|
|
}
|
|
g_debug ("%i profiles supported on port %s",
|
|
g_list_length (supported_profiles),
|
|
port->description);
|
|
return g_list_sort (supported_profiles, (GCompareFunc) gvc_mixer_card_profile_compare);
|
|
}
|
|
|
|
static gboolean
|
|
is_card_port_an_output (GvcMixerCardPort* port)
|
|
{
|
|
return port->direction == PA_DIRECTION_OUTPUT ? TRUE : FALSE;
|
|
}
|
|
|
|
/*
|
|
* This method will create a ui device for the given port.
|
|
*/
|
|
static void
|
|
create_ui_device_from_port (GvcMixerControl* control,
|
|
GvcMixerCardPort* port,
|
|
GvcMixerCard* card)
|
|
{
|
|
GvcMixerUIDeviceDirection direction;
|
|
GObject *object;
|
|
GvcMixerUIDevice *uidevice;
|
|
gboolean available = port->available != PA_PORT_AVAILABLE_NO;
|
|
|
|
direction = (is_card_port_an_output (port) == TRUE) ? UIDeviceOutput : UIDeviceInput;
|
|
|
|
object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
|
|
"type", (uint)direction,
|
|
"card", card,
|
|
"port-name", port->port,
|
|
"description", port->human_port,
|
|
"origin", gvc_mixer_card_get_name (card),
|
|
"port-available", available,
|
|
"icon-name", port->icon_name,
|
|
NULL);
|
|
|
|
uidevice = GVC_MIXER_UI_DEVICE (object);
|
|
gvc_mixer_ui_device_set_profiles (uidevice, port->profiles);
|
|
|
|
g_hash_table_insert (is_card_port_an_output (port) ? control->priv->ui_outputs : control->priv->ui_inputs,
|
|
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (uidevice)),
|
|
g_object_ref (uidevice));
|
|
|
|
|
|
if (available) {
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[is_card_port_an_output (port) ? OUTPUT_ADDED : INPUT_ADDED],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (uidevice));
|
|
}
|
|
|
|
g_debug ("create_ui_device_from_port, direction %u, description '%s', origin '%s', port available %i",
|
|
direction,
|
|
port->human_port,
|
|
gvc_mixer_card_get_name (card),
|
|
available);
|
|
}
|
|
|
|
/*
|
|
* This method will match up GvcMixerCardPorts with existing devices.
|
|
* A match is achieved if the device's card-id and the port's card-id are the same
|
|
* && the device's port-name and the card-port's port member are the same.
|
|
* A signal is then sent adding or removing that device from the UI depending on the availability of the port.
|
|
*/
|
|
static void
|
|
match_card_port_with_existing_device (GvcMixerControl *control,
|
|
GvcMixerCardPort *card_port,
|
|
GvcMixerCard *card,
|
|
gboolean available)
|
|
{
|
|
GList *d;
|
|
GList *devices;
|
|
GvcMixerUIDevice *device;
|
|
gboolean is_output = is_card_port_an_output (card_port);
|
|
|
|
devices = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
|
|
|
|
for (d = devices; d != NULL; d = d->next) {
|
|
GvcMixerCard *device_card;
|
|
gchar *device_port_name;
|
|
|
|
device = d->data;
|
|
g_object_get (G_OBJECT (device),
|
|
"card", &device_card,
|
|
"port-name", &device_port_name,
|
|
NULL);
|
|
|
|
if (g_strcmp0 (card_port->port, device_port_name) == 0 &&
|
|
device_card == card) {
|
|
g_debug ("Found the relevant device %s, update its port availability flag to %i, is_output %i",
|
|
device_port_name,
|
|
available,
|
|
is_output);
|
|
g_object_set (G_OBJECT (device),
|
|
"port-available", available, NULL);
|
|
g_signal_emit (G_OBJECT (control),
|
|
is_output ? signals[available ? OUTPUT_ADDED : OUTPUT_REMOVED] : signals[available ? INPUT_ADDED : INPUT_REMOVED],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (device));
|
|
}
|
|
g_free (device_port_name);
|
|
}
|
|
|
|
g_list_free (devices);
|
|
}
|
|
|
|
static void
|
|
create_ui_device_from_card (GvcMixerControl *control,
|
|
GvcMixerCard *card)
|
|
{
|
|
GObject *object;
|
|
GvcMixerUIDevice *in;
|
|
GvcMixerUIDevice *out;
|
|
const GList *profiles;
|
|
|
|
/* For now just create two devices and presume this device is multi directional
|
|
* Ensure to remove both on card removal (available to false by default) */
|
|
profiles = gvc_mixer_card_get_profiles (card);
|
|
|
|
g_debug ("Portless card just registered - %i", gvc_mixer_card_get_index (card));
|
|
|
|
object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
|
|
"type", UIDeviceInput,
|
|
"description", gvc_mixer_card_get_name (card),
|
|
"origin", "", /* Leave it empty for these special cases */
|
|
"port-name", NULL,
|
|
"port-available", FALSE,
|
|
"card", card,
|
|
NULL);
|
|
in = GVC_MIXER_UI_DEVICE (object);
|
|
gvc_mixer_ui_device_set_profiles (in, profiles);
|
|
|
|
g_hash_table_insert (control->priv->ui_inputs,
|
|
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (in)),
|
|
g_object_ref (in));
|
|
object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
|
|
"type", UIDeviceOutput,
|
|
"description", gvc_mixer_card_get_name (card),
|
|
"origin", "", /* Leave it empty for these special cases */
|
|
"port-name", NULL,
|
|
"port-available", FALSE,
|
|
"card", card,
|
|
NULL);
|
|
out = GVC_MIXER_UI_DEVICE (object);
|
|
gvc_mixer_ui_device_set_profiles (out, profiles);
|
|
|
|
g_hash_table_insert (control->priv->ui_outputs,
|
|
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (out)),
|
|
g_object_ref (out));
|
|
}
|
|
|
|
/*
|
|
* At this point we can determine all devices available to us (besides network 'ports')
|
|
* This is done by the following:
|
|
*
|
|
* - gvc_mixer_card and gvc_mixer_card_ports are created and relevant setters are called.
|
|
* - First it checks to see if it's a portless card. Bluetooth devices are portless AFAIHS.
|
|
* If so it creates two devices, an input and an output.
|
|
* - If it's a 'normal' card with ports it will create a new ui-device or
|
|
* synchronise port availability with the existing device cached for that port on this card. */
|
|
|
|
static void
|
|
update_card (GvcMixerControl *control,
|
|
const pa_card_info *info)
|
|
{
|
|
const GList *card_ports = NULL;
|
|
const GList *m = NULL;
|
|
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 *profile_list = NULL;
|
|
GList *port_list = NULL;
|
|
|
|
for (i = 0; i < info->n_profiles; i++) {
|
|
GvcMixerCardProfile *profile;
|
|
struct pa_card_profile_info pi = info->profiles[i];
|
|
|
|
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;
|
|
profile_list = g_list_prepend (profile_list, profile);
|
|
}
|
|
card = gvc_mixer_card_new (control->priv->pa_context,
|
|
info->index);
|
|
gvc_mixer_card_set_profiles (card, profile_list);
|
|
|
|
for (i = 0; i < info->n_ports; i++) {
|
|
GvcMixerCardPort *port;
|
|
port = g_new0 (GvcMixerCardPort, 1);
|
|
port->port = g_strdup (info->ports[i]->name);
|
|
port->human_port = g_strdup (info->ports[i]->description);
|
|
port->priority = info->ports[i]->priority;
|
|
port->available = info->ports[i]->available;
|
|
port->direction = info->ports[i]->direction;
|
|
port->icon_name = g_strdup (pa_proplist_gets (info->ports[i]->proplist, "device.icon_name"));
|
|
port->profiles = determine_profiles_for_port (info->ports[i], profile_list);
|
|
port_list = g_list_prepend (port_list, port);
|
|
}
|
|
gvc_mixer_card_set_ports (card, port_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));
|
|
}
|
|
|
|
card_ports = gvc_mixer_card_get_ports (card);
|
|
|
|
if (card_ports == NULL && is_new) {
|
|
g_debug ("Portless card just registered - %s", gvc_mixer_card_get_name (card));
|
|
create_ui_device_from_card (control, card);
|
|
}
|
|
|
|
for (m = card_ports; m != NULL; m = m->next) {
|
|
GvcMixerCardPort *card_port;
|
|
card_port = m->data;
|
|
if (is_new)
|
|
create_ui_device_from_port (control, card_port, card);
|
|
else {
|
|
for (i = 0; i < info->n_ports; i++) {
|
|
if (g_strcmp0 (card_port->port, info->ports[i]->name) == 0) {
|
|
if (card_port->available != info->ports[i]->available) {
|
|
card_port->available = info->ports[i]->available;
|
|
g_debug ("sync port availability on card %i, card port name '%s', new available value %i",
|
|
gvc_mixer_card_get_index (card),
|
|
card_port->port,
|
|
card_port->available);
|
|
match_card_port_with_existing_device (control,
|
|
card_port,
|
|
card,
|
|
card_port->available != PA_PORT_AVAILABLE_NO);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
g_debug ("get server info");
|
|
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)
|
|
{
|
|
|
|
GList *devices, *d;
|
|
|
|
devices = g_list_concat (g_hash_table_get_values (control->priv->ui_inputs),
|
|
g_hash_table_get_values (control->priv->ui_outputs));
|
|
|
|
for (d = devices; d != NULL; d = d->next) {
|
|
GvcMixerCard *card;
|
|
GvcMixerUIDevice *device = d->data;
|
|
|
|
g_object_get (G_OBJECT (device), "card", &card, NULL);
|
|
|
|
if (gvc_mixer_card_get_index (card) == index) {
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[gvc_mixer_ui_device_is_output (device) ? OUTPUT_REMOVED : INPUT_REMOVED],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (device));
|
|
g_debug ("Card removal remove device %s",
|
|
gvc_mixer_ui_device_get_description (device));
|
|
g_hash_table_remove (gvc_mixer_ui_device_is_output (device) ? control->priv->ui_outputs : control->priv->ui_inputs,
|
|
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)));
|
|
}
|
|
}
|
|
|
|
g_list_free (devices);
|
|
|
|
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;
|
|
GvcMixerUIDevice *device;
|
|
|
|
g_debug ("Removing sink: index=%u", index);
|
|
|
|
stream = g_hash_table_lookup (control->priv->sinks,
|
|
GUINT_TO_POINTER (index));
|
|
if (stream == NULL)
|
|
return;
|
|
|
|
device = gvc_mixer_control_lookup_device_from_stream (control, stream);
|
|
|
|
if (device != NULL) {
|
|
gvc_mixer_ui_device_invalidate_stream (device);
|
|
if (!gvc_mixer_ui_device_has_ports (device)) {
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[OUTPUT_REMOVED],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (device));
|
|
} else {
|
|
GList *devices, *d;
|
|
|
|
devices = g_hash_table_get_values (control->priv->ui_outputs);
|
|
|
|
for (d = devices; d != NULL; d = d->next) {
|
|
gint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
|
|
device = d->data;
|
|
g_object_get (G_OBJECT (device),
|
|
"stream-id", &stream_id,
|
|
NULL);
|
|
if (stream_id == gvc_mixer_stream_get_id (stream))
|
|
gvc_mixer_ui_device_invalidate_stream (device);
|
|
}
|
|
|
|
g_list_free (devices);
|
|
}
|
|
}
|
|
|
|
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;
|
|
GvcMixerUIDevice *device;
|
|
|
|
g_debug ("Removing source: index=%u", index);
|
|
|
|
stream = g_hash_table_lookup (control->priv->sources,
|
|
GUINT_TO_POINTER (index));
|
|
if (stream == NULL)
|
|
return;
|
|
|
|
device = gvc_mixer_control_lookup_device_from_stream (control, stream);
|
|
|
|
if (device != NULL) {
|
|
gvc_mixer_ui_device_invalidate_stream (device);
|
|
if (!gvc_mixer_ui_device_has_ports (device)) {
|
|
g_signal_emit (G_OBJECT (control),
|
|
signals[INPUT_REMOVED],
|
|
0,
|
|
gvc_mixer_ui_device_get_id (device));
|
|
} else {
|
|
GList *devices, *d;
|
|
|
|
devices = g_hash_table_get_values (control->priv->ui_inputs);
|
|
|
|
for (d = devices; d != NULL; d = d->next) {
|
|
gint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
|
|
device = d->data;
|
|
g_object_get (G_OBJECT (device),
|
|
"stream-id", &stream_id,
|
|
NULL);
|
|
if (stream_id == gvc_mixer_stream_get_id (stream))
|
|
gvc_mixer_ui_device_invalidate_stream (device);
|
|
}
|
|
|
|
g_list_free (devices);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
g_debug ("Removing sink input: index=%u", index);
|
|
|
|
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;
|
|
|
|
g_debug ("Removing source output: index=%u", index);
|
|
|
|
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_card (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);
|
|
|
|
|
|
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:
|
|
control->priv->state = GVC_STATE_FAILED;
|
|
g_signal_emit (control, signals[STATE_CHANGED], 0, GVC_STATE_FAILED);
|
|
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);
|
|
|
|
control->priv->state = GVC_STATE_CONNECTING;
|
|
g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CONNECTING);
|
|
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);
|
|
|
|
control->priv->state = GVC_STATE_CLOSED;
|
|
g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CLOSED);
|
|
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;
|
|
}
|
|
if (control->priv->ui_outputs != NULL) {
|
|
g_hash_table_destroy (control->priv->ui_outputs);
|
|
control->priv->ui_outputs = NULL;
|
|
}
|
|
if (control->priv->ui_inputs != NULL) {
|
|
g_hash_table_destroy (control->priv->ui_inputs);
|
|
control->priv->ui_inputs = 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);
|
|
self->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
|
|
|
|
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 [STATE_CHANGED] =
|
|
g_signal_new ("state-changed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, state_changed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
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);
|
|
signals [ACTIVE_OUTPUT_UPDATE] =
|
|
g_signal_new ("active-output-update",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, active_output_update),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [ACTIVE_INPUT_UPDATE] =
|
|
g_signal_new ("active-input-update",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, active_input_update),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [OUTPUT_ADDED] =
|
|
g_signal_new ("output-added",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, output_added),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [INPUT_ADDED] =
|
|
g_signal_new ("input-added",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, input_added),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [OUTPUT_REMOVED] =
|
|
g_signal_new ("output-removed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, output_removed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
signals [INPUT_REMOVED] =
|
|
g_signal_new ("input-removed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GvcMixerControlClass, input_removed),
|
|
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->ui_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
|
|
control->priv->ui_inputs = 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);
|
|
|
|
control->priv->state = GVC_STATE_CLOSED;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|