1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
3
* Copyright (C) 2006-2008 Lennart Poettering
4
* Copyright (C) 2008 Sjoerd Simons <sjoerd@luon.net>
5
* Copyright (C) 2008 William Jon McCann
6
* Copyright (C) 2012 Conor Curran
8
* This program is free software; you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License as published by
10
* the Free Software Foundation; either version 2 of the License, or
11
* (at your option) any later version.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
* GNU General Public License for more details.
18
* You should have received a copy of the GNU General Public License
19
* along with this program; if not, write to the Free Software
20
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31
#include <glib/gi18n-lib.h>
33
#include <pulse/pulseaudio.h>
34
#include <pulse/glib-mainloop.h>
35
#include <pulse/ext-stream-restore.h>
37
#include "gvc-mixer-control.h"
38
#include "gvc-mixer-sink.h"
39
#include "gvc-mixer-source.h"
40
#include "gvc-mixer-sink-input.h"
41
#include "gvc-mixer-source-output.h"
42
#include "gvc-mixer-event-role.h"
43
#include "gvc-mixer-card.h"
44
#include "gvc-mixer-card-private.h"
45
#include "gvc-channel-map-private.h"
46
#include "gvc-mixer-control-private.h"
47
#include "gvc-mixer-ui-device.h"
49
#define GVC_MIXER_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlPrivate))
51
#define RECONNECT_DELAY 5
58
struct GvcMixerControlPrivate
60
pa_glib_mainloop *pa_mainloop;
61
pa_mainloop_api *pa_api;
62
pa_context *pa_context;
67
gboolean default_sink_is_set;
68
guint default_sink_id;
69
char *default_sink_name;
70
gboolean default_source_is_set;
71
guint default_source_id;
72
char *default_source_name;
74
gboolean event_sink_input_is_set;
75
guint event_sink_input_id;
77
GHashTable *all_streams;
78
GHashTable *sinks; /* fixed outputs */
79
GHashTable *sources; /* fixed inputs */
80
GHashTable *sink_inputs; /* routable output streams */
81
GHashTable *source_outputs; /* routable input streams */
85
GvcMixerStream *new_default_sink_stream; /* new default sink stream, used in gvc_mixer_control_set_default_sink () */
86
GvcMixerStream *new_default_source_stream; /* new default source stream, used in gvc_mixer_control_set_default_source () */
88
GHashTable *ui_outputs; /* UI visible outputs */
89
GHashTable *ui_inputs; /* UI visible inputs */
91
/* When we change profile on a device that is not the server default sink,
92
* it will jump back to the default sink set by the server to prevent the
93
* audio setup from being 'outputless'.
95
* All well and good but then when we get the new stream created for the
96
* new profile how do we know that this is the intended default or selected
97
* device the user wishes to use. */
98
guint profile_swapping_device_id;
100
GvcMixerControlState state;
109
DEFAULT_SINK_CHANGED,
110
DEFAULT_SOURCE_CHANGED,
111
ACTIVE_OUTPUT_UPDATE,
120
static guint signals [LAST_SIGNAL] = { 0, };
122
static void gvc_mixer_control_class_init (GvcMixerControlClass *klass);
123
static void gvc_mixer_control_init (GvcMixerControl *mixer_control);
124
static void gvc_mixer_control_finalize (GObject *object);
126
G_DEFINE_TYPE (GvcMixerControl, gvc_mixer_control, G_TYPE_OBJECT)
129
gvc_mixer_control_get_pa_context (GvcMixerControl *control)
131
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
132
return control->priv->pa_context;
136
* gvc_mixer_control_get_event_sink_input:
139
* Returns: (transfer none):
142
gvc_mixer_control_get_event_sink_input (GvcMixerControl *control)
144
GvcMixerStream *stream;
146
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
148
stream = g_hash_table_lookup (control->priv->all_streams,
149
GUINT_TO_POINTER (control->priv->event_sink_input_id));
155
gvc_mixer_control_stream_restore_cb (pa_context *c,
156
GvcMixerStream *new_stream,
157
const pa_ext_stream_restore_info *info,
158
GvcMixerControl *control)
161
pa_ext_stream_restore_info new_info;
163
if (new_stream == NULL)
166
new_info.name = info->name;
167
new_info.channel_map = info->channel_map;
168
new_info.volume = info->volume;
169
new_info.mute = info->mute;
171
new_info.device = gvc_mixer_stream_get_name (new_stream);
173
o = pa_ext_stream_restore_write (control->priv->pa_context,
179
g_warning ("pa_ext_stream_restore_write() failed: %s",
180
pa_strerror (pa_context_errno (control->priv->pa_context)));
184
g_debug ("Changed default device for %s to %s", info->name, new_info.device);
186
pa_operation_unref (o);
190
gvc_mixer_control_stream_restore_sink_cb (pa_context *c,
191
const pa_ext_stream_restore_info *info,
195
GvcMixerControl *control = (GvcMixerControl *) userdata;
196
if (eol || info == NULL || !g_str_has_prefix(info->name, "sink-input-by"))
198
gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_sink_stream, info, control);
202
gvc_mixer_control_stream_restore_source_cb (pa_context *c,
203
const pa_ext_stream_restore_info *info,
207
GvcMixerControl *control = (GvcMixerControl *) userdata;
208
if (eol || info == NULL || !g_str_has_prefix(info->name, "source-output-by"))
210
gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_source_stream, info, control);
214
* gvc_mixer_control_lookup_device_from_stream:
218
* Returns: (transfer none): a #GvcUIDevice or %NULL
221
gvc_mixer_control_lookup_device_from_stream (GvcMixerControl *control,
222
GvcMixerStream *stream)
225
gboolean is_network_stream;
227
GvcMixerUIDevice *ret;
229
if (GVC_IS_MIXER_SOURCE (stream))
230
devices = g_hash_table_get_values (control->priv->ui_inputs);
232
devices = g_hash_table_get_values (control->priv->ui_outputs);
235
ports = gvc_mixer_stream_get_ports (stream);
236
is_network_stream = (ports == NULL);
238
for (d = devices; d != NULL; d = d->next) {
239
GvcMixerUIDevice *device = d->data;
240
gint stream_id = G_MAXINT;
242
g_object_get (G_OBJECT (device),
243
"stream-id", &stream_id,
246
if (is_network_stream &&
247
stream_id == gvc_mixer_stream_get_id (stream)) {
248
g_debug ("lookup device from stream - %s - it is a network_stream ",
249
gvc_mixer_ui_device_get_description (device));
252
} else if (!is_network_stream) {
253
const GvcMixerStreamPort *port;
254
port = gvc_mixer_stream_get_port (stream);
256
if (stream_id == gvc_mixer_stream_get_id (stream) &&
257
g_strcmp0 (gvc_mixer_ui_device_get_port (device),
259
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'",
260
gvc_mixer_ui_device_get_description (device),
261
gvc_mixer_ui_device_get_port (device),
264
gvc_mixer_stream_get_id (stream),
265
gvc_mixer_stream_get_description (stream));
272
g_debug ("gvc_mixer_control_lookup_device_from_stream - Could not find a device for stream '%s'",gvc_mixer_stream_get_description (stream));
274
g_list_free (devices);
280
gvc_mixer_control_set_default_sink (GvcMixerControl *control,
281
GvcMixerStream *stream)
285
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
286
g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
288
g_debug ("about to set default sink on server");
289
o = pa_context_set_default_sink (control->priv->pa_context,
290
gvc_mixer_stream_get_name (stream),
294
g_warning ("pa_context_set_default_sink() failed: %s",
295
pa_strerror (pa_context_errno (control->priv->pa_context)));
299
pa_operation_unref (o);
301
control->priv->new_default_sink_stream = stream;
302
g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_sink_stream);
304
o = pa_ext_stream_restore_read (control->priv->pa_context,
305
gvc_mixer_control_stream_restore_sink_cb,
309
g_warning ("pa_ext_stream_restore_read() failed: %s",
310
pa_strerror (pa_context_errno (control->priv->pa_context)));
314
pa_operation_unref (o);
320
gvc_mixer_control_set_default_source (GvcMixerControl *control,
321
GvcMixerStream *stream)
323
GvcMixerUIDevice* input;
326
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
327
g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
329
o = pa_context_set_default_source (control->priv->pa_context,
330
gvc_mixer_stream_get_name (stream),
334
g_warning ("pa_context_set_default_source() failed");
338
pa_operation_unref (o);
340
control->priv->new_default_source_stream = stream;
341
g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_source_stream);
343
o = pa_ext_stream_restore_read (control->priv->pa_context,
344
gvc_mixer_control_stream_restore_source_cb,
348
g_warning ("pa_ext_stream_restore_read() failed: %s",
349
pa_strerror (pa_context_errno (control->priv->pa_context)));
353
pa_operation_unref (o);
355
/* source change successful, update the UI. */
356
input = gvc_mixer_control_lookup_device_from_stream (control, stream);
357
g_signal_emit (G_OBJECT (control),
358
signals[ACTIVE_INPUT_UPDATE],
360
gvc_mixer_ui_device_get_id (input));
366
* gvc_mixer_control_get_default_sink:
369
* Returns: (transfer none):
372
gvc_mixer_control_get_default_sink (GvcMixerControl *control)
374
GvcMixerStream *stream;
376
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
378
if (control->priv->default_sink_is_set) {
379
stream = g_hash_table_lookup (control->priv->all_streams,
380
GUINT_TO_POINTER (control->priv->default_sink_id));
389
* gvc_mixer_control_get_default_source:
392
* Returns: (transfer none):
395
gvc_mixer_control_get_default_source (GvcMixerControl *control)
397
GvcMixerStream *stream;
399
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
401
if (control->priv->default_source_is_set) {
402
stream = g_hash_table_lookup (control->priv->all_streams,
403
GUINT_TO_POINTER (control->priv->default_source_id));
412
gvc_mixer_control_lookup_id (GHashTable *hash_table,
415
return g_hash_table_lookup (hash_table,
416
GUINT_TO_POINTER (id));
420
* gvc_mixer_control_lookup_stream_id:
424
* Returns: (transfer none):
427
gvc_mixer_control_lookup_stream_id (GvcMixerControl *control,
430
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
432
return gvc_mixer_control_lookup_id (control->priv->all_streams, id);
436
* gvc_mixer_control_lookup_card_id:
440
* Returns: (transfer none):
443
gvc_mixer_control_lookup_card_id (GvcMixerControl *control,
446
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
448
return gvc_mixer_control_lookup_id (control->priv->cards, id);
452
* gvc_mixer_control_lookup_output_id:
456
* Returns: (transfer none):
459
gvc_mixer_control_lookup_output_id (GvcMixerControl *control,
462
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
464
return gvc_mixer_control_lookup_id (control->priv->ui_outputs, id);
468
* gvc_mixer_control_lookup_input_id:
472
* Returns: (transfer none):
475
gvc_mixer_control_lookup_input_id (GvcMixerControl *control,
478
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
480
return gvc_mixer_control_lookup_id (control->priv->ui_inputs, id);
484
* gvc_mixer_control_get_stream_from_device:
488
* Returns: (transfer none):
491
gvc_mixer_control_get_stream_from_device (GvcMixerControl *control,
492
GvcMixerUIDevice *device)
496
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
497
g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
499
stream_id = gvc_mixer_ui_device_get_stream_id (device);
501
if (stream_id == GVC_MIXER_UI_DEVICE_INVALID) {
502
g_debug ("gvc_mixer_control_get_stream_from_device - device has a null stream");
505
return gvc_mixer_control_lookup_stream_id (control, stream_id);
509
* gvc_mixer_control_change_profile_on_selected_device:
512
* @profile: Can be null if any profile present on this port is okay
514
* Returns: This method will attempt to swap the profile on the card of
515
* the device with given profile name. If successfull it will set the
516
* preferred profile on that device so as we know the next time the user
517
* moves to that device it should have this profile active.
520
gvc_mixer_control_change_profile_on_selected_device (GvcMixerControl *control,
521
GvcMixerUIDevice *device,
522
const gchar *profile)
524
const gchar *best_profile;
525
GvcMixerCardProfile *current_profile;
528
g_object_get (G_OBJECT (device), "card", &card, NULL);
529
current_profile = gvc_mixer_card_get_profile (card);
532
best_profile = gvc_mixer_ui_device_get_best_profile (device, profile, current_profile->profile);
534
best_profile = profile;
536
g_assert (best_profile);
538
g_debug ("Selected '%s', moving to profile '%s' on card '%s' on stream id %i",
539
profile ? profile : "(any)", best_profile,
540
gvc_mixer_card_get_name (card),
541
gvc_mixer_ui_device_get_stream_id (device));
543
g_debug ("default sink name = %s and default sink id %u",
544
control->priv->default_sink_name,
545
control->priv->default_sink_id);
547
control->priv->profile_swapping_device_id = gvc_mixer_ui_device_get_id (device);
549
if (gvc_mixer_card_change_profile (card, best_profile)) {
550
gvc_mixer_ui_device_set_user_preferred_profile (device, best_profile);
557
* gvc_mixer_control_change_output:
560
* This method is called from the UI when the user selects a previously unselected device.
561
* - Firstly it queries the stream from the device.
562
* - 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)
563
* In the scenario of a NULL stream on the device
564
* - It fetches the device's preferred profile or if NUll the profile with the highest priority on that device.
565
* - It then caches this device in control->priv->cached_desired_output_id so that when the update_sink triggered
566
* from when we attempt to change profile we will know exactly what device to highlight on that stream.
567
* - It attempts to swap the profile on the card from that device and returns.
568
* - Next, it handles network or bluetooth streams that only require their stream to be made the default.
569
* - Next it deals with port changes so if the stream's active port is not the same as the port on the device
570
* it will attempt to change the port on that stream to be same as the device. If this fails it will return.
571
* - Finally it will set this new stream to be the default stream and emit a signal for the UI confirming the active output device.
574
gvc_mixer_control_change_output (GvcMixerControl *control,
575
GvcMixerUIDevice* output)
577
GvcMixerStream *stream;
578
GvcMixerStream *default_stream;
579
const GvcMixerStreamPort *active_port;
580
const gchar *output_port;
582
g_debug ("control change output");
584
stream = gvc_mixer_control_get_stream_from_device (control, output);
585
if (stream == NULL) {
586
gvc_mixer_control_change_profile_on_selected_device (control,
591
/* Handle a network sink as a portless or cardless device */
592
if (!gvc_mixer_ui_device_has_ports (output)) {
593
g_debug ("Did we try to move to a software/bluetooth sink ?");
594
if (gvc_mixer_control_set_default_sink (control, stream)) {
595
/* sink change was successful, update the UI.*/
596
g_signal_emit (G_OBJECT (control),
597
signals[ACTIVE_OUTPUT_UPDATE],
599
gvc_mixer_ui_device_get_id (output));
602
g_warning ("Failed to set default sink with stream from output %s",
603
gvc_mixer_ui_device_get_description (output));
608
active_port = gvc_mixer_stream_get_port (stream);
609
output_port = gvc_mixer_ui_device_get_port (output);
610
/* First ensure the correct port is active on the sink */
611
if (g_strcmp0 (active_port->port, output_port) != 0) {
612
g_debug ("Port change, switch to = %s", output_port);
613
if (gvc_mixer_stream_change_port (stream, output_port) == FALSE) {
614
g_warning ("Could not change port !");
619
default_stream = gvc_mixer_control_get_default_sink (control);
621
/* Finally if we are not on the correct stream, swap over. */
622
if (stream != default_stream) {
623
GvcMixerUIDevice* output;
625
g_debug ("Attempting to swap over to stream %s ",
626
gvc_mixer_stream_get_description (stream));
627
if (gvc_mixer_control_set_default_sink (control, stream)) {
628
output = gvc_mixer_control_lookup_device_from_stream (control, stream);
629
g_signal_emit (G_OBJECT (control),
630
signals[ACTIVE_OUTPUT_UPDATE],
632
gvc_mixer_ui_device_get_id (output));
634
/* If the move failed for some reason reset the UI. */
635
output = gvc_mixer_control_lookup_device_from_stream (control, default_stream);
636
g_signal_emit (G_OBJECT (control),
637
signals[ACTIVE_OUTPUT_UPDATE],
639
gvc_mixer_ui_device_get_id (output));
646
* gvc_mixer_control_change_input:
649
* This method is called from the UI when the user selects a previously unselected device.
650
* - Firstly it queries the stream from the device.
651
* - 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)
652
* In the scenario of a NULL stream on the device
653
* - It fetches the device's preferred profile or if NUll the profile with the highest priority on that device.
654
* - It then caches this device in control->priv->cached_desired_input_id so that when the update_source triggered
655
* from when we attempt to change profile we will know exactly what device to highlight on that stream.
656
* - It attempts to swap the profile on the card from that device and returns.
657
* - Next, it handles network or bluetooth streams that only require their stream to be made the default.
658
* - Next it deals with port changes so if the stream's active port is not the same as the port on the device
659
* it will attempt to change the port on that stream to be same as the device. If this fails it will return.
660
* - Finally it will set this new stream to be the default stream and emit a signal for the UI confirming the active input device.
663
gvc_mixer_control_change_input (GvcMixerControl *control,
664
GvcMixerUIDevice* input)
666
GvcMixerStream *stream;
667
GvcMixerStream *default_stream;
668
const GvcMixerStreamPort *active_port;
669
const gchar *input_port;
671
stream = gvc_mixer_control_get_stream_from_device (control, input);
672
if (stream == NULL) {
673
gvc_mixer_control_change_profile_on_selected_device (control,
678
/* Handle a network sink as a portless/cardless device */
679
if (!gvc_mixer_ui_device_has_ports (input)) {
680
g_debug ("Did we try to move to a software/bluetooth source ?");
681
if (! gvc_mixer_control_set_default_source (control, stream)) {
682
g_warning ("Failed to set default source with stream from input %s",
683
gvc_mixer_ui_device_get_description (input));
688
active_port = gvc_mixer_stream_get_port (stream);
689
input_port = gvc_mixer_ui_device_get_port (input);
690
/* First ensure the correct port is active on the sink */
691
if (g_strcmp0 (active_port->port, input_port) != 0) {
692
g_debug ("Port change, switch to = %s", input_port);
693
if (gvc_mixer_stream_change_port (stream, input_port) == FALSE) {
694
g_warning ("Could not change port!");
699
default_stream = gvc_mixer_control_get_default_source (control);
701
/* Finally if we are not on the correct stream, swap over. */
702
if (stream != default_stream) {
703
g_debug ("change-input - attempting to swap over to stream %s",
704
gvc_mixer_stream_get_description (stream));
705
gvc_mixer_control_set_default_source (control, stream);
711
listify_hash_values_hfunc (gpointer key,
715
GSList **list = user_data;
717
*list = g_slist_prepend (*list, value);
721
gvc_name_collate (const char *namea,
724
if (nameb == NULL && namea == NULL)
731
return g_utf8_collate (namea, nameb);
735
gvc_card_collate (GvcMixerCard *a,
741
g_return_val_if_fail (a == NULL || GVC_IS_MIXER_CARD (a), 0);
742
g_return_val_if_fail (b == NULL || GVC_IS_MIXER_CARD (b), 0);
744
namea = gvc_mixer_card_get_name (a);
745
nameb = gvc_mixer_card_get_name (b);
747
return gvc_name_collate (namea, nameb);
751
* gvc_mixer_control_get_cards:
754
* Returns: (transfer container) (element-type Gvc.MixerCard):
757
gvc_mixer_control_get_cards (GvcMixerControl *control)
761
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
764
g_hash_table_foreach (control->priv->cards,
765
listify_hash_values_hfunc,
767
return g_slist_sort (retval, (GCompareFunc) gvc_card_collate);
771
gvc_stream_collate (GvcMixerStream *a,
777
g_return_val_if_fail (a == NULL || GVC_IS_MIXER_STREAM (a), 0);
778
g_return_val_if_fail (b == NULL || GVC_IS_MIXER_STREAM (b), 0);
780
namea = gvc_mixer_stream_get_name (a);
781
nameb = gvc_mixer_stream_get_name (b);
783
return gvc_name_collate (namea, nameb);
787
* gvc_mixer_control_get_streams:
790
* Returns: (transfer container) (element-type Gvc.MixerStream):
793
gvc_mixer_control_get_streams (GvcMixerControl *control)
797
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
800
g_hash_table_foreach (control->priv->all_streams,
801
listify_hash_values_hfunc,
803
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
807
* gvc_mixer_control_get_sinks:
810
* Returns: (transfer container) (element-type Gvc.MixerSink):
813
gvc_mixer_control_get_sinks (GvcMixerControl *control)
817
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
820
g_hash_table_foreach (control->priv->sinks,
821
listify_hash_values_hfunc,
823
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
827
* gvc_mixer_control_get_sources:
830
* Returns: (transfer container) (element-type Gvc.MixerSource):
833
gvc_mixer_control_get_sources (GvcMixerControl *control)
837
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
840
g_hash_table_foreach (control->priv->sources,
841
listify_hash_values_hfunc,
843
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
847
* gvc_mixer_control_get_sink_inputs:
850
* Returns: (transfer container) (element-type Gvc.MixerSinkInput):
853
gvc_mixer_control_get_sink_inputs (GvcMixerControl *control)
857
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
860
g_hash_table_foreach (control->priv->sink_inputs,
861
listify_hash_values_hfunc,
863
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
867
* gvc_mixer_control_get_source_outputs:
870
* Returns: (transfer container) (element-type Gvc.MixerSourceOutput):
873
gvc_mixer_control_get_source_outputs (GvcMixerControl *control)
877
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
880
g_hash_table_foreach (control->priv->source_outputs,
881
listify_hash_values_hfunc,
883
return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
887
dec_outstanding (GvcMixerControl *control)
889
if (control->priv->n_outstanding <= 0) {
893
if (--control->priv->n_outstanding <= 0) {
894
control->priv->state = GVC_STATE_READY;
895
g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_READY);
900
gvc_mixer_control_get_state (GvcMixerControl *control)
902
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
904
return control->priv->state;
908
on_default_source_port_notify (GObject *object,
910
GvcMixerControl *control)
913
GvcMixerUIDevice *input;
915
g_object_get (object, "port", &port, NULL);
916
input = gvc_mixer_control_lookup_device_from_stream (control,
917
GVC_MIXER_STREAM (object));
919
g_debug ("on_default_source_port_notify - moved to port '%s' which SHOULD ?? correspond to output '%s'",
921
gvc_mixer_ui_device_get_description (input));
923
g_signal_emit (G_OBJECT (control),
924
signals[ACTIVE_INPUT_UPDATE],
926
gvc_mixer_ui_device_get_id (input));
933
_set_default_source (GvcMixerControl *control,
934
GvcMixerStream *stream)
938
if (stream == NULL) {
939
control->priv->default_source_id = 0;
940
control->priv->default_source_is_set = FALSE;
941
g_signal_emit (control,
942
signals[DEFAULT_SOURCE_CHANGED],
948
new_id = gvc_mixer_stream_get_id (stream);
950
if (control->priv->default_source_id != new_id) {
951
GvcMixerUIDevice *input;
952
control->priv->default_source_id = new_id;
953
control->priv->default_source_is_set = TRUE;
954
g_signal_emit (control,
955
signals[DEFAULT_SOURCE_CHANGED],
959
if (control->priv->default_source_is_set) {
960
g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_source (control),
961
on_default_source_port_notify,
965
g_signal_connect (stream,
967
G_CALLBACK (on_default_source_port_notify),
970
input = gvc_mixer_control_lookup_device_from_stream (control, stream);
972
g_signal_emit (G_OBJECT (control),
973
signals[ACTIVE_INPUT_UPDATE],
975
gvc_mixer_ui_device_get_id (input));
980
on_default_sink_port_notify (GObject *object,
982
GvcMixerControl *control)
985
GvcMixerUIDevice *output;
987
g_object_get (object, "port", &port, NULL);
989
output = gvc_mixer_control_lookup_device_from_stream (control,
990
GVC_MIXER_STREAM (object));
991
if (output != NULL) {
992
g_debug ("on_default_sink_port_notify - moved to port %s - which SHOULD correspond to output %s",
994
gvc_mixer_ui_device_get_description (output));
995
g_signal_emit (G_OBJECT (control),
996
signals[ACTIVE_OUTPUT_UPDATE],
998
gvc_mixer_ui_device_get_id (output));
1004
_set_default_sink (GvcMixerControl *control,
1005
GvcMixerStream *stream)
1009
if (stream == NULL) {
1010
/* Don't tell front-ends about an unset default
1011
* sink if it's already unset */
1012
if (control->priv->default_sink_is_set == FALSE)
1014
control->priv->default_sink_id = 0;
1015
control->priv->default_sink_is_set = FALSE;
1016
g_signal_emit (control,
1017
signals[DEFAULT_SINK_CHANGED],
1023
new_id = gvc_mixer_stream_get_id (stream);
1025
if (control->priv->default_sink_id != new_id) {
1026
GvcMixerUIDevice *output;
1027
if (control->priv->default_sink_is_set) {
1028
g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_sink (control),
1029
on_default_sink_port_notify,
1033
control->priv->default_sink_id = new_id;
1035
control->priv->default_sink_is_set = TRUE;
1036
g_signal_emit (control,
1037
signals[DEFAULT_SINK_CHANGED],
1041
g_signal_connect (stream,
1043
G_CALLBACK (on_default_sink_port_notify),
1046
output = gvc_mixer_control_lookup_device_from_stream (control, stream);
1048
g_debug ("active_sink change");
1050
g_signal_emit (G_OBJECT (control),
1051
signals[ACTIVE_OUTPUT_UPDATE],
1053
gvc_mixer_ui_device_get_id (output));
1058
_stream_has_name (gpointer key,
1059
GvcMixerStream *stream,
1064
t_name = gvc_mixer_stream_get_name (stream);
1068
&& strcmp (t_name, name) == 0) {
1075
static GvcMixerStream *
1076
find_stream_for_name (GvcMixerControl *control,
1079
GvcMixerStream *stream;
1081
stream = g_hash_table_find (control->priv->all_streams,
1082
(GHRFunc)_stream_has_name,
1088
update_default_source_from_name (GvcMixerControl *control,
1091
gboolean changed = FALSE;
1093
if ((control->priv->default_source_name == NULL
1095
|| (control->priv->default_source_name != NULL
1097
|| (name != NULL && strcmp (control->priv->default_source_name, name) != 0)) {
1102
GvcMixerStream *stream;
1104
g_free (control->priv->default_source_name);
1105
control->priv->default_source_name = g_strdup (name);
1107
stream = find_stream_for_name (control, name);
1108
_set_default_source (control, stream);
1113
update_default_sink_from_name (GvcMixerControl *control,
1116
gboolean changed = FALSE;
1118
if ((control->priv->default_sink_name == NULL
1120
|| (control->priv->default_sink_name != NULL
1122
|| (name != NULL && strcmp (control->priv->default_sink_name, name) != 0)) {
1127
GvcMixerStream *stream;
1128
g_free (control->priv->default_sink_name);
1129
control->priv->default_sink_name = g_strdup (name);
1131
stream = find_stream_for_name (control, name);
1132
_set_default_sink (control, stream);
1137
update_server (GvcMixerControl *control,
1138
const pa_server_info *info)
1140
if (info->default_source_name != NULL) {
1141
update_default_source_from_name (control, info->default_source_name);
1143
if (info->default_sink_name != NULL) {
1144
g_debug ("update server");
1145
update_default_sink_from_name (control, info->default_sink_name);
1150
remove_stream (GvcMixerControl *control,
1151
GvcMixerStream *stream)
1155
g_object_ref (stream);
1157
id = gvc_mixer_stream_get_id (stream);
1159
if (id == control->priv->default_sink_id) {
1160
_set_default_sink (control, NULL);
1161
} else if (id == control->priv->default_source_id) {
1162
_set_default_source (control, NULL);
1165
g_hash_table_remove (control->priv->all_streams,
1166
GUINT_TO_POINTER (id));
1167
g_signal_emit (G_OBJECT (control),
1168
signals[STREAM_REMOVED],
1170
gvc_mixer_stream_get_id (stream));
1171
g_object_unref (stream);
1175
add_stream (GvcMixerControl *control,
1176
GvcMixerStream *stream)
1178
g_hash_table_insert (control->priv->all_streams,
1179
GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)),
1181
g_signal_emit (G_OBJECT (control),
1182
signals[STREAM_ADDED],
1184
gvc_mixer_stream_get_id (stream));
1187
/* This method will match individual stream ports against its corresponding device
1189
* - 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
1190
* and the port-name on the device is the same as the streamport-name.
1191
* This should always find a match and is used exclusively by sync_devices().
1194
match_stream_with_devices (GvcMixerControl *control,
1195
GvcMixerStreamPort *stream_port,
1196
GvcMixerStream *stream)
1199
guint stream_card_id;
1201
gboolean in_possession = FALSE;
1203
stream_id = gvc_mixer_stream_get_id (stream);
1204
stream_card_id = gvc_mixer_stream_get_card_index (stream);
1206
devices = g_hash_table_get_values (GVC_IS_MIXER_SOURCE (stream) ? control->priv->ui_inputs : control->priv->ui_outputs);
1208
for (d = devices; d != NULL; d = d->next) {
1209
GvcMixerUIDevice *device;
1210
gint device_stream_id;
1211
gchar *device_port_name;
1218
g_object_get (G_OBJECT (device),
1219
"stream-id", &device_stream_id,
1222
"description", &description,
1223
"port-name", &device_port_name,
1226
card_id = gvc_mixer_card_get_index (card);
1228
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",
1236
if (stream_card_id == card_id &&
1237
g_strcmp0 (device_port_name, stream_port->port) == 0) {
1238
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",
1241
gvc_mixer_ui_device_get_id (device),
1244
g_object_set (G_OBJECT (device),
1245
"stream-id", (gint)stream_id,
1247
in_possession = TRUE;
1250
g_free (device_port_name);
1252
g_free (description);
1254
if (in_possession == TRUE)
1258
g_list_free (devices);
1259
return in_possession;
1263
* This method attempts to match a sink or source with its relevant UI device.
1264
* GvcMixerStream can represent both a sink or source.
1265
* Using static card port introspection implies that we know beforehand what
1266
* outputs and inputs are available to the user.
1267
* But that does not mean that all of these inputs and outputs are available to be used.
1268
* For instance we might be able to see that there is a HDMI port available but if
1269
* we are on the default analog stereo output profile there is no valid sink for
1270
* that HDMI device. We first need to change profile and when update_sink() is called
1271
* only then can we match the new hdmi sink with its corresponding device.
1273
* Firstly it checks to see if the incoming stream has no ports.
1274
* - If a stream has no ports but has a valid card ID (bluetooth), it will attempt
1275
* to match the device with the stream using the card id.
1276
* - If a stream has no ports and no valid card id, it goes ahead and makes a new
1277
* device (software/network devices are only detectable at the sink/source level)
1278
* If the stream has ports it will match each port against the stream using match_stream_with_devices().
1280
* This method should always find a match.
1283
sync_devices (GvcMixerControl *control,
1284
GvcMixerStream* stream)
1286
/* Go through ports to see what outputs can be created. */
1287
const GList *stream_ports;
1288
const GList *n = NULL;
1289
gboolean is_output = !GVC_IS_MIXER_SOURCE (stream);
1290
gint stream_port_count = 0;
1292
stream_ports = gvc_mixer_stream_get_ports (stream);
1294
if (stream_ports == NULL) {
1295
GvcMixerUIDevice *device;
1296
/* Bluetooth, no ports but a valid card */
1297
if (gvc_mixer_stream_get_card_index (stream) != PA_INVALID_INDEX) {
1299
gboolean in_possession = FALSE;
1301
devices = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
1303
for (d = devices; d != NULL; d = d->next) {
1309
g_object_get (G_OBJECT (device),
1312
card_id = gvc_mixer_card_get_index (card);
1313
g_debug ("sync devices, device description - '%s', device card id - %i, stream description - %s, stream card id - %i",
1314
gvc_mixer_ui_device_get_description (device),
1316
gvc_mixer_stream_get_description (stream),
1317
gvc_mixer_stream_get_card_index (stream));
1318
if (card_id == gvc_mixer_stream_get_card_index (stream)) {
1319
in_possession = TRUE;
1323
g_list_free (devices);
1325
if (!in_possession) {
1326
g_warning ("Couldn't match the portless stream (with card) - '%s' is it an input ? -> %i, streams card id -> %i",
1327
gvc_mixer_stream_get_description (stream),
1328
GVC_IS_MIXER_SOURCE (stream),
1329
gvc_mixer_stream_get_card_index (stream));
1333
g_object_set (G_OBJECT (device),
1334
"stream-id", (gint)gvc_mixer_stream_get_id (stream),
1335
"description", gvc_mixer_stream_get_description (stream),
1336
"origin", "", /*Leave it empty for these special cases*/
1338
"port-available", TRUE,
1340
} else { /* Network sink/source has no ports and no card. */
1343
object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
1344
"stream-id", (gint)gvc_mixer_stream_get_id (stream),
1345
"description", gvc_mixer_stream_get_description (stream),
1346
"origin", "", /* Leave it empty for these special cases */
1348
"port-available", TRUE,
1350
device = GVC_MIXER_UI_DEVICE (object);
1352
g_hash_table_insert (is_output ? control->priv->ui_outputs : control->priv->ui_inputs,
1353
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)),
1354
g_object_ref (device));
1357
g_signal_emit (G_OBJECT (control),
1358
signals[is_output ? OUTPUT_ADDED : INPUT_ADDED],
1360
gvc_mixer_ui_device_get_id (device));
1365
/* Go ahead and make sure to match each port against a previously created device */
1366
for (n = stream_ports; n != NULL; n = n->next) {
1368
GvcMixerStreamPort *stream_port;
1369
stream_port = n->data;
1370
stream_port_count ++;
1372
if (match_stream_with_devices (control, stream_port, stream))
1375
g_warning ("Sync_devices: Failed to match stream id: %u, description: '%s', origin: '%s'",
1376
gvc_mixer_stream_get_id (stream),
1377
stream_port->human_port,
1378
gvc_mixer_stream_get_description (stream));
1383
set_icon_name_from_proplist (GvcMixerStream *stream,
1385
const char *default_icon_name)
1389
if ((t = pa_proplist_gets (l, PA_PROP_DEVICE_ICON_NAME))) {
1393
if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ICON_NAME))) {
1397
if ((t = pa_proplist_gets (l, PA_PROP_WINDOW_ICON_NAME))) {
1401
if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ICON_NAME))) {
1405
if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
1407
if (strcmp (t, "video") == 0 ||
1408
strcmp (t, "phone") == 0) {
1412
if (strcmp (t, "music") == 0) {
1417
if (strcmp (t, "game") == 0) {
1418
t = "applications-games";
1422
if (strcmp (t, "event") == 0) {
1423
t = "dialog-information";
1428
t = default_icon_name;
1431
gvc_mixer_stream_set_icon_name (stream, t);
1435
* Called when anything changes with a sink.
1438
update_sink (GvcMixerControl *control,
1439
const pa_sink_info *info)
1441
GvcMixerStream *stream;
1443
pa_volume_t max_volume;
1445
char map_buff[PA_CHANNEL_MAP_SNPRINT_MAX];
1447
pa_channel_map_snprint (map_buff, PA_CHANNEL_MAP_SNPRINT_MAX, &info->channel_map);
1449
g_debug ("Updating sink: index=%u name='%s' description='%s' map='%s'",
1458
stream = g_hash_table_lookup (control->priv->sinks,
1459
GUINT_TO_POINTER (info->index));
1461
if (stream == NULL) {
1465
map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
1466
stream = gvc_mixer_sink_new (control->priv->pa_context,
1470
for (i = 0; i < info->n_ports; i++) {
1471
GvcMixerStreamPort *port;
1473
port = g_slice_new0 (GvcMixerStreamPort);
1474
port->port = g_strdup (info->ports[i]->name);
1475
port->human_port = g_strdup (info->ports[i]->description);
1476
port->priority = info->ports[i]->priority;
1477
port->available = info->ports[i]->available != PA_PORT_AVAILABLE_NO;
1479
list = g_list_prepend (list, port);
1481
gvc_mixer_stream_set_ports (stream, list);
1483
g_object_unref (map);
1486
} else if (gvc_mixer_stream_is_running (stream)) {
1487
/* Ignore events if volume changes are outstanding */
1488
g_debug ("Ignoring event, volume changes are outstanding");
1492
max_volume = pa_cvolume_max (&info->volume);
1493
gvc_mixer_stream_set_name (stream, info->name);
1494
gvc_mixer_stream_set_card_index (stream, info->card);
1495
gvc_mixer_stream_set_description (stream, info->description);
1496
set_icon_name_from_proplist (stream, info->proplist, "audio-card");
1497
gvc_mixer_stream_set_form_factor (stream, pa_proplist_gets (info->proplist, PA_PROP_DEVICE_FORM_FACTOR));
1498
gvc_mixer_stream_set_sysfs_path (stream, pa_proplist_gets (info->proplist, "sysfs.path"));
1499
gvc_mixer_stream_set_volume (stream, (guint)max_volume);
1500
gvc_mixer_stream_set_is_muted (stream, info->mute);
1501
gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME));
1502
gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
1504
/* Messy I know but to set the port everytime regardless of whether it has changed will cost us a
1505
* port change notify signal which causes the frontend to resync.
1506
* Only update the UI when something has changed. */
1507
if (info->active_port != NULL) {
1509
gvc_mixer_stream_set_port (stream, info->active_port->name);
1511
const GvcMixerStreamPort *active_port;
1512
active_port = gvc_mixer_stream_get_port (stream);
1513
if (active_port == NULL ||
1514
g_strcmp0 (active_port->port, info->active_port->name) != 0) {
1515
g_debug ("update sink - apparently a port update");
1516
gvc_mixer_stream_set_port (stream, info->active_port->name);
1522
g_debug ("update sink - is new");
1524
g_hash_table_insert (control->priv->sinks,
1525
GUINT_TO_POINTER (info->index),
1526
g_object_ref (stream));
1527
add_stream (control, stream);
1528
/* Always sink on a new stream to able to assign the right stream id
1529
* to the appropriate outputs (multiple potential outputs per stream). */
1530
sync_devices (control, stream);
1534
* When we change profile on a device that is not the server default sink,
1535
* it will jump back to the default sink set by the server to prevent the audio setup from being 'outputless'.
1536
* All well and good but then when we get the new stream created for the new profile how do we know
1537
* that this is the intended default or selected device the user wishes to use.
1538
* This is messy but it's the only reliable way that it can be done without ripping the whole thing apart.
1540
if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
1541
GvcMixerUIDevice *dev = NULL;
1542
dev = gvc_mixer_control_lookup_output_id (control, control->priv->profile_swapping_device_id);
1544
/* now check to make sure this new stream is the same stream just matched and set on the device object */
1545
if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
1546
g_debug ("Looks like we profile swapped on a non server default sink");
1547
gvc_mixer_control_set_default_sink (control, stream);
1550
control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
1553
if (control->priv->default_sink_name != NULL
1554
&& info->name != NULL
1555
&& strcmp (control->priv->default_sink_name, info->name) == 0) {
1556
_set_default_sink (control, stream);
1560
map = (GvcChannelMap *) gvc_mixer_stream_get_channel_map (stream);
1562
gvc_channel_map_volume_changed (map, &info->volume, FALSE);
1566
update_source (GvcMixerControl *control,
1567
const pa_source_info *info)
1569
GvcMixerStream *stream;
1571
pa_volume_t max_volume;
1574
g_debug ("Updating source: index=%u name='%s' description='%s'",
1580
/* completely ignore monitors, they're not real sources */
1581
if (info->monitor_of_sink != PA_INVALID_INDEX) {
1587
stream = g_hash_table_lookup (control->priv->sources,
1588
GUINT_TO_POINTER (info->index));
1589
if (stream == NULL) {
1594
map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
1595
stream = gvc_mixer_source_new (control->priv->pa_context,
1599
for (i = 0; i < info->n_ports; i++) {
1600
GvcMixerStreamPort *port;
1602
port = g_slice_new0 (GvcMixerStreamPort);
1603
port->port = g_strdup (info->ports[i]->name);
1604
port->human_port = g_strdup (info->ports[i]->description);
1605
port->priority = info->ports[i]->priority;
1606
list = g_list_prepend (list, port);
1608
gvc_mixer_stream_set_ports (stream, list);
1610
g_object_unref (map);
1612
} else if (gvc_mixer_stream_is_running (stream)) {
1613
/* Ignore events if volume changes are outstanding */
1614
g_debug ("Ignoring event, volume changes are outstanding");
1618
max_volume = pa_cvolume_max (&info->volume);
1620
gvc_mixer_stream_set_name (stream, info->name);
1621
gvc_mixer_stream_set_card_index (stream, info->card);
1622
gvc_mixer_stream_set_description (stream, info->description);
1623
set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
1624
gvc_mixer_stream_set_form_factor (stream, pa_proplist_gets (info->proplist, PA_PROP_DEVICE_FORM_FACTOR));
1625
gvc_mixer_stream_set_volume (stream, (guint)max_volume);
1626
gvc_mixer_stream_set_is_muted (stream, info->mute);
1627
gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME));
1628
gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
1629
g_debug ("update source");
1631
if (info->active_port != NULL) {
1633
gvc_mixer_stream_set_port (stream, info->active_port->name);
1635
const GvcMixerStreamPort *active_port;
1636
active_port = gvc_mixer_stream_get_port (stream);
1637
if (active_port == NULL ||
1638
g_strcmp0 (active_port->port, info->active_port->name) != 0) {
1639
g_debug ("update source - apparently a port update");
1640
gvc_mixer_stream_set_port (stream, info->active_port->name);
1646
g_hash_table_insert (control->priv->sources,
1647
GUINT_TO_POINTER (info->index),
1648
g_object_ref (stream));
1649
add_stream (control, stream);
1650
sync_devices (control, stream);
1653
if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
1654
GvcMixerUIDevice *dev = NULL;
1656
dev = gvc_mixer_control_lookup_input_id (control, control->priv->profile_swapping_device_id);
1659
/* now check to make sure this new stream is the same stream just matched and set on the device object */
1660
if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
1661
g_debug ("Looks like we profile swapped on a non server default sink");
1662
gvc_mixer_control_set_default_source (control, stream);
1665
control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
1667
if (control->priv->default_source_name != NULL
1668
&& info->name != NULL
1669
&& strcmp (control->priv->default_source_name, info->name) == 0) {
1670
_set_default_source (control, stream);
1675
set_is_event_stream_from_proplist (GvcMixerStream *stream,
1679
gboolean is_event_stream;
1681
is_event_stream = FALSE;
1683
if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
1684
if (g_str_equal (t, "event"))
1685
is_event_stream = TRUE;
1688
gvc_mixer_stream_set_is_event_stream (stream, is_event_stream);
1692
set_application_id_from_proplist (GvcMixerStream *stream,
1697
if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ID))) {
1698
gvc_mixer_stream_set_application_id (stream, t);
1703
update_sink_input (GvcMixerControl *control,
1704
const pa_sink_input_info *info)
1706
GvcMixerStream *stream;
1708
pa_volume_t max_volume;
1712
g_debug ("Updating sink input: index=%u name='%s' client=%u sink=%u",
1721
stream = g_hash_table_lookup (control->priv->sink_inputs,
1722
GUINT_TO_POINTER (info->index));
1723
if (stream == NULL) {
1725
map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
1726
stream = gvc_mixer_sink_input_new (control->priv->pa_context,
1729
g_object_unref (map);
1731
} else if (gvc_mixer_stream_is_running (stream)) {
1732
/* Ignore events if volume changes are outstanding */
1733
g_debug ("Ignoring event, volume changes are outstanding");
1737
max_volume = pa_cvolume_max (&info->volume);
1739
name = (const char *)g_hash_table_lookup (control->priv->clients,
1740
GUINT_TO_POINTER (info->client));
1741
gvc_mixer_stream_set_name (stream, name);
1742
gvc_mixer_stream_set_description (stream, info->name);
1744
set_application_id_from_proplist (stream, info->proplist);
1745
set_is_event_stream_from_proplist (stream, info->proplist);
1746
set_icon_name_from_proplist (stream, info->proplist, "applications-multimedia");
1747
gvc_mixer_stream_set_volume (stream, (guint)max_volume);
1748
gvc_mixer_stream_set_is_muted (stream, info->mute);
1749
gvc_mixer_stream_set_is_virtual (stream, info->client == PA_INVALID_INDEX);
1752
g_hash_table_insert (control->priv->sink_inputs,
1753
GUINT_TO_POINTER (info->index),
1754
g_object_ref (stream));
1755
add_stream (control, stream);
1760
update_source_output (GvcMixerControl *control,
1761
const pa_source_output_info *info)
1763
GvcMixerStream *stream;
1768
g_debug ("Updating source output: index=%u name='%s' client=%u source=%u",
1776
stream = g_hash_table_lookup (control->priv->source_outputs,
1777
GUINT_TO_POINTER (info->index));
1778
if (stream == NULL) {
1780
map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
1781
stream = gvc_mixer_source_output_new (control->priv->pa_context,
1784
g_object_unref (map);
1788
name = (const char *)g_hash_table_lookup (control->priv->clients,
1789
GUINT_TO_POINTER (info->client));
1791
gvc_mixer_stream_set_name (stream, name);
1792
gvc_mixer_stream_set_description (stream, info->name);
1793
set_application_id_from_proplist (stream, info->proplist);
1794
set_is_event_stream_from_proplist (stream, info->proplist);
1795
set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
1798
g_hash_table_insert (control->priv->source_outputs,
1799
GUINT_TO_POINTER (info->index),
1800
g_object_ref (stream));
1801
add_stream (control, stream);
1806
update_client (GvcMixerControl *control,
1807
const pa_client_info *info)
1810
g_debug ("Updating client: index=%u name='%s'",
1814
g_hash_table_insert (control->priv->clients,
1815
GUINT_TO_POINTER (info->index),
1816
g_strdup (info->name));
1820
card_num_streams_to_status (guint sinks,
1827
if (sinks == 0 && sources == 0) {
1829
* The device has been disabled */
1830
return g_strdup (_("Disabled"));
1836
* The number of sound outputs on a particular device */
1837
sinks_str = g_strdup_printf (ngettext ("%u Output",
1846
* The number of sound inputs on a particular device */
1847
sources_str = g_strdup_printf (ngettext ("%u Input",
1852
if (sources_str == NULL)
1854
if (sinks_str == NULL)
1856
ret = g_strdup_printf ("%s / %s", sinks_str, sources_str);
1858
g_free (sources_str);
1863
* A utility method to gather which card profiles are relevant to the port .
1866
determine_profiles_for_port (pa_card_port_info *port,
1867
GList* card_profiles)
1870
GList *supported_profiles = NULL;
1872
for (i = 0; i < port->n_profiles; i++) {
1873
for (p = card_profiles; p != NULL; p = p->next) {
1874
GvcMixerCardProfile *prof;
1876
if (g_strcmp0 (port->profiles[i]->name, prof->profile) == 0)
1877
supported_profiles = g_list_append (supported_profiles, prof);
1880
g_debug ("%i profiles supported on port %s",
1881
g_list_length (supported_profiles),
1883
return g_list_sort (supported_profiles, (GCompareFunc) gvc_mixer_card_profile_compare);
1887
is_card_port_an_output (GvcMixerCardPort* port)
1889
return port->direction == PA_DIRECTION_OUTPUT ? TRUE : FALSE;
1893
* This method will create a ui device for the given port.
1896
create_ui_device_from_port (GvcMixerControl* control,
1897
GvcMixerCardPort* port,
1900
GvcMixerUIDeviceDirection direction;
1902
GvcMixerUIDevice *uidevice;
1903
gboolean available = port->available != PA_PORT_AVAILABLE_NO;
1905
direction = (is_card_port_an_output (port) == TRUE) ? UIDeviceOutput : UIDeviceInput;
1907
object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
1908
"type", (guint)direction,
1910
"port-name", port->port,
1911
"description", port->human_port,
1912
"origin", gvc_mixer_card_get_name (card),
1913
"port-available", available,
1914
"icon-name", port->icon_name,
1917
uidevice = GVC_MIXER_UI_DEVICE (object);
1918
gvc_mixer_ui_device_set_profiles (uidevice, port->profiles);
1920
g_hash_table_insert (is_card_port_an_output (port) ? control->priv->ui_outputs : control->priv->ui_inputs,
1921
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (uidevice)),
1922
g_object_ref (uidevice));
1926
g_signal_emit (G_OBJECT (control),
1927
signals[is_card_port_an_output (port) ? OUTPUT_ADDED : INPUT_ADDED],
1929
gvc_mixer_ui_device_get_id (uidevice));
1932
g_debug ("create_ui_device_from_port, direction %u, description '%s', origin '%s', port available %i",
1935
gvc_mixer_card_get_name (card),
1940
* This method will match up GvcMixerCardPorts with existing devices.
1941
* A match is achieved if the device's card-id and the port's card-id are the same
1942
* && the device's port-name and the card-port's port member are the same.
1943
* A signal is then sent adding or removing that device from the UI depending on the availability of the port.
1946
match_card_port_with_existing_device (GvcMixerControl *control,
1947
GvcMixerCardPort *card_port,
1953
GvcMixerUIDevice *device;
1954
gboolean is_output = is_card_port_an_output (card_port);
1956
devices = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
1958
for (d = devices; d != NULL; d = d->next) {
1959
GvcMixerCard *device_card;
1960
gchar *device_port_name;
1963
g_object_get (G_OBJECT (device),
1964
"card", &device_card,
1965
"port-name", &device_port_name,
1968
if (g_strcmp0 (card_port->port, device_port_name) == 0 &&
1969
device_card == card) {
1970
g_debug ("Found the relevant device %s, update its port availability flag to %i, is_output %i",
1974
g_object_set (G_OBJECT (device),
1975
"port-available", available, NULL);
1976
g_signal_emit (G_OBJECT (control),
1977
is_output ? signals[available ? OUTPUT_ADDED : OUTPUT_REMOVED] : signals[available ? INPUT_ADDED : INPUT_REMOVED],
1979
gvc_mixer_ui_device_get_id (device));
1981
g_free (device_port_name);
1984
g_list_free (devices);
1988
create_ui_device_from_card (GvcMixerControl *control,
1992
GvcMixerUIDevice *in;
1993
GvcMixerUIDevice *out;
1994
const GList *profiles;
1996
/* For now just create two devices and presume this device is multi directional
1997
* Ensure to remove both on card removal (available to false by default) */
1998
profiles = gvc_mixer_card_get_profiles (card);
2000
g_debug ("Portless card just registered - %i", gvc_mixer_card_get_index (card));
2002
object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
2003
"type", UIDeviceInput,
2004
"description", gvc_mixer_card_get_name (card),
2005
"origin", "", /* Leave it empty for these special cases */
2007
"port-available", FALSE,
2010
in = GVC_MIXER_UI_DEVICE (object);
2011
gvc_mixer_ui_device_set_profiles (in, profiles);
2013
g_hash_table_insert (control->priv->ui_inputs,
2014
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (in)),
2016
object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
2017
"type", UIDeviceOutput,
2018
"description", gvc_mixer_card_get_name (card),
2019
"origin", "", /* Leave it empty for these special cases */
2021
"port-available", FALSE,
2024
out = GVC_MIXER_UI_DEVICE (object);
2025
gvc_mixer_ui_device_set_profiles (out, profiles);
2027
g_hash_table_insert (control->priv->ui_outputs,
2028
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (out)),
2029
g_object_ref (out));
2033
* At this point we can determine all devices available to us (besides network 'ports')
2034
* This is done by the following:
2036
* - gvc_mixer_card and gvc_mixer_card_ports are created and relevant setters are called.
2037
* - First it checks to see if it's a portless card. Bluetooth devices are portless AFAIHS.
2038
* If so it creates two devices, an input and an output.
2039
* - If it's a 'normal' card with ports it will create a new ui-device or
2040
* synchronise port availability with the existing device cached for that port on this card. */
2043
update_card (GvcMixerControl *control,
2044
const pa_card_info *info)
2046
const GList *card_ports = NULL;
2047
const GList *m = NULL;
2049
gboolean is_new = FALSE;
2055
g_debug ("Udpating card %s (index: %u driver: %s):",
2056
info->name, info->index, info->driver);
2058
for (i = 0; i < info->n_profiles; i++) {
2059
struct pa_card_profile_info pi = info->profiles[i];
2060
gboolean is_default;
2062
is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0);
2063
g_debug ("\tProfile '%s': %d sources %d sinks%s",
2064
pi.name, pi.n_sources, pi.n_sinks,
2065
is_default ? " (Current)" : "");
2068
key = pa_proplist_iterate (info->proplist, &state);
2069
while (key != NULL) {
2070
g_debug ("\tProperty: '%s' = '%s'",
2071
key, pa_proplist_gets (info->proplist, key));
2072
key = pa_proplist_iterate (info->proplist, &state);
2075
card = g_hash_table_lookup (control->priv->cards,
2076
GUINT_TO_POINTER (info->index));
2078
GList *profile_list = NULL;
2079
GList *port_list = NULL;
2081
for (i = 0; i < info->n_profiles; i++) {
2082
GvcMixerCardProfile *profile;
2083
struct pa_card_profile_info pi = info->profiles[i];
2085
profile = g_new0 (GvcMixerCardProfile, 1);
2086
profile->profile = g_strdup (pi.name);
2087
profile->human_profile = g_strdup (pi.description);
2088
profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources);
2089
profile->n_sinks = pi.n_sinks;
2090
profile->n_sources = pi.n_sources;
2091
profile->priority = pi.priority;
2092
profile_list = g_list_prepend (profile_list, profile);
2094
card = gvc_mixer_card_new (control->priv->pa_context,
2096
gvc_mixer_card_set_profiles (card, profile_list);
2098
for (i = 0; i < info->n_ports; i++) {
2099
GvcMixerCardPort *port;
2100
port = g_new0 (GvcMixerCardPort, 1);
2101
port->port = g_strdup (info->ports[i]->name);
2102
port->human_port = g_strdup (info->ports[i]->description);
2103
port->priority = info->ports[i]->priority;
2104
port->available = info->ports[i]->available;
2105
port->direction = info->ports[i]->direction;
2106
port->icon_name = g_strdup (pa_proplist_gets (info->ports[i]->proplist, "device.icon_name"));
2107
port->profiles = determine_profiles_for_port (info->ports[i], profile_list);
2108
port_list = g_list_prepend (port_list, port);
2110
gvc_mixer_card_set_ports (card, port_list);
2114
gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description"));
2115
gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name"));
2116
gvc_mixer_card_set_profile (card, info->active_profile->name);
2119
g_hash_table_insert (control->priv->cards,
2120
GUINT_TO_POINTER (info->index),
2121
g_object_ref (card));
2124
card_ports = gvc_mixer_card_get_ports (card);
2126
if (card_ports == NULL && is_new) {
2127
g_debug ("Portless card just registered - %s", gvc_mixer_card_get_name (card));
2128
create_ui_device_from_card (control, card);
2131
for (m = card_ports; m != NULL; m = m->next) {
2132
GvcMixerCardPort *card_port;
2133
card_port = m->data;
2135
create_ui_device_from_port (control, card_port, card);
2137
for (i = 0; i < info->n_ports; i++) {
2138
if (g_strcmp0 (card_port->port, info->ports[i]->name) == 0) {
2139
if (card_port->available != info->ports[i]->available) {
2140
card_port->available = info->ports[i]->available;
2141
g_debug ("sync port availability on card %i, card port name '%s', new available value %i",
2142
gvc_mixer_card_get_index (card),
2144
card_port->available);
2145
match_card_port_with_existing_device (control,
2148
card_port->available != PA_PORT_AVAILABLE_NO);
2154
g_signal_emit (G_OBJECT (control),
2155
signals[CARD_ADDED],
2161
_pa_context_get_sink_info_cb (pa_context *context,
2162
const pa_sink_info *i,
2166
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2169
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
2173
g_warning ("Sink callback failure");
2178
dec_outstanding (control);
2182
update_sink (control, i);
2186
_pa_context_get_source_info_cb (pa_context *context,
2187
const pa_source_info *i,
2191
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2194
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
2198
g_warning ("Source callback failure");
2203
dec_outstanding (control);
2207
update_source (control, i);
2211
_pa_context_get_sink_input_info_cb (pa_context *context,
2212
const pa_sink_input_info *i,
2216
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2219
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
2223
g_warning ("Sink input callback failure");
2228
dec_outstanding (control);
2232
update_sink_input (control, i);
2236
_pa_context_get_source_output_info_cb (pa_context *context,
2237
const pa_source_output_info *i,
2241
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2244
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
2248
g_warning ("Source output callback failure");
2253
dec_outstanding (control);
2257
update_source_output (control, i);
2261
_pa_context_get_client_info_cb (pa_context *context,
2262
const pa_client_info *i,
2266
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2269
if (pa_context_errno (context) == PA_ERR_NOENTITY) {
2273
g_warning ("Client callback failure");
2278
dec_outstanding (control);
2282
update_client (control, i);
2286
_pa_context_get_card_info_by_index_cb (pa_context *context,
2287
const pa_card_info *i,
2291
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2294
if (pa_context_errno (context) == PA_ERR_NOENTITY)
2297
g_warning ("Card callback failure");
2302
dec_outstanding (control);
2306
update_card (control, i);
2310
_pa_context_get_server_info_cb (pa_context *context,
2311
const pa_server_info *i,
2314
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2317
g_warning ("Server info callback failure");
2320
g_debug ("get server info");
2321
update_server (control, i);
2322
dec_outstanding (control);
2326
remove_event_role_stream (GvcMixerControl *control)
2328
g_debug ("Removing event role");
2332
update_event_role_stream (GvcMixerControl *control,
2333
const pa_ext_stream_restore_info *info)
2335
GvcMixerStream *stream;
2337
pa_volume_t max_volume;
2339
if (strcmp (info->name, "sink-input-by-media-role:event") != 0) {
2344
g_debug ("Updating event role: name='%s' device='%s'",
2351
if (!control->priv->event_sink_input_is_set) {
2352
pa_channel_map pa_map;
2355
pa_map.channels = 1;
2356
pa_map.map[0] = PA_CHANNEL_POSITION_MONO;
2357
map = gvc_channel_map_new_from_pa_channel_map (&pa_map);
2359
stream = gvc_mixer_event_role_new (control->priv->pa_context,
2362
control->priv->event_sink_input_id = gvc_mixer_stream_get_id (stream);
2363
control->priv->event_sink_input_is_set = TRUE;
2367
stream = g_hash_table_lookup (control->priv->all_streams,
2368
GUINT_TO_POINTER (control->priv->event_sink_input_id));
2371
max_volume = pa_cvolume_max (&info->volume);
2373
gvc_mixer_stream_set_name (stream, _("System Sounds"));
2374
gvc_mixer_stream_set_icon_name (stream, "multimedia-volume-control");
2375
gvc_mixer_stream_set_volume (stream, (guint)max_volume);
2376
gvc_mixer_stream_set_is_muted (stream, info->mute);
2379
add_stream (control, stream);
2384
_pa_ext_stream_restore_read_cb (pa_context *context,
2385
const pa_ext_stream_restore_info *i,
2389
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2392
g_debug ("Failed to initialized stream_restore extension: %s",
2393
pa_strerror (pa_context_errno (context)));
2394
remove_event_role_stream (control);
2399
dec_outstanding (control);
2400
/* If we don't have an event stream to restore, then
2401
* set one up with a default 100% volume */
2402
if (!control->priv->event_sink_input_is_set) {
2403
pa_ext_stream_restore_info info;
2405
memset (&info, 0, sizeof(info));
2406
info.name = "sink-input-by-media-role:event";
2407
info.volume.channels = 1;
2408
info.volume.values[0] = PA_VOLUME_NORM;
2409
update_event_role_stream (control, &info);
2414
update_event_role_stream (control, i);
2418
_pa_ext_stream_restore_subscribe_cb (pa_context *context,
2421
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2424
o = pa_ext_stream_restore_read (context,
2425
_pa_ext_stream_restore_read_cb,
2428
g_warning ("pa_ext_stream_restore_read() failed");
2432
pa_operation_unref (o);
2436
req_update_server_info (GvcMixerControl *control,
2441
o = pa_context_get_server_info (control->priv->pa_context,
2442
_pa_context_get_server_info_cb,
2445
g_warning ("pa_context_get_server_info() failed");
2448
pa_operation_unref (o);
2452
req_update_client_info (GvcMixerControl *control,
2458
o = pa_context_get_client_info_list (control->priv->pa_context,
2459
_pa_context_get_client_info_cb,
2462
o = pa_context_get_client_info (control->priv->pa_context,
2464
_pa_context_get_client_info_cb,
2469
g_warning ("pa_context_client_info_list() failed");
2472
pa_operation_unref (o);
2476
req_update_card (GvcMixerControl *control,
2482
o = pa_context_get_card_info_list (control->priv->pa_context,
2483
_pa_context_get_card_info_by_index_cb,
2486
o = pa_context_get_card_info_by_index (control->priv->pa_context,
2488
_pa_context_get_card_info_by_index_cb,
2493
g_warning ("pa_context_get_card_info_by_index() failed");
2496
pa_operation_unref (o);
2500
req_update_sink_info (GvcMixerControl *control,
2506
o = pa_context_get_sink_info_list (control->priv->pa_context,
2507
_pa_context_get_sink_info_cb,
2510
o = pa_context_get_sink_info_by_index (control->priv->pa_context,
2512
_pa_context_get_sink_info_cb,
2517
g_warning ("pa_context_get_sink_info_list() failed");
2520
pa_operation_unref (o);
2524
req_update_source_info (GvcMixerControl *control,
2530
o = pa_context_get_source_info_list (control->priv->pa_context,
2531
_pa_context_get_source_info_cb,
2534
o = pa_context_get_source_info_by_index(control->priv->pa_context,
2536
_pa_context_get_source_info_cb,
2541
g_warning ("pa_context_get_source_info_list() failed");
2544
pa_operation_unref (o);
2548
req_update_sink_input_info (GvcMixerControl *control,
2554
o = pa_context_get_sink_input_info_list (control->priv->pa_context,
2555
_pa_context_get_sink_input_info_cb,
2558
o = pa_context_get_sink_input_info (control->priv->pa_context,
2560
_pa_context_get_sink_input_info_cb,
2565
g_warning ("pa_context_get_sink_input_info_list() failed");
2568
pa_operation_unref (o);
2572
req_update_source_output_info (GvcMixerControl *control,
2578
o = pa_context_get_source_output_info_list (control->priv->pa_context,
2579
_pa_context_get_source_output_info_cb,
2582
o = pa_context_get_source_output_info (control->priv->pa_context,
2584
_pa_context_get_source_output_info_cb,
2589
g_warning ("pa_context_get_source_output_info_list() failed");
2592
pa_operation_unref (o);
2596
remove_client (GvcMixerControl *control,
2599
g_hash_table_remove (control->priv->clients,
2600
GUINT_TO_POINTER (index));
2604
remove_card (GvcMixerControl *control,
2610
devices = g_list_concat (g_hash_table_get_values (control->priv->ui_inputs),
2611
g_hash_table_get_values (control->priv->ui_outputs));
2613
for (d = devices; d != NULL; d = d->next) {
2615
GvcMixerUIDevice *device = d->data;
2617
g_object_get (G_OBJECT (device), "card", &card, NULL);
2619
if (gvc_mixer_card_get_index (card) == index) {
2620
g_signal_emit (G_OBJECT (control),
2621
signals[gvc_mixer_ui_device_is_output (device) ? OUTPUT_REMOVED : INPUT_REMOVED],
2623
gvc_mixer_ui_device_get_id (device));
2624
g_debug ("Card removal remove device %s",
2625
gvc_mixer_ui_device_get_description (device));
2626
g_hash_table_remove (gvc_mixer_ui_device_is_output (device) ? control->priv->ui_outputs : control->priv->ui_inputs,
2627
GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)));
2631
g_list_free (devices);
2633
g_hash_table_remove (control->priv->cards,
2634
GUINT_TO_POINTER (index));
2636
g_signal_emit (G_OBJECT (control),
2637
signals[CARD_REMOVED],
2643
remove_sink (GvcMixerControl *control,
2646
GvcMixerStream *stream;
2647
GvcMixerUIDevice *device;
2649
g_debug ("Removing sink: index=%u", index);
2651
stream = g_hash_table_lookup (control->priv->sinks,
2652
GUINT_TO_POINTER (index));
2656
device = gvc_mixer_control_lookup_device_from_stream (control, stream);
2658
if (device != NULL) {
2659
gvc_mixer_ui_device_invalidate_stream (device);
2660
if (!gvc_mixer_ui_device_has_ports (device)) {
2661
g_signal_emit (G_OBJECT (control),
2662
signals[OUTPUT_REMOVED],
2664
gvc_mixer_ui_device_get_id (device));
2668
devices = g_hash_table_get_values (control->priv->ui_outputs);
2670
for (d = devices; d != NULL; d = d->next) {
2671
gint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
2673
g_object_get (G_OBJECT (device),
2674
"stream-id", &stream_id,
2676
if (stream_id == gvc_mixer_stream_get_id (stream))
2677
gvc_mixer_ui_device_invalidate_stream (device);
2680
g_list_free (devices);
2684
g_hash_table_remove (control->priv->sinks,
2685
GUINT_TO_POINTER (index));
2687
remove_stream (control, stream);
2691
remove_source (GvcMixerControl *control,
2694
GvcMixerStream *stream;
2695
GvcMixerUIDevice *device;
2697
g_debug ("Removing source: index=%u", index);
2699
stream = g_hash_table_lookup (control->priv->sources,
2700
GUINT_TO_POINTER (index));
2704
device = gvc_mixer_control_lookup_device_from_stream (control, stream);
2706
if (device != NULL) {
2707
gvc_mixer_ui_device_invalidate_stream (device);
2708
if (!gvc_mixer_ui_device_has_ports (device)) {
2709
g_signal_emit (G_OBJECT (control),
2710
signals[INPUT_REMOVED],
2712
gvc_mixer_ui_device_get_id (device));
2716
devices = g_hash_table_get_values (control->priv->ui_inputs);
2718
for (d = devices; d != NULL; d = d->next) {
2719
gint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
2721
g_object_get (G_OBJECT (device),
2722
"stream-id", &stream_id,
2724
if (stream_id == gvc_mixer_stream_get_id (stream))
2725
gvc_mixer_ui_device_invalidate_stream (device);
2728
g_list_free (devices);
2732
g_hash_table_remove (control->priv->sources,
2733
GUINT_TO_POINTER (index));
2735
remove_stream (control, stream);
2739
remove_sink_input (GvcMixerControl *control,
2742
GvcMixerStream *stream;
2744
g_debug ("Removing sink input: index=%u", index);
2746
stream = g_hash_table_lookup (control->priv->sink_inputs,
2747
GUINT_TO_POINTER (index));
2748
if (stream == NULL) {
2751
g_hash_table_remove (control->priv->sink_inputs,
2752
GUINT_TO_POINTER (index));
2754
remove_stream (control, stream);
2758
remove_source_output (GvcMixerControl *control,
2761
GvcMixerStream *stream;
2763
g_debug ("Removing source output: index=%u", index);
2765
stream = g_hash_table_lookup (control->priv->source_outputs,
2766
GUINT_TO_POINTER (index));
2767
if (stream == NULL) {
2770
g_hash_table_remove (control->priv->source_outputs,
2771
GUINT_TO_POINTER (index));
2773
remove_stream (control, stream);
2777
_pa_context_subscribe_cb (pa_context *context,
2778
pa_subscription_event_type_t t,
2782
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2784
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
2785
case PA_SUBSCRIPTION_EVENT_SINK:
2786
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
2787
remove_sink (control, index);
2789
req_update_sink_info (control, index);
2793
case PA_SUBSCRIPTION_EVENT_SOURCE:
2794
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
2795
remove_source (control, index);
2797
req_update_source_info (control, index);
2801
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
2802
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
2803
remove_sink_input (control, index);
2805
req_update_sink_input_info (control, index);
2809
case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
2810
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
2811
remove_source_output (control, index);
2813
req_update_source_output_info (control, index);
2817
case PA_SUBSCRIPTION_EVENT_CLIENT:
2818
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
2819
remove_client (control, index);
2821
req_update_client_info (control, index);
2825
case PA_SUBSCRIPTION_EVENT_SERVER:
2826
req_update_server_info (control, index);
2829
case PA_SUBSCRIPTION_EVENT_CARD:
2830
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
2831
remove_card (control, index);
2833
req_update_card (control, index);
2840
gvc_mixer_control_ready (GvcMixerControl *control)
2844
pa_context_set_subscribe_callback (control->priv->pa_context,
2845
_pa_context_subscribe_cb,
2847
o = pa_context_subscribe (control->priv->pa_context,
2848
(pa_subscription_mask_t)
2849
(PA_SUBSCRIPTION_MASK_SINK|
2850
PA_SUBSCRIPTION_MASK_SOURCE|
2851
PA_SUBSCRIPTION_MASK_SINK_INPUT|
2852
PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
2853
PA_SUBSCRIPTION_MASK_CLIENT|
2854
PA_SUBSCRIPTION_MASK_SERVER|
2855
PA_SUBSCRIPTION_MASK_CARD),
2860
g_warning ("pa_context_subscribe() failed");
2863
pa_operation_unref (o);
2865
req_update_server_info (control, -1);
2866
req_update_card (control, -1);
2867
req_update_client_info (control, -1);
2868
req_update_sink_info (control, -1);
2869
req_update_source_info (control, -1);
2870
req_update_sink_input_info (control, -1);
2871
req_update_source_output_info (control, -1);
2874
control->priv->n_outstanding = 6;
2876
/* This call is not always supported */
2877
o = pa_ext_stream_restore_read (control->priv->pa_context,
2878
_pa_ext_stream_restore_read_cb,
2881
pa_operation_unref (o);
2882
control->priv->n_outstanding++;
2884
pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context,
2885
_pa_ext_stream_restore_subscribe_cb,
2888
o = pa_ext_stream_restore_subscribe (control->priv->pa_context,
2893
pa_operation_unref (o);
2897
g_debug ("Failed to initialized stream_restore extension: %s",
2898
pa_strerror (pa_context_errno (control->priv->pa_context)));
2903
gvc_mixer_new_pa_context (GvcMixerControl *self)
2905
pa_proplist *proplist;
2907
g_return_if_fail (self);
2908
g_return_if_fail (!self->priv->pa_context);
2910
proplist = pa_proplist_new ();
2911
pa_proplist_sets (proplist,
2912
PA_PROP_APPLICATION_NAME,
2914
pa_proplist_sets (proplist,
2915
PA_PROP_APPLICATION_ID,
2916
"org.gnome.VolumeControl");
2917
pa_proplist_sets (proplist,
2918
PA_PROP_APPLICATION_ICON_NAME,
2919
"multimedia-volume-control");
2920
pa_proplist_sets (proplist,
2921
PA_PROP_APPLICATION_VERSION,
2924
self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist);
2926
pa_proplist_free (proplist);
2927
g_assert (self->priv->pa_context);
2931
remove_all_streams (GvcMixerControl *control, GHashTable *hash_table)
2933
GHashTableIter iter;
2934
gpointer key, value;
2936
g_hash_table_iter_init (&iter, hash_table);
2937
while (g_hash_table_iter_next (&iter, &key, &value)) {
2938
remove_stream (control, value);
2939
g_hash_table_iter_remove (&iter);
2944
idle_reconnect (gpointer data)
2946
GvcMixerControl *control = GVC_MIXER_CONTROL (data);
2947
GHashTableIter iter;
2948
gpointer key, value;
2950
g_return_val_if_fail (control, FALSE);
2952
if (control->priv->pa_context) {
2953
pa_context_unref (control->priv->pa_context);
2954
control->priv->pa_context = NULL;
2955
gvc_mixer_new_pa_context (control);
2958
remove_all_streams (control, control->priv->sinks);
2959
remove_all_streams (control, control->priv->sources);
2960
remove_all_streams (control, control->priv->sink_inputs);
2961
remove_all_streams (control, control->priv->source_outputs);
2963
g_hash_table_iter_init (&iter, control->priv->clients);
2964
while (g_hash_table_iter_next (&iter, &key, &value))
2965
g_hash_table_iter_remove (&iter);
2967
gvc_mixer_control_open (control); /* cannot fail */
2969
control->priv->reconnect_id = 0;
2974
_pa_context_state_cb (pa_context *context,
2977
GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
2979
switch (pa_context_get_state (context)) {
2980
case PA_CONTEXT_UNCONNECTED:
2981
case PA_CONTEXT_CONNECTING:
2982
case PA_CONTEXT_AUTHORIZING:
2983
case PA_CONTEXT_SETTING_NAME:
2986
case PA_CONTEXT_READY:
2987
gvc_mixer_control_ready (control);
2990
case PA_CONTEXT_FAILED:
2991
control->priv->state = GVC_STATE_FAILED;
2992
g_signal_emit (control, signals[STATE_CHANGED], 0, GVC_STATE_FAILED);
2993
if (control->priv->reconnect_id == 0)
2994
control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control);
2997
case PA_CONTEXT_TERMINATED:
3005
gvc_mixer_control_open (GvcMixerControl *control)
3009
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
3010
g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
3011
g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE);
3013
pa_context_set_state_callback (control->priv->pa_context,
3014
_pa_context_state_cb,
3017
control->priv->state = GVC_STATE_CONNECTING;
3018
g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CONNECTING);
3019
res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, NULL);
3021
g_warning ("Failed to connect context: %s",
3022
pa_strerror (pa_context_errno (control->priv->pa_context)));
3029
gvc_mixer_control_close (GvcMixerControl *control)
3031
g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
3032
g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
3034
pa_context_disconnect (control->priv->pa_context);
3036
control->priv->state = GVC_STATE_CLOSED;
3037
g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CLOSED);
3042
gvc_mixer_control_dispose (GObject *object)
3044
GvcMixerControl *control = GVC_MIXER_CONTROL (object);
3046
if (control->priv->reconnect_id != 0) {
3047
g_source_remove (control->priv->reconnect_id);
3048
control->priv->reconnect_id = 0;
3051
if (control->priv->pa_context != NULL) {
3052
pa_context_unref (control->priv->pa_context);
3053
control->priv->pa_context = NULL;
3056
if (control->priv->default_source_name != NULL) {
3057
g_free (control->priv->default_source_name);
3058
control->priv->default_source_name = NULL;
3060
if (control->priv->default_sink_name != NULL) {
3061
g_free (control->priv->default_sink_name);
3062
control->priv->default_sink_name = NULL;
3065
if (control->priv->pa_mainloop != NULL) {
3066
pa_glib_mainloop_free (control->priv->pa_mainloop);
3067
control->priv->pa_mainloop = NULL;
3070
if (control->priv->all_streams != NULL) {
3071
g_hash_table_destroy (control->priv->all_streams);
3072
control->priv->all_streams = NULL;
3075
if (control->priv->sinks != NULL) {
3076
g_hash_table_destroy (control->priv->sinks);
3077
control->priv->sinks = NULL;
3079
if (control->priv->sources != NULL) {
3080
g_hash_table_destroy (control->priv->sources);
3081
control->priv->sources = NULL;
3083
if (control->priv->sink_inputs != NULL) {
3084
g_hash_table_destroy (control->priv->sink_inputs);
3085
control->priv->sink_inputs = NULL;
3087
if (control->priv->source_outputs != NULL) {
3088
g_hash_table_destroy (control->priv->source_outputs);
3089
control->priv->source_outputs = NULL;
3091
if (control->priv->clients != NULL) {
3092
g_hash_table_destroy (control->priv->clients);
3093
control->priv->clients = NULL;
3095
if (control->priv->cards != NULL) {
3096
g_hash_table_destroy (control->priv->cards);
3097
control->priv->cards = NULL;
3099
if (control->priv->ui_outputs != NULL) {
3100
g_hash_table_destroy (control->priv->ui_outputs);
3101
control->priv->ui_outputs = NULL;
3103
if (control->priv->ui_inputs != NULL) {
3104
g_hash_table_destroy (control->priv->ui_inputs);
3105
control->priv->ui_inputs = NULL;
3108
G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object);
3112
gvc_mixer_control_set_property (GObject *object,
3114
const GValue *value,
3117
GvcMixerControl *self = GVC_MIXER_CONTROL (object);
3121
g_free (self->priv->name);
3122
self->priv->name = g_value_dup_string (value);
3123
g_object_notify (G_OBJECT (self), "name");
3126
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3132
gvc_mixer_control_get_property (GObject *object,
3137
GvcMixerControl *self = GVC_MIXER_CONTROL (object);
3141
g_value_set_string (value, self->priv->name);
3144
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3151
gvc_mixer_control_constructor (GType type,
3152
guint n_construct_properties,
3153
GObjectConstructParam *construct_params)
3156
GvcMixerControl *self;
3158
object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params);
3160
self = GVC_MIXER_CONTROL (object);
3162
gvc_mixer_new_pa_context (self);
3163
self->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
3169
gvc_mixer_control_class_init (GvcMixerControlClass *klass)
3171
GObjectClass *object_class = G_OBJECT_CLASS (klass);
3173
object_class->constructor = gvc_mixer_control_constructor;
3174
object_class->dispose = gvc_mixer_control_dispose;
3175
object_class->finalize = gvc_mixer_control_finalize;
3176
object_class->set_property = gvc_mixer_control_set_property;
3177
object_class->get_property = gvc_mixer_control_get_property;
3179
g_object_class_install_property (object_class,
3181
g_param_spec_string ("name",
3183
"Name to display for this mixer control",
3185
G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
3187
signals [STATE_CHANGED] =
3188
g_signal_new ("state-changed",
3189
G_TYPE_FROM_CLASS (klass),
3191
G_STRUCT_OFFSET (GvcMixerControlClass, state_changed),
3193
g_cclosure_marshal_VOID__UINT,
3194
G_TYPE_NONE, 1, G_TYPE_UINT);
3195
signals [STREAM_ADDED] =
3196
g_signal_new ("stream-added",
3197
G_TYPE_FROM_CLASS (klass),
3199
G_STRUCT_OFFSET (GvcMixerControlClass, stream_added),
3201
g_cclosure_marshal_VOID__UINT,
3202
G_TYPE_NONE, 1, G_TYPE_UINT);
3203
signals [STREAM_REMOVED] =
3204
g_signal_new ("stream-removed",
3205
G_TYPE_FROM_CLASS (klass),
3207
G_STRUCT_OFFSET (GvcMixerControlClass, stream_removed),
3209
g_cclosure_marshal_VOID__UINT,
3210
G_TYPE_NONE, 1, G_TYPE_UINT);
3211
signals [CARD_ADDED] =
3212
g_signal_new ("card-added",
3213
G_TYPE_FROM_CLASS (klass),
3215
G_STRUCT_OFFSET (GvcMixerControlClass, card_added),
3217
g_cclosure_marshal_VOID__UINT,
3218
G_TYPE_NONE, 1, G_TYPE_UINT);
3219
signals [CARD_REMOVED] =
3220
g_signal_new ("card-removed",
3221
G_TYPE_FROM_CLASS (klass),
3223
G_STRUCT_OFFSET (GvcMixerControlClass, card_removed),
3225
g_cclosure_marshal_VOID__UINT,
3226
G_TYPE_NONE, 1, G_TYPE_UINT);
3227
signals [DEFAULT_SINK_CHANGED] =
3228
g_signal_new ("default-sink-changed",
3229
G_TYPE_FROM_CLASS (klass),
3231
G_STRUCT_OFFSET (GvcMixerControlClass, default_sink_changed),
3233
g_cclosure_marshal_VOID__UINT,
3234
G_TYPE_NONE, 1, G_TYPE_UINT);
3235
signals [DEFAULT_SOURCE_CHANGED] =
3236
g_signal_new ("default-source-changed",
3237
G_TYPE_FROM_CLASS (klass),
3239
G_STRUCT_OFFSET (GvcMixerControlClass, default_source_changed),
3241
g_cclosure_marshal_VOID__UINT,
3242
G_TYPE_NONE, 1, G_TYPE_UINT);
3243
signals [ACTIVE_OUTPUT_UPDATE] =
3244
g_signal_new ("active-output-update",
3245
G_TYPE_FROM_CLASS (klass),
3247
G_STRUCT_OFFSET (GvcMixerControlClass, active_output_update),
3249
g_cclosure_marshal_VOID__UINT,
3250
G_TYPE_NONE, 1, G_TYPE_UINT);
3251
signals [ACTIVE_INPUT_UPDATE] =
3252
g_signal_new ("active-input-update",
3253
G_TYPE_FROM_CLASS (klass),
3255
G_STRUCT_OFFSET (GvcMixerControlClass, active_input_update),
3257
g_cclosure_marshal_VOID__UINT,
3258
G_TYPE_NONE, 1, G_TYPE_UINT);
3259
signals [OUTPUT_ADDED] =
3260
g_signal_new ("output-added",
3261
G_TYPE_FROM_CLASS (klass),
3263
G_STRUCT_OFFSET (GvcMixerControlClass, output_added),
3265
g_cclosure_marshal_VOID__UINT,
3266
G_TYPE_NONE, 1, G_TYPE_UINT);
3267
signals [INPUT_ADDED] =
3268
g_signal_new ("input-added",
3269
G_TYPE_FROM_CLASS (klass),
3271
G_STRUCT_OFFSET (GvcMixerControlClass, input_added),
3273
g_cclosure_marshal_VOID__UINT,
3274
G_TYPE_NONE, 1, G_TYPE_UINT);
3275
signals [OUTPUT_REMOVED] =
3276
g_signal_new ("output-removed",
3277
G_TYPE_FROM_CLASS (klass),
3279
G_STRUCT_OFFSET (GvcMixerControlClass, output_removed),
3281
g_cclosure_marshal_VOID__UINT,
3282
G_TYPE_NONE, 1, G_TYPE_UINT);
3283
signals [INPUT_REMOVED] =
3284
g_signal_new ("input-removed",
3285
G_TYPE_FROM_CLASS (klass),
3287
G_STRUCT_OFFSET (GvcMixerControlClass, input_removed),
3289
g_cclosure_marshal_VOID__UINT,
3290
G_TYPE_NONE, 1, G_TYPE_UINT);
3291
g_type_class_add_private (klass, sizeof (GvcMixerControlPrivate));
3296
gvc_mixer_control_init (GvcMixerControl *control)
3298
control->priv = GVC_MIXER_CONTROL_GET_PRIVATE (control);
3300
control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ());
3301
g_assert (control->priv->pa_mainloop);
3303
control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop);
3304
g_assert (control->priv->pa_api);
3306
control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
3307
control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
3308
control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
3309
control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
3310
control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
3311
control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
3312
control->priv->ui_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
3313
control->priv->ui_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
3315
control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
3317
control->priv->state = GVC_STATE_CLOSED;
3321
gvc_mixer_control_finalize (GObject *object)
3323
GvcMixerControl *mixer_control;
3325
g_return_if_fail (object != NULL);
3326
g_return_if_fail (GVC_IS_MIXER_CONTROL (object));
3328
mixer_control = GVC_MIXER_CONTROL (object);
3329
g_free (mixer_control->priv->name);
3330
mixer_control->priv->name = NULL;
3332
g_return_if_fail (mixer_control->priv != NULL);
3333
G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object);
3337
gvc_mixer_control_new (const char *name)
3340
control = g_object_new (GVC_TYPE_MIXER_CONTROL,
3343
return GVC_MIXER_CONTROL (control);
3347
gvc_mixer_control_get_vol_max_norm (GvcMixerControl *control)
3349
return (gdouble) PA_VOLUME_NORM;
3353
gvc_mixer_control_get_vol_max_amplified (GvcMixerControl *control)
3355
return (gdouble) PA_VOLUME_UI_MAX;