~ubuntu-branches/ubuntu/trusty/unity-control-center/trusty

« back to all changes in this revision

Viewing changes to panels/sound/gvc-mixer-control.c

  • Committer: Package Import Robot
  • Author(s): Robert Ancell
  • Date: 2014-01-08 16:29:18 UTC
  • Revision ID: package-import@ubuntu.com-20140108162918-g29dd08tr913y2qh
Tags: upstream-14.04.0
ImportĀ upstreamĀ versionĀ 14.04.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 
2
 *
 
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
 
7
 *
 
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.
 
12
 *
 
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.
 
17
 *
 
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.
 
21
 *
 
22
 */
 
23
 
 
24
#include "config.h"
 
25
 
 
26
#include <stdlib.h>
 
27
#include <stdio.h>
 
28
#include <unistd.h>
 
29
 
 
30
#include <glib.h>
 
31
#include <glib/gi18n-lib.h>
 
32
 
 
33
#include <pulse/pulseaudio.h>
 
34
#include <pulse/glib-mainloop.h>
 
35
#include <pulse/ext-stream-restore.h>
 
36
 
 
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"
 
48
 
 
49
#define GVC_MIXER_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlPrivate))
 
50
 
 
51
#define RECONNECT_DELAY 5
 
52
 
 
53
enum {
 
54
        PROP_0,
 
55
        PROP_NAME
 
56
};
 
57
 
 
58
struct GvcMixerControlPrivate
 
59
{
 
60
        pa_glib_mainloop *pa_mainloop;
 
61
        pa_mainloop_api  *pa_api;
 
62
        pa_context       *pa_context;
 
63
        int               n_outstanding;
 
64
        guint             reconnect_id;
 
65
        char             *name;
 
66
 
 
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;
 
73
 
 
74
        gboolean          event_sink_input_is_set;
 
75
        guint             event_sink_input_id;
 
76
 
 
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 */
 
82
        GHashTable       *clients;
 
83
        GHashTable       *cards;
 
84
 
 
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 () */
 
87
 
 
88
        GHashTable       *ui_outputs; /* UI visible outputs */
 
89
        GHashTable       *ui_inputs;  /* UI visible inputs */
 
90
 
 
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'.
 
94
         *
 
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;
 
99
 
 
100
        GvcMixerControlState state;
 
101
};
 
102
 
 
103
enum {
 
104
        STATE_CHANGED,
 
105
        STREAM_ADDED,
 
106
        STREAM_REMOVED,
 
107
        CARD_ADDED,
 
108
        CARD_REMOVED,
 
109
        DEFAULT_SINK_CHANGED,
 
110
        DEFAULT_SOURCE_CHANGED,
 
111
        ACTIVE_OUTPUT_UPDATE,
 
112
        ACTIVE_INPUT_UPDATE,
 
113
        OUTPUT_ADDED,
 
114
        INPUT_ADDED,
 
115
        OUTPUT_REMOVED,
 
116
        INPUT_REMOVED,
 
117
        LAST_SIGNAL
 
118
};
 
119
 
 
120
static guint signals [LAST_SIGNAL] = { 0, };
 
121
 
 
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);
 
125
 
 
126
G_DEFINE_TYPE (GvcMixerControl, gvc_mixer_control, G_TYPE_OBJECT)
 
127
 
 
128
pa_context *
 
129
gvc_mixer_control_get_pa_context (GvcMixerControl *control)
 
130
{
 
131
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
132
        return control->priv->pa_context;
 
133
}
 
134
 
 
135
/**
 
136
 * gvc_mixer_control_get_event_sink_input:
 
137
 *
 
138
 * @control:
 
139
 *
 
140
 * Returns: (transfer none):
 
141
 */
 
142
GvcMixerStream *
 
143
gvc_mixer_control_get_event_sink_input (GvcMixerControl *control)
 
144
{
 
145
        GvcMixerStream *stream;
 
146
 
 
147
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
148
 
 
149
        stream = g_hash_table_lookup (control->priv->all_streams,
 
150
                                      GUINT_TO_POINTER (control->priv->event_sink_input_id));
 
151
 
 
152
        return stream;
 
153
}
 
154
 
 
155
static void
 
156
gvc_mixer_control_stream_restore_cb (pa_context *c,
 
157
                                     GvcMixerStream *new_stream,
 
158
                                     const pa_ext_stream_restore_info *info,
 
159
                                     GvcMixerControl *control)
 
160
{
 
161
        pa_operation *o;
 
162
        pa_ext_stream_restore_info new_info;
 
163
 
 
164
        if (new_stream == NULL)
 
165
                return;
 
166
 
 
167
        new_info.name = info->name;
 
168
        new_info.channel_map = info->channel_map;
 
169
        new_info.volume = info->volume;
 
170
        new_info.mute = info->mute;
 
171
 
 
172
        new_info.device = gvc_mixer_stream_get_name (new_stream);
 
173
 
 
174
        o = pa_ext_stream_restore_write (control->priv->pa_context,
 
175
                                         PA_UPDATE_REPLACE,
 
176
                                         &new_info, 1,
 
177
                                         TRUE, NULL, NULL);
 
178
 
 
179
        if (o == NULL) {
 
180
                g_warning ("pa_ext_stream_restore_write() failed: %s",
 
181
                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 
182
                return;
 
183
        }
 
184
 
 
185
        g_debug ("Changed default device for %s to %s", info->name, new_info.device);
 
186
 
 
187
        pa_operation_unref (o);
 
188
}
 
189
 
 
190
static void
 
191
gvc_mixer_control_stream_restore_sink_cb (pa_context *c,
 
192
                                          const pa_ext_stream_restore_info *info,
 
193
                                          int eol,
 
194
                                          void *userdata)
 
195
{
 
196
        GvcMixerControl *control = (GvcMixerControl *) userdata;
 
197
        if (eol || info == NULL || !g_str_has_prefix(info->name, "sink-input-by"))
 
198
                return;
 
199
        gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_sink_stream, info, control);
 
200
}
 
201
 
 
202
static void
 
203
gvc_mixer_control_stream_restore_source_cb (pa_context *c,
 
204
                                            const pa_ext_stream_restore_info *info,
 
205
                                            int eol,
 
206
                                            void *userdata)
 
207
{
 
208
        GvcMixerControl *control = (GvcMixerControl *) userdata;
 
209
        if (eol || info == NULL || !g_str_has_prefix(info->name, "source-output-by"))
 
210
                return;
 
211
        gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_source_stream, info, control);
 
212
}
 
213
 
 
214
/**
 
215
 * gvc_mixer_control_lookup_device_from_stream:
 
216
 * @control:
 
217
 * @stream:
 
218
 * Returns: (transfer none): a #GvcUIDevice or %NULL
 
219
 */
 
220
GvcMixerUIDevice *
 
221
gvc_mixer_control_lookup_device_from_stream (GvcMixerControl *control,
 
222
                                             GvcMixerStream *stream)
 
223
{
 
224
        GList                   *devices, *d;
 
225
        gboolean                 is_network_stream;
 
226
        const GList             *ports;
 
227
        GvcMixerUIDevice        *ret;
 
228
 
 
229
        if (GVC_IS_MIXER_SOURCE (stream))
 
230
               devices = g_hash_table_get_values (control->priv->ui_inputs);
 
231
        else
 
232
               devices = g_hash_table_get_values (control->priv->ui_outputs);
 
233
 
 
234
        ret = NULL;
 
235
        ports = gvc_mixer_stream_get_ports (stream);
 
236
        is_network_stream = (ports == NULL);
 
237
 
 
238
        for (d = devices; d != NULL; d = d->next) {
 
239
                GvcMixerUIDevice *device = d->data;
 
240
                gint stream_id = G_MAXINT;
 
241
 
 
242
                g_object_get (G_OBJECT (device),
 
243
                             "stream-id", &stream_id,
 
244
                              NULL);
 
245
 
 
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));
 
250
                        ret = device;
 
251
                        break;
 
252
                } else if (!is_network_stream) {
 
253
                        const GvcMixerStreamPort *port;
 
254
                        port = gvc_mixer_stream_get_port (stream);
 
255
 
 
256
                        if (stream_id == gvc_mixer_stream_get_id (stream) &&
 
257
                            g_strcmp0 (gvc_mixer_ui_device_get_port (device),
 
258
                                       port->port) == 0) {
 
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),
 
262
                                         stream_id,
 
263
                                         port->port,
 
264
                                         gvc_mixer_stream_get_id (stream),
 
265
                                         gvc_mixer_stream_get_description (stream));
 
266
                                ret = device;
 
267
                                break;
 
268
                        }
 
269
                }
 
270
        }
 
271
 
 
272
        g_debug ("gvc_mixer_control_lookup_device_from_stream - Could not find a device for stream '%s'",gvc_mixer_stream_get_description (stream));
 
273
 
 
274
        g_list_free (devices);
 
275
 
 
276
        return ret;
 
277
}
 
278
 
 
279
gboolean
 
280
gvc_mixer_control_set_default_sink (GvcMixerControl *control,
 
281
                                    GvcMixerStream  *stream)
 
282
{
 
283
        pa_operation *o;
 
284
 
 
285
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 
286
        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
 
287
 
 
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),
 
291
                                         NULL,
 
292
                                         NULL);
 
293
        if (o == NULL) {
 
294
                g_warning ("pa_context_set_default_sink() failed: %s",
 
295
                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 
296
                return FALSE;
 
297
        }
 
298
 
 
299
        pa_operation_unref (o);
 
300
 
 
301
        control->priv->new_default_sink_stream = stream;
 
302
        g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_sink_stream);
 
303
 
 
304
        o = pa_ext_stream_restore_read (control->priv->pa_context,
 
305
                                        gvc_mixer_control_stream_restore_sink_cb,
 
306
                                        control);
 
307
 
 
308
        if (o == NULL) {
 
309
                g_warning ("pa_ext_stream_restore_read() failed: %s",
 
310
                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 
311
                return FALSE;
 
312
        }
 
313
 
 
314
        pa_operation_unref (o);
 
315
 
 
316
        return TRUE;
 
317
}
 
318
 
 
319
gboolean
 
320
gvc_mixer_control_set_default_source (GvcMixerControl *control,
 
321
                                      GvcMixerStream  *stream)
 
322
{
 
323
        GvcMixerUIDevice* input;
 
324
        pa_operation *o;
 
325
 
 
326
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 
327
        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
 
328
 
 
329
        o = pa_context_set_default_source (control->priv->pa_context,
 
330
                                           gvc_mixer_stream_get_name (stream),
 
331
                                           NULL,
 
332
                                           NULL);
 
333
        if (o == NULL) {
 
334
                g_warning ("pa_context_set_default_source() failed");
 
335
                return FALSE;
 
336
        }
 
337
 
 
338
        pa_operation_unref (o);
 
339
 
 
340
        control->priv->new_default_source_stream = stream;
 
341
        g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_source_stream);
 
342
 
 
343
        o = pa_ext_stream_restore_read (control->priv->pa_context,
 
344
                                        gvc_mixer_control_stream_restore_source_cb,
 
345
                                        control);
 
346
 
 
347
        if (o == NULL) {
 
348
                g_warning ("pa_ext_stream_restore_read() failed: %s",
 
349
                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 
350
                return FALSE;
 
351
        }
 
352
 
 
353
        pa_operation_unref (o);
 
354
 
 
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],
 
359
                       0,
 
360
                       gvc_mixer_ui_device_get_id (input));
 
361
 
 
362
        return TRUE;
 
363
}
 
364
 
 
365
/**
 
366
 * gvc_mixer_control_get_default_sink:
 
367
 *
 
368
 * @control:
 
369
 *
 
370
 * Returns: (transfer none):
 
371
 */
 
372
GvcMixerStream *
 
373
gvc_mixer_control_get_default_sink (GvcMixerControl *control)
 
374
{
 
375
        GvcMixerStream *stream;
 
376
 
 
377
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
378
 
 
379
        if (control->priv->default_sink_is_set) {
 
380
                stream = g_hash_table_lookup (control->priv->all_streams,
 
381
                                              GUINT_TO_POINTER (control->priv->default_sink_id));
 
382
        } else {
 
383
                stream = NULL;
 
384
        }
 
385
 
 
386
        return stream;
 
387
}
 
388
 
 
389
/**
 
390
 * gvc_mixer_control_get_default_source:
 
391
 *
 
392
 * @control:
 
393
 *
 
394
 * Returns: (transfer none):
 
395
 */
 
396
GvcMixerStream *
 
397
gvc_mixer_control_get_default_source (GvcMixerControl *control)
 
398
{
 
399
        GvcMixerStream *stream;
 
400
 
 
401
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
402
 
 
403
        if (control->priv->default_source_is_set) {
 
404
                stream = g_hash_table_lookup (control->priv->all_streams,
 
405
                                              GUINT_TO_POINTER (control->priv->default_source_id));
 
406
        } else {
 
407
                stream = NULL;
 
408
        }
 
409
 
 
410
        return stream;
 
411
}
 
412
 
 
413
static gpointer
 
414
gvc_mixer_control_lookup_id (GHashTable *hash_table,
 
415
                             guint       id)
 
416
{
 
417
        return g_hash_table_lookup (hash_table,
 
418
                                    GUINT_TO_POINTER (id));
 
419
}
 
420
 
 
421
/**
 
422
 * gvc_mixer_control_lookup_stream_id:
 
423
 *
 
424
 * @control:
 
425
 * @id:
 
426
 *
 
427
 * Returns: (transfer none):
 
428
 */
 
429
GvcMixerStream *
 
430
gvc_mixer_control_lookup_stream_id (GvcMixerControl *control,
 
431
                                    guint            id)
 
432
{
 
433
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
434
 
 
435
        return gvc_mixer_control_lookup_id (control->priv->all_streams, id);
 
436
}
 
437
 
 
438
/**
 
439
 * gvc_mixer_control_lookup_card_id:
 
440
 *
 
441
 * @control:
 
442
 * @id:
 
443
 *
 
444
 * Returns: (transfer none):
 
445
 */
 
446
GvcMixerCard *
 
447
gvc_mixer_control_lookup_card_id (GvcMixerControl *control,
 
448
                                  guint            id)
 
449
{
 
450
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
451
 
 
452
        return gvc_mixer_control_lookup_id (control->priv->cards, id);
 
453
}
 
454
 
 
455
/**
 
456
 * gvc_mixer_control_lookup_output_id:
 
457
 * @control:
 
458
 * @id:
 
459
 * Returns: (transfer none):
 
460
 */
 
461
GvcMixerUIDevice *
 
462
gvc_mixer_control_lookup_output_id (GvcMixerControl *control,
 
463
                                    guint            id)
 
464
{
 
465
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
466
 
 
467
        return gvc_mixer_control_lookup_id (control->priv->ui_outputs, id);
 
468
}
 
469
 
 
470
/**
 
471
 * gvc_mixer_control_lookup_input_id:
 
472
 * @control:
 
473
 * @id:
 
474
 * Returns: (transfer none):
 
475
 */
 
476
GvcMixerUIDevice *
 
477
gvc_mixer_control_lookup_input_id (GvcMixerControl *control,
 
478
                                    guint            id)
 
479
{
 
480
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
481
 
 
482
        return gvc_mixer_control_lookup_id (control->priv->ui_inputs, id);
 
483
}
 
484
 
 
485
/**
 
486
 * gvc_mixer_control_get_stream_from_device:
 
487
 * @control:
 
488
 * @device:
 
489
 * Returns: (transfer none):
 
490
 */
 
491
GvcMixerStream *
 
492
gvc_mixer_control_get_stream_from_device (GvcMixerControl *control,
 
493
                                          GvcMixerUIDevice *device)
 
494
{
 
495
        gint stream_id;
 
496
 
 
497
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
498
        g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
 
499
 
 
500
        stream_id = gvc_mixer_ui_device_get_stream_id (device);
 
501
 
 
502
        if (stream_id == GVC_MIXER_UI_DEVICE_INVALID) {
 
503
                g_debug ("gvc_mixer_control_get_stream_from_device - device has a null stream");
 
504
                return NULL;
 
505
        }
 
506
        return gvc_mixer_control_lookup_stream_id (control, stream_id);
 
507
}
 
508
 
 
509
/**
 
510
 * gvc_mixer_control_change_profile_on_selected_device:
 
511
 * @control:
 
512
 * @device:
 
513
 * @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.
 
518
 */
 
519
gboolean
 
520
gvc_mixer_control_change_profile_on_selected_device (GvcMixerControl  *control,
 
521
                                                     GvcMixerUIDevice *device,
 
522
                                                     const gchar      *profile)
 
523
{
 
524
        const gchar         *best_profile;
 
525
        GvcMixerCardProfile *current_profile;
 
526
        GvcMixerCard        *card;
 
527
 
 
528
        g_object_get (G_OBJECT (device), "card", &card, NULL);
 
529
        current_profile = gvc_mixer_card_get_profile (card);
 
530
 
 
531
        if (current_profile)
 
532
                best_profile = gvc_mixer_ui_device_get_best_profile (device, profile, current_profile->profile);
 
533
        else
 
534
                best_profile = profile;
 
535
 
 
536
        g_assert (best_profile);
 
537
 
 
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));
 
542
 
 
543
        g_debug ("default sink name = %s and default sink id %u",
 
544
                 control->priv->default_sink_name,
 
545
                 control->priv->default_sink_id);
 
546
 
 
547
        control->priv->profile_swapping_device_id = gvc_mixer_ui_device_get_id (device);
 
548
 
 
549
        if (gvc_mixer_card_change_profile (card, best_profile)) {
 
550
                gvc_mixer_ui_device_set_user_preferred_profile (device, best_profile);
 
551
                return TRUE;
 
552
        }
 
553
        return FALSE;
 
554
}
 
555
 
 
556
/**
 
557
 * gvc_mixer_control_change_output:
 
558
 * @control:
 
559
 * @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.
 
572
 */
 
573
void
 
574
gvc_mixer_control_change_output (GvcMixerControl *control,
 
575
                                 GvcMixerUIDevice* output)
 
576
{
 
577
        GvcMixerStream           *stream;
 
578
        GvcMixerStream           *default_stream;
 
579
        const GvcMixerStreamPort *active_port;
 
580
        const gchar              *output_port;
 
581
 
 
582
        g_debug ("control change output");
 
583
 
 
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,
 
587
                        output, NULL);
 
588
                return;
 
589
        }
 
590
 
 
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],
 
598
                                       0,
 
599
                                       gvc_mixer_ui_device_get_id (output));
 
600
                }
 
601
                else {
 
602
                        g_warning ("Failed to set default sink with stream from output %s",
 
603
                                   gvc_mixer_ui_device_get_description (output));
 
604
                }
 
605
                return;
 
606
        }
 
607
 
 
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 !");
 
615
                        return;
 
616
                }
 
617
        }
 
618
 
 
619
        default_stream = gvc_mixer_control_get_default_sink (control);
 
620
 
 
621
        /* Finally if we are not on the correct stream, swap over. */
 
622
        if (stream != default_stream) {
 
623
                GvcMixerUIDevice* output;
 
624
 
 
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],
 
631
                                       0,
 
632
                                       gvc_mixer_ui_device_get_id (output));
 
633
                } else {
 
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],
 
638
                                       0,
 
639
                                       gvc_mixer_ui_device_get_id (output));
 
640
                }
 
641
        }
 
642
}
 
643
 
 
644
 
 
645
/**
 
646
 * gvc_mixer_control_change_input:
 
647
 * @control:
 
648
 * @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.
 
661
 */
 
662
void
 
663
gvc_mixer_control_change_input (GvcMixerControl *control,
 
664
                                GvcMixerUIDevice* input)
 
665
{
 
666
        GvcMixerStream           *stream;
 
667
        GvcMixerStream           *default_stream;
 
668
        const GvcMixerStreamPort *active_port;
 
669
        const gchar              *input_port;
 
670
 
 
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,
 
674
                        input, NULL);
 
675
                return;
 
676
        }
 
677
 
 
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));
 
684
                }
 
685
                return;
 
686
        }
 
687
 
 
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!");
 
695
                        return;
 
696
                }
 
697
        }
 
698
 
 
699
        default_stream = gvc_mixer_control_get_default_source (control);
 
700
 
 
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);
 
706
        }
 
707
}
 
708
 
 
709
 
 
710
static void
 
711
listify_hash_values_hfunc (gpointer key,
 
712
                           gpointer value,
 
713
                           gpointer user_data)
 
714
{
 
715
        GSList **list = user_data;
 
716
 
 
717
        *list = g_slist_prepend (*list, value);
 
718
}
 
719
 
 
720
static int
 
721
gvc_name_collate (const char *namea,
 
722
                  const char *nameb)
 
723
{
 
724
        if (nameb == NULL && namea == NULL)
 
725
                return 0;
 
726
        if (nameb == NULL)
 
727
                return 1;
 
728
        if (namea == NULL)
 
729
                return -1;
 
730
 
 
731
        return g_utf8_collate (namea, nameb);
 
732
}
 
733
 
 
734
static int
 
735
gvc_card_collate (GvcMixerCard *a,
 
736
                  GvcMixerCard *b)
 
737
{
 
738
        const char *namea;
 
739
        const char *nameb;
 
740
 
 
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);
 
743
 
 
744
        namea = gvc_mixer_card_get_name (a);
 
745
        nameb = gvc_mixer_card_get_name (b);
 
746
 
 
747
        return gvc_name_collate (namea, nameb);
 
748
}
 
749
 
 
750
/**
 
751
 * gvc_mixer_control_get_cards:
 
752
 *
 
753
 * @control:
 
754
 *
 
755
 * Returns: (transfer container) (element-type Gvc.MixerCard):
 
756
 */
 
757
GSList *
 
758
gvc_mixer_control_get_cards (GvcMixerControl *control)
 
759
{
 
760
        GSList *retval;
 
761
 
 
762
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
763
 
 
764
        retval = NULL;
 
765
        g_hash_table_foreach (control->priv->cards,
 
766
                              listify_hash_values_hfunc,
 
767
                              &retval);
 
768
        return g_slist_sort (retval, (GCompareFunc) gvc_card_collate);
 
769
}
 
770
 
 
771
static int
 
772
gvc_stream_collate (GvcMixerStream *a,
 
773
                    GvcMixerStream *b)
 
774
{
 
775
        const char *namea;
 
776
        const char *nameb;
 
777
 
 
778
        g_return_val_if_fail (a == NULL || GVC_IS_MIXER_STREAM (a), 0);
 
779
        g_return_val_if_fail (b == NULL || GVC_IS_MIXER_STREAM (b), 0);
 
780
 
 
781
        namea = gvc_mixer_stream_get_name (a);
 
782
        nameb = gvc_mixer_stream_get_name (b);
 
783
 
 
784
        return gvc_name_collate (namea, nameb);
 
785
}
 
786
 
 
787
/**
 
788
 * gvc_mixer_control_get_streams:
 
789
 *
 
790
 * @control:
 
791
 *
 
792
 * Returns: (transfer container) (element-type Gvc.MixerStream):
 
793
 */
 
794
GSList *
 
795
gvc_mixer_control_get_streams (GvcMixerControl *control)
 
796
{
 
797
        GSList *retval;
 
798
 
 
799
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
800
 
 
801
        retval = NULL;
 
802
        g_hash_table_foreach (control->priv->all_streams,
 
803
                              listify_hash_values_hfunc,
 
804
                              &retval);
 
805
        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 
806
}
 
807
 
 
808
/**
 
809
 * gvc_mixer_control_get_sinks:
 
810
 *
 
811
 * @control:
 
812
 *
 
813
 * Returns: (transfer container) (element-type Gvc.MixerSink):
 
814
 */
 
815
GSList *
 
816
gvc_mixer_control_get_sinks (GvcMixerControl *control)
 
817
{
 
818
        GSList *retval;
 
819
 
 
820
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
821
 
 
822
        retval = NULL;
 
823
        g_hash_table_foreach (control->priv->sinks,
 
824
                              listify_hash_values_hfunc,
 
825
                              &retval);
 
826
        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 
827
}
 
828
 
 
829
/**
 
830
 * gvc_mixer_control_get_sources:
 
831
 *
 
832
 * @control:
 
833
 *
 
834
 * Returns: (transfer container) (element-type Gvc.MixerSource):
 
835
 */
 
836
GSList *
 
837
gvc_mixer_control_get_sources (GvcMixerControl *control)
 
838
{
 
839
        GSList *retval;
 
840
 
 
841
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
842
 
 
843
        retval = NULL;
 
844
        g_hash_table_foreach (control->priv->sources,
 
845
                              listify_hash_values_hfunc,
 
846
                              &retval);
 
847
        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 
848
}
 
849
 
 
850
/**
 
851
 * gvc_mixer_control_get_sink_inputs:
 
852
 *
 
853
 * @control:
 
854
 *
 
855
 * Returns: (transfer container) (element-type Gvc.MixerSinkInput):
 
856
 */
 
857
GSList *
 
858
gvc_mixer_control_get_sink_inputs (GvcMixerControl *control)
 
859
{
 
860
        GSList *retval;
 
861
 
 
862
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
863
 
 
864
        retval = NULL;
 
865
        g_hash_table_foreach (control->priv->sink_inputs,
 
866
                              listify_hash_values_hfunc,
 
867
                              &retval);
 
868
        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 
869
}
 
870
 
 
871
/**
 
872
 * gvc_mixer_control_get_source_outputs:
 
873
 *
 
874
 * @control:
 
875
 *
 
876
 * Returns: (transfer container) (element-type Gvc.MixerSourceOutput):
 
877
 */
 
878
GSList *
 
879
gvc_mixer_control_get_source_outputs (GvcMixerControl *control)
 
880
{
 
881
        GSList *retval;
 
882
 
 
883
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 
884
 
 
885
        retval = NULL;
 
886
        g_hash_table_foreach (control->priv->source_outputs,
 
887
                              listify_hash_values_hfunc,
 
888
                              &retval);
 
889
        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 
890
}
 
891
 
 
892
static void
 
893
dec_outstanding (GvcMixerControl *control)
 
894
{
 
895
        if (control->priv->n_outstanding <= 0) {
 
896
                return;
 
897
        }
 
898
 
 
899
        if (--control->priv->n_outstanding <= 0) {
 
900
                control->priv->state = GVC_STATE_READY;
 
901
                g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_READY);
 
902
        }
 
903
}
 
904
 
 
905
GvcMixerControlState
 
906
gvc_mixer_control_get_state (GvcMixerControl *control)
 
907
{
 
908
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 
909
 
 
910
        return control->priv->state;
 
911
}
 
912
 
 
913
static void
 
914
on_default_source_port_notify (GObject        *object,
 
915
                               GParamSpec     *pspec,
 
916
                               GvcMixerControl *control)
 
917
{
 
918
        char             *port;
 
919
        GvcMixerUIDevice *input;
 
920
 
 
921
        g_object_get (object, "port", &port, NULL);
 
922
        input = gvc_mixer_control_lookup_device_from_stream (control,
 
923
                                                             GVC_MIXER_STREAM (object));
 
924
 
 
925
        g_debug ("on_default_source_port_notify - moved to port '%s' which SHOULD ?? correspond to output '%s'",
 
926
                 port,
 
927
                 gvc_mixer_ui_device_get_description (input));
 
928
 
 
929
        g_signal_emit (G_OBJECT (control),
 
930
                       signals[ACTIVE_INPUT_UPDATE],
 
931
                       0,
 
932
                       gvc_mixer_ui_device_get_id (input));
 
933
 
 
934
        g_free (port);
 
935
}
 
936
 
 
937
 
 
938
static void
 
939
_set_default_source (GvcMixerControl *control,
 
940
                     GvcMixerStream  *stream)
 
941
{
 
942
        guint new_id;
 
943
 
 
944
        if (stream == NULL) {
 
945
                control->priv->default_source_id = 0;
 
946
                control->priv->default_source_is_set = FALSE;
 
947
                g_signal_emit (control,
 
948
                               signals[DEFAULT_SOURCE_CHANGED],
 
949
                               0,
 
950
                               PA_INVALID_INDEX);
 
951
                return;
 
952
        }
 
953
 
 
954
        new_id = gvc_mixer_stream_get_id (stream);
 
955
 
 
956
        if (control->priv->default_source_id != new_id) {
 
957
                GvcMixerUIDevice *input;
 
958
                control->priv->default_source_id = new_id;
 
959
                control->priv->default_source_is_set = TRUE;
 
960
                g_signal_emit (control,
 
961
                               signals[DEFAULT_SOURCE_CHANGED],
 
962
                               0,
 
963
                               new_id);
 
964
 
 
965
                if (control->priv->default_source_is_set) {
 
966
                        g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_source (control),
 
967
                                                              on_default_source_port_notify,
 
968
                                                              control);
 
969
                }
 
970
 
 
971
                g_signal_connect (stream,
 
972
                                  "notify::port",
 
973
                                  G_CALLBACK (on_default_source_port_notify),
 
974
                                  control);
 
975
 
 
976
                input = gvc_mixer_control_lookup_device_from_stream (control, stream);
 
977
 
 
978
                g_signal_emit (G_OBJECT (control),
 
979
                               signals[ACTIVE_INPUT_UPDATE],
 
980
                               0,
 
981
                               gvc_mixer_ui_device_get_id (input));
 
982
        }
 
983
}
 
984
 
 
985
static void
 
986
on_default_sink_port_notify (GObject        *object,
 
987
                             GParamSpec     *pspec,
 
988
                             GvcMixerControl *control)
 
989
{
 
990
        char             *port;
 
991
        GvcMixerUIDevice *output;
 
992
 
 
993
        g_object_get (object, "port", &port, NULL);
 
994
 
 
995
        output = gvc_mixer_control_lookup_device_from_stream (control,
 
996
                                                              GVC_MIXER_STREAM (object));
 
997
        if (output != NULL) {
 
998
                g_debug ("on_default_sink_port_notify - moved to port %s - which SHOULD correspond to output %s",
 
999
                         port,
 
1000
                         gvc_mixer_ui_device_get_description (output));
 
1001
                g_signal_emit (G_OBJECT (control),
 
1002
                               signals[ACTIVE_OUTPUT_UPDATE],
 
1003
                               0,
 
1004
                               gvc_mixer_ui_device_get_id (output));
 
1005
        }
 
1006
        g_free (port);
 
1007
}
 
1008
 
 
1009
static void
 
1010
_set_default_sink (GvcMixerControl *control,
 
1011
                   GvcMixerStream  *stream)
 
1012
{
 
1013
        guint new_id;
 
1014
 
 
1015
        if (stream == NULL) {
 
1016
                /* Don't tell front-ends about an unset default
 
1017
                 * sink if it's already unset */
 
1018
                if (control->priv->default_sink_is_set == FALSE)
 
1019
                        return;
 
1020
                control->priv->default_sink_id = 0;
 
1021
                control->priv->default_sink_is_set = FALSE;
 
1022
                g_signal_emit (control,
 
1023
                               signals[DEFAULT_SINK_CHANGED],
 
1024
                               0,
 
1025
                               PA_INVALID_INDEX);
 
1026
                return;
 
1027
        }
 
1028
 
 
1029
        new_id = gvc_mixer_stream_get_id (stream);
 
1030
 
 
1031
        if (control->priv->default_sink_id != new_id) {
 
1032
                GvcMixerUIDevice *output;
 
1033
                if (control->priv->default_sink_is_set) {
 
1034
                        g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_sink (control),
 
1035
                                                              on_default_sink_port_notify,
 
1036
                                                              control);
 
1037
                }
 
1038
 
 
1039
                control->priv->default_sink_id = new_id;
 
1040
 
 
1041
                control->priv->default_sink_is_set = TRUE;
 
1042
                g_signal_emit (control,
 
1043
                               signals[DEFAULT_SINK_CHANGED],
 
1044
                               0,
 
1045
                               new_id);
 
1046
 
 
1047
                g_signal_connect (stream,
 
1048
                                  "notify::port",
 
1049
                                  G_CALLBACK (on_default_sink_port_notify),
 
1050
                                  control);
 
1051
 
 
1052
                output = gvc_mixer_control_lookup_device_from_stream (control, stream);
 
1053
 
 
1054
                g_debug ("active_sink change");
 
1055
 
 
1056
                g_signal_emit (G_OBJECT (control),
 
1057
                               signals[ACTIVE_OUTPUT_UPDATE],
 
1058
                               0,
 
1059
                               gvc_mixer_ui_device_get_id (output));
 
1060
        }
 
1061
}
 
1062
 
 
1063
static gboolean
 
1064
_stream_has_name (gpointer        key,
 
1065
                  GvcMixerStream *stream,
 
1066
                  const char     *name)
 
1067
{
 
1068
        const char *t_name;
 
1069
 
 
1070
        t_name = gvc_mixer_stream_get_name (stream);
 
1071
 
 
1072
        if (t_name != NULL
 
1073
            && name != NULL
 
1074
            && strcmp (t_name, name) == 0) {
 
1075
                return TRUE;
 
1076
        }
 
1077
 
 
1078
        return FALSE;
 
1079
}
 
1080
 
 
1081
static GvcMixerStream *
 
1082
find_stream_for_name (GvcMixerControl *control,
 
1083
                      const char      *name)
 
1084
{
 
1085
        GvcMixerStream *stream;
 
1086
 
 
1087
        stream = g_hash_table_find (control->priv->all_streams,
 
1088
                                    (GHRFunc)_stream_has_name,
 
1089
                                    (char *)name);
 
1090
        return stream;
 
1091
}
 
1092
 
 
1093
static void
 
1094
update_default_source_from_name (GvcMixerControl *control,
 
1095
                                 const char      *name)
 
1096
{
 
1097
        gboolean changed = FALSE;
 
1098
 
 
1099
        if ((control->priv->default_source_name == NULL
 
1100
             && name != NULL)
 
1101
            || (control->priv->default_source_name != NULL
 
1102
                && name == NULL)
 
1103
            || (name != NULL && strcmp (control->priv->default_source_name, name) != 0)) {
 
1104
                changed = TRUE;
 
1105
        }
 
1106
 
 
1107
        if (changed) {
 
1108
                GvcMixerStream *stream;
 
1109
 
 
1110
                g_free (control->priv->default_source_name);
 
1111
                control->priv->default_source_name = g_strdup (name);
 
1112
 
 
1113
                stream = find_stream_for_name (control, name);
 
1114
                _set_default_source (control, stream);
 
1115
        }
 
1116
}
 
1117
 
 
1118
static void
 
1119
update_default_sink_from_name (GvcMixerControl *control,
 
1120
                               const char      *name)
 
1121
{
 
1122
        gboolean changed = FALSE;
 
1123
 
 
1124
        if ((control->priv->default_sink_name == NULL
 
1125
             && name != NULL)
 
1126
            || (control->priv->default_sink_name != NULL
 
1127
                && name == NULL)
 
1128
            || (name != NULL && strcmp (control->priv->default_sink_name, name) != 0)) {
 
1129
                changed = TRUE;
 
1130
        }
 
1131
 
 
1132
        if (changed) {
 
1133
                GvcMixerStream *stream;
 
1134
                g_free (control->priv->default_sink_name);
 
1135
                control->priv->default_sink_name = g_strdup (name);
 
1136
 
 
1137
                stream = find_stream_for_name (control, name);
 
1138
                _set_default_sink (control, stream);
 
1139
        }
 
1140
}
 
1141
 
 
1142
static void
 
1143
update_server (GvcMixerControl      *control,
 
1144
               const pa_server_info *info)
 
1145
{
 
1146
        if (info->default_source_name != NULL) {
 
1147
                update_default_source_from_name (control, info->default_source_name);
 
1148
        }
 
1149
        if (info->default_sink_name != NULL) {
 
1150
                g_debug ("update server");
 
1151
                update_default_sink_from_name (control, info->default_sink_name);
 
1152
        }
 
1153
}
 
1154
 
 
1155
static void
 
1156
remove_stream (GvcMixerControl *control,
 
1157
               GvcMixerStream  *stream)
 
1158
{
 
1159
        guint id;
 
1160
 
 
1161
        g_object_ref (stream);
 
1162
 
 
1163
        id = gvc_mixer_stream_get_id (stream);
 
1164
 
 
1165
        if (id == control->priv->default_sink_id) {
 
1166
                _set_default_sink (control, NULL);
 
1167
        } else if (id == control->priv->default_source_id) {
 
1168
                _set_default_source (control, NULL);
 
1169
        }
 
1170
 
 
1171
        g_hash_table_remove (control->priv->all_streams,
 
1172
                             GUINT_TO_POINTER (id));
 
1173
        g_signal_emit (G_OBJECT (control),
 
1174
                       signals[STREAM_REMOVED],
 
1175
                       0,
 
1176
                       gvc_mixer_stream_get_id (stream));
 
1177
        g_object_unref (stream);
 
1178
}
 
1179
 
 
1180
static void
 
1181
add_stream (GvcMixerControl *control,
 
1182
            GvcMixerStream  *stream)
 
1183
{
 
1184
        g_hash_table_insert (control->priv->all_streams,
 
1185
                             GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)),
 
1186
                             stream);
 
1187
        g_signal_emit (G_OBJECT (control),
 
1188
                       signals[STREAM_ADDED],
 
1189
                       0,
 
1190
                       gvc_mixer_stream_get_id (stream));
 
1191
}
 
1192
 
 
1193
/* This method will match individual stream ports against its corresponding device
 
1194
 * It does this by:
 
1195
 * - 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
 
1196
 *   and the port-name on the device is the same as the streamport-name.
 
1197
 * This should always find a match and is used exclusively by sync_devices().
 
1198
 */
 
1199
static gboolean
 
1200
match_stream_with_devices (GvcMixerControl    *control,
 
1201
                           GvcMixerStreamPort *stream_port,
 
1202
                           GvcMixerStream     *stream)
 
1203
{
 
1204
        GList                   *devices, *d;
 
1205
        guint                    stream_card_id;
 
1206
        guint                    stream_id;
 
1207
        gboolean                 in_possession = FALSE;
 
1208
 
 
1209
        stream_id      =  gvc_mixer_stream_get_id (stream);
 
1210
        stream_card_id =  gvc_mixer_stream_get_card_index (stream);
 
1211
 
 
1212
        devices  = g_hash_table_get_values (GVC_IS_MIXER_SOURCE (stream) ? control->priv->ui_inputs : control->priv->ui_outputs);
 
1213
 
 
1214
        for (d = devices; d != NULL; d = d->next) {
 
1215
                GvcMixerUIDevice *device;
 
1216
                gint              device_stream_id;
 
1217
                gchar            *device_port_name;
 
1218
                gchar            *origin;
 
1219
                gchar            *description;
 
1220
                GvcMixerCard     *card;
 
1221
                gint              card_id;
 
1222
 
 
1223
                device = d->data;
 
1224
                g_object_get (G_OBJECT (device),
 
1225
                             "stream-id", &device_stream_id,
 
1226
                             "card", &card,
 
1227
                             "origin", &origin,
 
1228
                             "description", &description,
 
1229
                             "port-name", &device_port_name,
 
1230
                              NULL);
 
1231
 
 
1232
                card_id = gvc_mixer_card_get_index (card);
 
1233
 
 
1234
                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",
 
1235
                         description,
 
1236
                         origin,
 
1237
                         device_port_name,
 
1238
                         card,
 
1239
                         stream_port->port,
 
1240
                         stream_card_id);
 
1241
 
 
1242
                if (stream_card_id == card_id &&
 
1243
                    g_strcmp0 (device_port_name, stream_port->port) == 0) {
 
1244
                        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",
 
1245
                                 description,
 
1246
                                 origin,
 
1247
                                 gvc_mixer_ui_device_get_id (device),
 
1248
                                 stream_id);
 
1249
 
 
1250
                        g_object_set (G_OBJECT (device),
 
1251
                                      "stream-id", (gint)stream_id,
 
1252
                                      NULL);
 
1253
                        in_possession = TRUE;
 
1254
                }
 
1255
 
 
1256
                g_free (device_port_name);
 
1257
                g_free (origin);
 
1258
                g_free (description);
 
1259
 
 
1260
                if (in_possession == TRUE)
 
1261
                        break;
 
1262
        }
 
1263
 
 
1264
        g_list_free (devices);
 
1265
        return in_possession;
 
1266
}
 
1267
 
 
1268
/*
 
1269
 * This method attempts to match a sink or source with its relevant UI device.
 
1270
 * GvcMixerStream can represent both a sink or source.
 
1271
 * Using static card port introspection implies that we know beforehand what
 
1272
 * outputs and inputs are available to the user.
 
1273
 * But that does not mean that all of these inputs and outputs are available to be used.
 
1274
 * For instance we might be able to see that there is a HDMI port available but if
 
1275
 * we are on the default analog stereo output profile there is no valid sink for
 
1276
 * that HDMI device. We first need to change profile and when update_sink() is called
 
1277
 * only then can we match the new hdmi sink with its corresponding device.
 
1278
 *
 
1279
 * Firstly it checks to see if the incoming stream has no ports.
 
1280
 * - If a stream has no ports but has a valid card ID (bluetooth), it will attempt
 
1281
 *   to match the device with the stream using the card id.
 
1282
 * - If a stream has no ports and no valid card id, it goes ahead and makes a new
 
1283
 *   device (software/network devices are only detectable at the sink/source level)
 
1284
 * If the stream has ports it will match each port against the stream using match_stream_with_devices().
 
1285
 *
 
1286
 * This method should always find a match.
 
1287
 */
 
1288
static void
 
1289
sync_devices (GvcMixerControl *control,
 
1290
              GvcMixerStream* stream)
 
1291
{
 
1292
        /* Go through ports to see what outputs can be created. */
 
1293
        const GList *stream_ports;
 
1294
        const GList *n = NULL;
 
1295
        gboolean     is_output = !GVC_IS_MIXER_SOURCE (stream);
 
1296
        gint         stream_port_count = 0;
 
1297
 
 
1298
        stream_ports = gvc_mixer_stream_get_ports (stream);
 
1299
 
 
1300
        if (stream_ports == NULL) {
 
1301
                GvcMixerUIDevice *device;
 
1302
                /* Bluetooth, no ports but a valid card */
 
1303
                if (gvc_mixer_stream_get_card_index (stream) != PA_INVALID_INDEX) {
 
1304
                        GList *devices, *d;
 
1305
                        gboolean in_possession = FALSE;
 
1306
 
 
1307
                        devices = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
 
1308
 
 
1309
                        for (d = devices; d != NULL; d = d->next) {
 
1310
                                GvcMixerCard *card;
 
1311
                                gint card_id;
 
1312
 
 
1313
                                device = d->data;
 
1314
 
 
1315
                                g_object_get (G_OBJECT (device),
 
1316
                                             "card", &card,
 
1317
                                              NULL);
 
1318
                                card_id = gvc_mixer_card_get_index (card);
 
1319
                                g_debug ("sync devices, device description - '%s', device card id - %i, stream description - %s, stream card id - %i",
 
1320
                                         gvc_mixer_ui_device_get_description (device),
 
1321
                                         card_id,
 
1322
                                         gvc_mixer_stream_get_description (stream),
 
1323
                                         gvc_mixer_stream_get_card_index (stream));
 
1324
                                if (card_id == gvc_mixer_stream_get_card_index (stream)) {
 
1325
                                        in_possession = TRUE;
 
1326
                                        break;
 
1327
                                }
 
1328
                        }
 
1329
                        g_list_free (devices);
 
1330
 
 
1331
                        if (!in_possession) {
 
1332
                                g_warning ("Couldn't match the portless stream (with card) - '%s' is it an input ? -> %i, streams card id -> %i",
 
1333
                                           gvc_mixer_stream_get_description (stream),
 
1334
                                           GVC_IS_MIXER_SOURCE (stream),
 
1335
                                           gvc_mixer_stream_get_card_index (stream));
 
1336
                                return;
 
1337
                        }
 
1338
 
 
1339
                        g_object_set (G_OBJECT (device),
 
1340
                                      "stream-id", (gint)gvc_mixer_stream_get_id (stream),
 
1341
                                      "description", gvc_mixer_stream_get_description (stream),
 
1342
                                      "origin", "", /*Leave it empty for these special cases*/
 
1343
                                      "port-name", NULL,
 
1344
                                      "port-available", TRUE,
 
1345
                                      NULL);
 
1346
                } else { /* Network sink/source has no ports and no card. */
 
1347
                        GObject *object;
 
1348
 
 
1349
                        object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
 
1350
                                               "stream-id", (gint)gvc_mixer_stream_get_id (stream),
 
1351
                                               "description", gvc_mixer_stream_get_description (stream),
 
1352
                                               "origin", "", /* Leave it empty for these special cases */
 
1353
                                               "port-name", NULL,
 
1354
                                               "port-available", TRUE,
 
1355
                                                NULL);
 
1356
                        device = GVC_MIXER_UI_DEVICE (object);
 
1357
 
 
1358
                        g_hash_table_insert (is_output ? control->priv->ui_outputs : control->priv->ui_inputs,
 
1359
                                             GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)),
 
1360
                                             g_object_ref (device));
 
1361
 
 
1362
                }
 
1363
                g_signal_emit (G_OBJECT (control),
 
1364
                               signals[is_output ? OUTPUT_ADDED : INPUT_ADDED],
 
1365
                               0,
 
1366
                               gvc_mixer_ui_device_get_id (device));
 
1367
 
 
1368
                return;
 
1369
        }
 
1370
 
 
1371
        /* Go ahead and make sure to match each port against a previously created device */
 
1372
        for (n = stream_ports; n != NULL; n = n->next) {
 
1373
 
 
1374
                GvcMixerStreamPort *stream_port;
 
1375
                stream_port = n->data;
 
1376
                stream_port_count ++;
 
1377
 
 
1378
                if (match_stream_with_devices (control, stream_port, stream))
 
1379
                        continue;
 
1380
 
 
1381
                g_warning ("Sync_devices: Failed to match stream id: %u, description: '%s', origin: '%s'",
 
1382
                           gvc_mixer_stream_get_id (stream),
 
1383
                           stream_port->human_port,
 
1384
                           gvc_mixer_stream_get_description (stream));
 
1385
        }
 
1386
}
 
1387
 
 
1388
static void
 
1389
set_icon_name_from_proplist (GvcMixerStream *stream,
 
1390
                             pa_proplist    *l,
 
1391
                             const char     *default_icon_name)
 
1392
{
 
1393
        const char *t;
 
1394
 
 
1395
        if ((t = pa_proplist_gets (l, PA_PROP_DEVICE_ICON_NAME))) {
 
1396
                goto finish;
 
1397
        }
 
1398
 
 
1399
        if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ICON_NAME))) {
 
1400
                goto finish;
 
1401
        }
 
1402
 
 
1403
        if ((t = pa_proplist_gets (l, PA_PROP_WINDOW_ICON_NAME))) {
 
1404
                goto finish;
 
1405
        }
 
1406
 
 
1407
        if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ICON_NAME))) {
 
1408
                goto finish;
 
1409
        }
 
1410
 
 
1411
        if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
 
1412
 
 
1413
                if (strcmp (t, "video") == 0 ||
 
1414
                    strcmp (t, "phone") == 0) {
 
1415
                        goto finish;
 
1416
                }
 
1417
 
 
1418
                if (strcmp (t, "music") == 0) {
 
1419
                        t = "audio";
 
1420
                        goto finish;
 
1421
                }
 
1422
 
 
1423
                if (strcmp (t, "game") == 0) {
 
1424
                        t = "applications-games";
 
1425
                        goto finish;
 
1426
                }
 
1427
 
 
1428
                if (strcmp (t, "event") == 0) {
 
1429
                        t = "dialog-information";
 
1430
                        goto finish;
 
1431
                }
 
1432
        }
 
1433
 
 
1434
        t = default_icon_name;
 
1435
 
 
1436
 finish:
 
1437
        gvc_mixer_stream_set_icon_name (stream, t);
 
1438
}
 
1439
 
 
1440
/*
 
1441
 * Called when anything changes with a sink.
 
1442
 */
 
1443
static void
 
1444
update_sink (GvcMixerControl    *control,
 
1445
             const pa_sink_info *info)
 
1446
{
 
1447
        GvcMixerStream  *stream;
 
1448
        gboolean        is_new;
 
1449
        pa_volume_t     max_volume;
 
1450
        GvcChannelMap   *map;
 
1451
        char            map_buff[PA_CHANNEL_MAP_SNPRINT_MAX];
 
1452
 
 
1453
        pa_channel_map_snprint (map_buff, PA_CHANNEL_MAP_SNPRINT_MAX, &info->channel_map);
 
1454
#if 1
 
1455
        g_debug ("Updating sink: index=%u name='%s' description='%s' map='%s'",
 
1456
                 info->index,
 
1457
                 info->name,
 
1458
                 info->description,
 
1459
                 map_buff);
 
1460
#endif
 
1461
 
 
1462
        map = NULL;
 
1463
        is_new = FALSE;
 
1464
        stream = g_hash_table_lookup (control->priv->sinks,
 
1465
                                      GUINT_TO_POINTER (info->index));
 
1466
 
 
1467
        if (stream == NULL) {
 
1468
                GList *list = NULL;
 
1469
                guint i;
 
1470
 
 
1471
                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
 
1472
                stream = gvc_mixer_sink_new (control->priv->pa_context,
 
1473
                                             info->index,
 
1474
                                             map);
 
1475
 
 
1476
                for (i = 0; i < info->n_ports; i++) {
 
1477
                        GvcMixerStreamPort *port;
 
1478
 
 
1479
                        port = g_new0 (GvcMixerStreamPort, 1);
 
1480
                        port->port = g_strdup (info->ports[i]->name);
 
1481
                        port->human_port = g_strdup (info->ports[i]->description);
 
1482
                        port->priority = info->ports[i]->priority;
 
1483
                        port->available = info->ports[i]->available != PA_PORT_AVAILABLE_NO;
 
1484
 
 
1485
                        list = g_list_prepend (list, port);
 
1486
                }
 
1487
                gvc_mixer_stream_set_ports (stream, list);
 
1488
 
 
1489
                g_object_unref (map);
 
1490
                is_new = TRUE;
 
1491
 
 
1492
        } else if (gvc_mixer_stream_is_running (stream)) {
 
1493
                /* Ignore events if volume changes are outstanding */
 
1494
                g_debug ("Ignoring event, volume changes are outstanding");
 
1495
                return;
 
1496
        }
 
1497
 
 
1498
        max_volume = pa_cvolume_max (&info->volume);
 
1499
        gvc_mixer_stream_set_name (stream, info->name);
 
1500
        gvc_mixer_stream_set_card_index (stream, info->card);
 
1501
        gvc_mixer_stream_set_description (stream, info->description);
 
1502
        set_icon_name_from_proplist (stream, info->proplist, "audio-card");
 
1503
        gvc_mixer_stream_set_sysfs_path (stream, pa_proplist_gets (info->proplist, "sysfs.path"));
 
1504
        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
 
1505
        gvc_mixer_stream_set_is_muted (stream, info->mute);
 
1506
        gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME));
 
1507
        gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
 
1508
 
 
1509
        /* Messy I know but to set the port everytime regardless of whether it has changed will cost us a
 
1510
         * port change notify signal which causes the frontend to resync.
 
1511
         * Only update the UI when something has changed. */
 
1512
        if (info->active_port != NULL) {
 
1513
                if (is_new)
 
1514
                        gvc_mixer_stream_set_port (stream, info->active_port->name);
 
1515
                else {
 
1516
                        const GvcMixerStreamPort *active_port;
 
1517
                        active_port = gvc_mixer_stream_get_port (stream);
 
1518
                        if (active_port == NULL ||
 
1519
                            g_strcmp0 (active_port->port, info->active_port->name) != 0) {
 
1520
                                g_debug ("update sink - apparently a port update");
 
1521
                                gvc_mixer_stream_set_port (stream, info->active_port->name);
 
1522
                        }
 
1523
                }
 
1524
        }
 
1525
 
 
1526
        if (is_new) {
 
1527
                g_debug ("update sink - is new");
 
1528
 
 
1529
                g_hash_table_insert (control->priv->sinks,
 
1530
                                     GUINT_TO_POINTER (info->index),
 
1531
                                     g_object_ref (stream));
 
1532
                add_stream (control, stream);
 
1533
                /* Always sink on a new stream to able to assign the right stream id
 
1534
                 * to the appropriate outputs (multiple potential outputs per stream). */
 
1535
                sync_devices (control, stream);
 
1536
        }
 
1537
 
 
1538
        /*
 
1539
         * When we change profile on a device that is not the server default sink,
 
1540
         * it will jump back to the default sink set by the server to prevent the audio setup from being 'outputless'.
 
1541
         * All well and good but then when we get the new stream created for the new profile how do we know
 
1542
         * that this is the intended default or selected device the user wishes to use.
 
1543
         * This is messy but it's the only reliable way that it can be done without ripping the whole thing apart.
 
1544
         */
 
1545
        if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
 
1546
                GvcMixerUIDevice *dev = NULL;
 
1547
                dev = gvc_mixer_control_lookup_output_id (control, control->priv->profile_swapping_device_id);
 
1548
                if (dev != NULL) {
 
1549
                        /* now check to make sure this new stream is the same stream just matched and set on the device object */
 
1550
                        if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
 
1551
                                g_debug ("Looks like we profile swapped on a non server default sink");
 
1552
                                gvc_mixer_control_set_default_sink (control, stream);
 
1553
                        }
 
1554
                }
 
1555
                control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
 
1556
        }
 
1557
 
 
1558
        if (control->priv->default_sink_name != NULL
 
1559
            && info->name != NULL
 
1560
            && strcmp (control->priv->default_sink_name, info->name) == 0) {
 
1561
                _set_default_sink (control, stream);
 
1562
        }
 
1563
 
 
1564
        if (map == NULL)
 
1565
                map = (GvcChannelMap *) gvc_mixer_stream_get_channel_map (stream);
 
1566
 
 
1567
        gvc_channel_map_volume_changed (map, &info->volume, FALSE);
 
1568
}
 
1569
 
 
1570
static void
 
1571
update_source (GvcMixerControl      *control,
 
1572
               const pa_source_info *info)
 
1573
{
 
1574
        GvcMixerStream *stream;
 
1575
        gboolean        is_new;
 
1576
        pa_volume_t     max_volume;
 
1577
 
 
1578
#if 1
 
1579
        g_debug ("Updating source: index=%u name='%s' description='%s'",
 
1580
                 info->index,
 
1581
                 info->name,
 
1582
                 info->description);
 
1583
#endif
 
1584
 
 
1585
        /* completely ignore monitors, they're not real sources */
 
1586
        if (info->monitor_of_sink != PA_INVALID_INDEX) {
 
1587
                return;
 
1588
        }
 
1589
 
 
1590
        is_new = FALSE;
 
1591
 
 
1592
        stream = g_hash_table_lookup (control->priv->sources,
 
1593
                                      GUINT_TO_POINTER (info->index));
 
1594
        if (stream == NULL) {
 
1595
                GList *list = NULL;
 
1596
                guint i;
 
1597
                GvcChannelMap *map;
 
1598
 
 
1599
                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
 
1600
                stream = gvc_mixer_source_new (control->priv->pa_context,
 
1601
                                               info->index,
 
1602
                                               map);
 
1603
 
 
1604
                for (i = 0; i < info->n_ports; i++) {
 
1605
                        GvcMixerStreamPort *port;
 
1606
 
 
1607
                        port = g_new0 (GvcMixerStreamPort, 1);
 
1608
                        port->port = g_strdup (info->ports[i]->name);
 
1609
                        port->human_port = g_strdup (info->ports[i]->description);
 
1610
                        port->priority = info->ports[i]->priority;
 
1611
                        list = g_list_prepend (list, port);
 
1612
                }
 
1613
                gvc_mixer_stream_set_ports (stream, list);
 
1614
 
 
1615
                g_object_unref (map);
 
1616
                is_new = TRUE;
 
1617
        } else if (gvc_mixer_stream_is_running (stream)) {
 
1618
                /* Ignore events if volume changes are outstanding */
 
1619
                g_debug ("Ignoring event, volume changes are outstanding");
 
1620
                return;
 
1621
        }
 
1622
 
 
1623
        max_volume = pa_cvolume_max (&info->volume);
 
1624
 
 
1625
        gvc_mixer_stream_set_name (stream, info->name);
 
1626
        gvc_mixer_stream_set_card_index (stream, info->card);
 
1627
        gvc_mixer_stream_set_description (stream, info->description);
 
1628
        set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
 
1629
        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
 
1630
        gvc_mixer_stream_set_is_muted (stream, info->mute);
 
1631
        gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME));
 
1632
        gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
 
1633
        g_debug ("update source");
 
1634
 
 
1635
        if (info->active_port != NULL) {
 
1636
                if (is_new)
 
1637
                        gvc_mixer_stream_set_port (stream, info->active_port->name);
 
1638
                else {
 
1639
                        const GvcMixerStreamPort *active_port;
 
1640
                        active_port = gvc_mixer_stream_get_port (stream);
 
1641
                        if (active_port == NULL ||
 
1642
                            g_strcmp0 (active_port->port, info->active_port->name) != 0) {
 
1643
                                g_debug ("update source - apparently a port update");
 
1644
                                gvc_mixer_stream_set_port (stream, info->active_port->name);
 
1645
                        }
 
1646
                }
 
1647
        }
 
1648
 
 
1649
        if (is_new) {
 
1650
                g_hash_table_insert (control->priv->sources,
 
1651
                                     GUINT_TO_POINTER (info->index),
 
1652
                                     g_object_ref (stream));
 
1653
                add_stream (control, stream);
 
1654
                sync_devices (control, stream);
 
1655
        }
 
1656
 
 
1657
        if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
 
1658
                GvcMixerUIDevice *dev = NULL;
 
1659
 
 
1660
                dev = gvc_mixer_control_lookup_input_id (control, control->priv->profile_swapping_device_id);
 
1661
 
 
1662
                if (dev != NULL) {
 
1663
                        /* now check to make sure this new stream is the same stream just matched and set on the device object */
 
1664
                        if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
 
1665
                                g_debug ("Looks like we profile swapped on a non server default sink");
 
1666
                                gvc_mixer_control_set_default_source (control, stream);
 
1667
                        }
 
1668
                }
 
1669
                control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
 
1670
        }
 
1671
        if (control->priv->default_source_name != NULL
 
1672
            && info->name != NULL
 
1673
            && strcmp (control->priv->default_source_name, info->name) == 0) {
 
1674
                _set_default_source (control, stream);
 
1675
        }
 
1676
}
 
1677
 
 
1678
static void
 
1679
set_is_event_stream_from_proplist (GvcMixerStream *stream,
 
1680
                                   pa_proplist    *l)
 
1681
{
 
1682
        const char *t;
 
1683
        gboolean is_event_stream;
 
1684
 
 
1685
        is_event_stream = FALSE;
 
1686
 
 
1687
        if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
 
1688
                if (g_str_equal (t, "event"))
 
1689
                        is_event_stream = TRUE;
 
1690
        }
 
1691
 
 
1692
        gvc_mixer_stream_set_is_event_stream (stream, is_event_stream);
 
1693
}
 
1694
 
 
1695
static void
 
1696
set_application_id_from_proplist (GvcMixerStream *stream,
 
1697
                                  pa_proplist    *l)
 
1698
{
 
1699
        const char *t;
 
1700
 
 
1701
        if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ID))) {
 
1702
                gvc_mixer_stream_set_application_id (stream, t);
 
1703
        }
 
1704
}
 
1705
 
 
1706
static void
 
1707
update_sink_input (GvcMixerControl          *control,
 
1708
                   const pa_sink_input_info *info)
 
1709
{
 
1710
        GvcMixerStream *stream;
 
1711
        gboolean        is_new;
 
1712
        pa_volume_t     max_volume;
 
1713
        const char     *name;
 
1714
 
 
1715
#if 0
 
1716
        g_debug ("Updating sink input: index=%u name='%s' client=%u sink=%u",
 
1717
                 info->index,
 
1718
                 info->name,
 
1719
                 info->client,
 
1720
                 info->sink);
 
1721
#endif
 
1722
 
 
1723
        is_new = FALSE;
 
1724
 
 
1725
        stream = g_hash_table_lookup (control->priv->sink_inputs,
 
1726
                                      GUINT_TO_POINTER (info->index));
 
1727
        if (stream == NULL) {
 
1728
                GvcChannelMap *map;
 
1729
                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
 
1730
                stream = gvc_mixer_sink_input_new (control->priv->pa_context,
 
1731
                                                   info->index,
 
1732
                                                   map);
 
1733
                g_object_unref (map);
 
1734
                is_new = TRUE;
 
1735
        } else if (gvc_mixer_stream_is_running (stream)) {
 
1736
                /* Ignore events if volume changes are outstanding */
 
1737
                g_debug ("Ignoring event, volume changes are outstanding");
 
1738
                return;
 
1739
        }
 
1740
 
 
1741
        max_volume = pa_cvolume_max (&info->volume);
 
1742
 
 
1743
        name = (const char *)g_hash_table_lookup (control->priv->clients,
 
1744
                                                  GUINT_TO_POINTER (info->client));
 
1745
        gvc_mixer_stream_set_name (stream, name);
 
1746
        gvc_mixer_stream_set_description (stream, info->name);
 
1747
 
 
1748
        set_application_id_from_proplist (stream, info->proplist);
 
1749
        set_is_event_stream_from_proplist (stream, info->proplist);
 
1750
        set_icon_name_from_proplist (stream, info->proplist, "applications-multimedia");
 
1751
        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
 
1752
        gvc_mixer_stream_set_is_muted (stream, info->mute);
 
1753
        gvc_mixer_stream_set_is_virtual (stream, info->client == PA_INVALID_INDEX);
 
1754
 
 
1755
        if (is_new) {
 
1756
                g_hash_table_insert (control->priv->sink_inputs,
 
1757
                                     GUINT_TO_POINTER (info->index),
 
1758
                                     g_object_ref (stream));
 
1759
                add_stream (control, stream);
 
1760
        }
 
1761
}
 
1762
 
 
1763
static void
 
1764
update_source_output (GvcMixerControl             *control,
 
1765
                      const pa_source_output_info *info)
 
1766
{
 
1767
        GvcMixerStream *stream;
 
1768
        gboolean        is_new;
 
1769
        const char     *name;
 
1770
 
 
1771
#if 1
 
1772
        g_debug ("Updating source output: index=%u name='%s' client=%u source=%u",
 
1773
                 info->index,
 
1774
                 info->name,
 
1775
                 info->client,
 
1776
                 info->source);
 
1777
#endif
 
1778
 
 
1779
        is_new = FALSE;
 
1780
        stream = g_hash_table_lookup (control->priv->source_outputs,
 
1781
                                      GUINT_TO_POINTER (info->index));
 
1782
        if (stream == NULL) {
 
1783
                GvcChannelMap *map;
 
1784
                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
 
1785
                stream = gvc_mixer_source_output_new (control->priv->pa_context,
 
1786
                                                      info->index,
 
1787
                                                      map);
 
1788
                g_object_unref (map);
 
1789
                is_new = TRUE;
 
1790
        }
 
1791
 
 
1792
        name = (const char *)g_hash_table_lookup (control->priv->clients,
 
1793
                                                  GUINT_TO_POINTER (info->client));
 
1794
 
 
1795
        gvc_mixer_stream_set_name (stream, name);
 
1796
        gvc_mixer_stream_set_description (stream, info->name);
 
1797
        set_application_id_from_proplist (stream, info->proplist);
 
1798
        set_is_event_stream_from_proplist (stream, info->proplist);
 
1799
        set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
 
1800
 
 
1801
        if (is_new) {
 
1802
                g_hash_table_insert (control->priv->source_outputs,
 
1803
                                     GUINT_TO_POINTER (info->index),
 
1804
                                     g_object_ref (stream));
 
1805
                add_stream (control, stream);
 
1806
        }
 
1807
}
 
1808
 
 
1809
static void
 
1810
update_client (GvcMixerControl      *control,
 
1811
               const pa_client_info *info)
 
1812
{
 
1813
#if 1
 
1814
        g_debug ("Updating client: index=%u name='%s'",
 
1815
                 info->index,
 
1816
                 info->name);
 
1817
#endif
 
1818
        g_hash_table_insert (control->priv->clients,
 
1819
                             GUINT_TO_POINTER (info->index),
 
1820
                             g_strdup (info->name));
 
1821
}
 
1822
 
 
1823
static char *
 
1824
card_num_streams_to_status (guint sinks,
 
1825
                            guint sources)
 
1826
{
 
1827
        char *sinks_str;
 
1828
        char *sources_str;
 
1829
        char *ret;
 
1830
 
 
1831
        if (sinks == 0 && sources == 0) {
 
1832
                /* translators:
 
1833
                 * The device has been disabled */
 
1834
                return g_strdup (_("Disabled"));
 
1835
        }
 
1836
        if (sinks == 0) {
 
1837
                sinks_str = NULL;
 
1838
        } else {
 
1839
                /* translators:
 
1840
                 * The number of sound outputs on a particular device */
 
1841
                sinks_str = g_strdup_printf (ngettext ("%u Output",
 
1842
                                                       "%u Outputs",
 
1843
                                                       sinks),
 
1844
                                             sinks);
 
1845
        }
 
1846
        if (sources == 0) {
 
1847
                sources_str = NULL;
 
1848
        } else {
 
1849
                /* translators:
 
1850
                 * The number of sound inputs on a particular device */
 
1851
                sources_str = g_strdup_printf (ngettext ("%u Input",
 
1852
                                                         "%u Inputs",
 
1853
                                                         sources),
 
1854
                                               sources);
 
1855
        }
 
1856
        if (sources_str == NULL)
 
1857
                return sinks_str;
 
1858
        if (sinks_str == NULL)
 
1859
                return sources_str;
 
1860
        ret = g_strdup_printf ("%s / %s", sinks_str, sources_str);
 
1861
        g_free (sinks_str);
 
1862
        g_free (sources_str);
 
1863
        return ret;
 
1864
}
 
1865
 
 
1866
/**
 
1867
 * A utility method to gather which card profiles are relevant to the port .
 
1868
 */
 
1869
static GList *
 
1870
determine_profiles_for_port (pa_card_port_info *port,
 
1871
                             GList* card_profiles)
 
1872
{
 
1873
        gint i;
 
1874
        GList *supported_profiles = NULL;
 
1875
        GList *p;
 
1876
        for (i = 0; i < port->n_profiles; i++) {
 
1877
                for (p = card_profiles; p != NULL; p = p->next) {
 
1878
                        GvcMixerCardProfile *prof;
 
1879
                        prof = p->data;
 
1880
                        if (g_strcmp0 (port->profiles[i]->name, prof->profile) == 0)
 
1881
                                supported_profiles = g_list_append (supported_profiles, prof);
 
1882
                }
 
1883
        }
 
1884
        g_debug ("%i profiles supported on port %s",
 
1885
                 g_list_length (supported_profiles),
 
1886
                 port->description);
 
1887
        return g_list_sort (supported_profiles, (GCompareFunc) gvc_mixer_card_profile_compare);
 
1888
}
 
1889
 
 
1890
static gboolean
 
1891
is_card_port_an_output (GvcMixerCardPort* port)
 
1892
{
 
1893
        return port->direction == PA_DIRECTION_OUTPUT ? TRUE : FALSE;
 
1894
}
 
1895
 
 
1896
/*
 
1897
 * This method will create a ui device for the given port.
 
1898
 */
 
1899
static void
 
1900
create_ui_device_from_port (GvcMixerControl* control,
 
1901
                            GvcMixerCardPort* port,
 
1902
                            GvcMixerCard* card)
 
1903
{
 
1904
        GvcMixerUIDeviceDirection  direction;
 
1905
        GObject                   *object;
 
1906
        GvcMixerUIDevice          *uidevice;
 
1907
        gboolean                   available = port->available != PA_PORT_AVAILABLE_NO;
 
1908
 
 
1909
        direction = (is_card_port_an_output (port) == TRUE) ? UIDeviceOutput : UIDeviceInput;
 
1910
 
 
1911
        object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
 
1912
                               "type", (uint)direction,
 
1913
                               "card", card,
 
1914
                               "port-name", port->port,
 
1915
                               "description", port->human_port,
 
1916
                               "origin", gvc_mixer_card_get_name (card),
 
1917
                               "port-available", available,
 
1918
                               NULL);
 
1919
 
 
1920
        uidevice = GVC_MIXER_UI_DEVICE (object);
 
1921
        gvc_mixer_ui_device_set_profiles (uidevice, port->profiles);
 
1922
 
 
1923
        g_hash_table_insert (is_card_port_an_output (port) ? control->priv->ui_outputs : control->priv->ui_inputs,
 
1924
                             GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (uidevice)),
 
1925
                             g_object_ref (uidevice));
 
1926
 
 
1927
 
 
1928
        if (available) {
 
1929
                g_signal_emit (G_OBJECT (control),
 
1930
                               signals[is_card_port_an_output (port) ? OUTPUT_ADDED : INPUT_ADDED],
 
1931
                               0,
 
1932
                               gvc_mixer_ui_device_get_id (uidevice));
 
1933
        }
 
1934
 
 
1935
        g_debug ("create_ui_device_from_port, direction %u, description '%s', origin '%s', port available %i", 
 
1936
                 direction,
 
1937
                 port->human_port,
 
1938
                 gvc_mixer_card_get_name (card),
 
1939
                 available);
 
1940
}
 
1941
 
 
1942
/*
 
1943
 * This method will match up GvcMixerCardPorts with existing devices.
 
1944
 * A match is achieved if the device's card-id and the port's card-id are the same
 
1945
 * && the device's port-name and the card-port's port member are the same.
 
1946
 * A signal is then sent adding or removing that device from the UI depending on the availability of the port.
 
1947
 */
 
1948
static void
 
1949
match_card_port_with_existing_device (GvcMixerControl   *control,
 
1950
                                      GvcMixerCardPort  *card_port,
 
1951
                                      GvcMixerCard      *card,
 
1952
                                      gboolean           available)
 
1953
{
 
1954
        GList                   *d;
 
1955
        GList                   *devices;
 
1956
        GvcMixerUIDevice        *device;
 
1957
        gboolean                 is_output = is_card_port_an_output (card_port);
 
1958
 
 
1959
        devices  = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
 
1960
 
 
1961
        for (d = devices; d != NULL; d = d->next) {
 
1962
                GvcMixerCard *device_card;
 
1963
                gchar        *device_port_name;
 
1964
 
 
1965
                device = d->data;
 
1966
                g_object_get (G_OBJECT (device),
 
1967
                             "card", &device_card,
 
1968
                             "port-name", &device_port_name,
 
1969
                              NULL);
 
1970
 
 
1971
                if (g_strcmp0 (card_port->port, device_port_name) == 0 &&
 
1972
                        device_card == card) {
 
1973
                        g_debug ("Found the relevant device %s, update its port availability flag to %i, is_output %i",
 
1974
                                 device_port_name,
 
1975
                                 available,
 
1976
                                 is_output);
 
1977
                        g_object_set (G_OBJECT (device),
 
1978
                                      "port-available", available, NULL);
 
1979
                        g_signal_emit (G_OBJECT (control),
 
1980
                                       is_output ? signals[available ? OUTPUT_ADDED : OUTPUT_REMOVED] : signals[available ? INPUT_ADDED : INPUT_REMOVED],
 
1981
                                       0,
 
1982
                                       gvc_mixer_ui_device_get_id (device));
 
1983
               }
 
1984
               g_free (device_port_name);
 
1985
        }
 
1986
 
 
1987
        g_list_free (devices);
 
1988
}
 
1989
 
 
1990
static void
 
1991
create_ui_device_from_card (GvcMixerControl *control,
 
1992
                            GvcMixerCard    *card)
 
1993
{
 
1994
        GObject          *object;
 
1995
        GvcMixerUIDevice *in;
 
1996
        GvcMixerUIDevice *out;
 
1997
        const GList      *profiles;
 
1998
 
 
1999
        /* For now just create two devices and presume this device is multi directional
 
2000
         * Ensure to remove both on card removal (available to false by default) */
 
2001
        profiles = gvc_mixer_card_get_profiles (card);
 
2002
 
 
2003
        g_debug ("Portless card just registered - %i", gvc_mixer_card_get_index (card));
 
2004
 
 
2005
        object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
 
2006
                               "type", UIDeviceInput,
 
2007
                               "description", gvc_mixer_card_get_name (card),
 
2008
                               "origin", "", /* Leave it empty for these special cases */
 
2009
                               "port-name", NULL,
 
2010
                               "port-available", FALSE,
 
2011
                               "card", card,
 
2012
                               NULL);
 
2013
        in = GVC_MIXER_UI_DEVICE (object);
 
2014
        gvc_mixer_ui_device_set_profiles (in, profiles);
 
2015
 
 
2016
        g_hash_table_insert (control->priv->ui_inputs,
 
2017
                             GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (in)),
 
2018
                             g_object_ref (in));
 
2019
        object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
 
2020
                               "type", UIDeviceOutput,
 
2021
                               "description", gvc_mixer_card_get_name (card),
 
2022
                               "origin", "", /* Leave it empty for these special cases */
 
2023
                               "port-name", NULL,
 
2024
                               "port-available", FALSE,
 
2025
                               "card", card,
 
2026
                               NULL);
 
2027
        out = GVC_MIXER_UI_DEVICE (object);
 
2028
        gvc_mixer_ui_device_set_profiles (out, profiles);
 
2029
 
 
2030
        g_hash_table_insert (control->priv->ui_outputs,
 
2031
                             GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (out)),
 
2032
                             g_object_ref (out));
 
2033
}
 
2034
 
 
2035
/*
 
2036
 * At this point we can determine all devices available to us (besides network 'ports')
 
2037
 * This is done by the following:
 
2038
 *
 
2039
 * - gvc_mixer_card and gvc_mixer_card_ports are created and relevant setters are called.
 
2040
 * - First it checks to see if it's a portless card. Bluetooth devices are portless AFAIHS.
 
2041
 *        If so it creates two devices, an input and an output.
 
2042
 * - If it's a 'normal' card with ports it will create a new ui-device or
 
2043
 *   synchronise port availability with the existing device cached for that port on this card. */
 
2044
 
 
2045
static void
 
2046
update_card (GvcMixerControl      *control,
 
2047
             const pa_card_info   *info)
 
2048
{
 
2049
        const GList  *card_ports = NULL;
 
2050
        const GList  *m = NULL;
 
2051
        GvcMixerCard *card;
 
2052
        gboolean      is_new = FALSE;
 
2053
#if 1
 
2054
        guint i;
 
2055
        const char *key;
 
2056
        void *state;
 
2057
 
 
2058
        g_debug ("Udpating card %s (index: %u driver: %s):",
 
2059
                 info->name, info->index, info->driver);
 
2060
 
 
2061
        for (i = 0; i < info->n_profiles; i++) {
 
2062
                struct pa_card_profile_info pi = info->profiles[i];
 
2063
                gboolean is_default;
 
2064
 
 
2065
                is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0);
 
2066
                g_debug ("\tProfile '%s': %d sources %d sinks%s",
 
2067
                         pi.name, pi.n_sources, pi.n_sinks,
 
2068
                         is_default ? " (Current)" : "");
 
2069
        }
 
2070
        state = NULL;
 
2071
        key = pa_proplist_iterate (info->proplist, &state);
 
2072
        while (key != NULL) {
 
2073
                g_debug ("\tProperty: '%s' = '%s'",
 
2074
                        key, pa_proplist_gets (info->proplist, key));
 
2075
                key = pa_proplist_iterate (info->proplist, &state);
 
2076
        }
 
2077
#endif
 
2078
        card = g_hash_table_lookup (control->priv->cards,
 
2079
                                    GUINT_TO_POINTER (info->index));
 
2080
        if (card == NULL) {
 
2081
                GList *profile_list = NULL;
 
2082
                GList *port_list = NULL;
 
2083
 
 
2084
                for (i = 0; i < info->n_profiles; i++) {
 
2085
                        GvcMixerCardProfile *profile;
 
2086
                        struct pa_card_profile_info pi = info->profiles[i];
 
2087
 
 
2088
                        profile = g_new0 (GvcMixerCardProfile, 1);
 
2089
                        profile->profile = g_strdup (pi.name);
 
2090
                        profile->human_profile = g_strdup (pi.description);
 
2091
                        profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources);
 
2092
                        profile->n_sinks = pi.n_sinks;
 
2093
                        profile->n_sources = pi.n_sources;
 
2094
                        profile->priority = pi.priority;
 
2095
                        profile_list = g_list_prepend (profile_list, profile);
 
2096
                }
 
2097
                card = gvc_mixer_card_new (control->priv->pa_context,
 
2098
                                           info->index);
 
2099
                gvc_mixer_card_set_profiles (card, profile_list);
 
2100
 
 
2101
                for (i = 0; i < info->n_ports; i++) {
 
2102
                        GvcMixerCardPort *port;
 
2103
                        port = g_new0 (GvcMixerCardPort, 1);
 
2104
                        port->port = g_strdup (info->ports[i]->name);
 
2105
                        port->human_port = g_strdup (info->ports[i]->description);
 
2106
                        port->priority = info->ports[i]->priority;
 
2107
                        port->available = info->ports[i]->available;
 
2108
                        port->direction = info->ports[i]->direction;
 
2109
                        port->profiles = determine_profiles_for_port (info->ports[i], profile_list);
 
2110
                        port_list = g_list_prepend (port_list, port);
 
2111
                }
 
2112
                gvc_mixer_card_set_ports (card, port_list);
 
2113
                is_new = TRUE;
 
2114
        }
 
2115
 
 
2116
        gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description"));
 
2117
        gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name"));
 
2118
        gvc_mixer_card_set_profile (card, info->active_profile->name);
 
2119
 
 
2120
        if (is_new) {
 
2121
                g_hash_table_insert (control->priv->cards,
 
2122
                                     GUINT_TO_POINTER (info->index),
 
2123
                                     g_object_ref (card));
 
2124
        }
 
2125
 
 
2126
        card_ports = gvc_mixer_card_get_ports (card);
 
2127
 
 
2128
        if (card_ports == NULL && is_new) {
 
2129
                g_debug ("Portless card just registered - %s", gvc_mixer_card_get_name (card));
 
2130
                create_ui_device_from_card (control, card);
 
2131
        }
 
2132
 
 
2133
        for (m = card_ports; m != NULL; m = m->next) {
 
2134
                GvcMixerCardPort *card_port;
 
2135
                card_port = m->data;
 
2136
                if (is_new)
 
2137
                        create_ui_device_from_port (control, card_port, card);
 
2138
                else {
 
2139
                        for (i = 0; i < info->n_ports; i++) {
 
2140
                                if (g_strcmp0 (card_port->port, info->ports[i]->name) == 0) {
 
2141
                                        if ((card_port->available == PA_PORT_AVAILABLE_NO) !=  (info->ports[i]->available == PA_PORT_AVAILABLE_NO)) {
 
2142
                                                card_port->available = info->ports[i]->available;
 
2143
                                                g_debug ("sync port availability on card %i, card port name '%s', new available value %i",
 
2144
                                                          gvc_mixer_card_get_index (card),
 
2145
                                                          card_port->port,
 
2146
                                                          card_port->available);
 
2147
                                                match_card_port_with_existing_device (control,
 
2148
                                                                                      card_port,
 
2149
                                                                                      card,
 
2150
                                                                                      card_port->available != PA_PORT_AVAILABLE_NO);
 
2151
                                        }
 
2152
                                }
 
2153
                        }
 
2154
                }
 
2155
        }
 
2156
        g_signal_emit (G_OBJECT (control),
 
2157
                       signals[CARD_ADDED],
 
2158
                       0,
 
2159
                       info->index);
 
2160
}
 
2161
 
 
2162
static void
 
2163
_pa_context_get_sink_info_cb (pa_context         *context,
 
2164
                              const pa_sink_info *i,
 
2165
                              int                 eol,
 
2166
                              void               *userdata)
 
2167
{
 
2168
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2169
 
 
2170
        if (eol < 0) {
 
2171
                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 
2172
                        return;
 
2173
                }
 
2174
 
 
2175
                g_warning ("Sink callback failure");
 
2176
                return;
 
2177
        }
 
2178
 
 
2179
        if (eol > 0) {
 
2180
                dec_outstanding (control);
 
2181
                return;
 
2182
        }
 
2183
 
 
2184
        update_sink (control, i);
 
2185
}
 
2186
 
 
2187
static void
 
2188
_pa_context_get_source_info_cb (pa_context           *context,
 
2189
                                const pa_source_info *i,
 
2190
                                int                   eol,
 
2191
                                void                 *userdata)
 
2192
{
 
2193
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2194
 
 
2195
        if (eol < 0) {
 
2196
                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 
2197
                        return;
 
2198
                }
 
2199
 
 
2200
                g_warning ("Source callback failure");
 
2201
                return;
 
2202
        }
 
2203
 
 
2204
        if (eol > 0) {
 
2205
                dec_outstanding (control);
 
2206
                return;
 
2207
        }
 
2208
 
 
2209
        update_source (control, i);
 
2210
}
 
2211
 
 
2212
static void
 
2213
_pa_context_get_sink_input_info_cb (pa_context               *context,
 
2214
                                    const pa_sink_input_info *i,
 
2215
                                    int                       eol,
 
2216
                                    void                     *userdata)
 
2217
{
 
2218
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2219
 
 
2220
        if (eol < 0) {
 
2221
                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 
2222
                        return;
 
2223
                }
 
2224
 
 
2225
                g_warning ("Sink input callback failure");
 
2226
                return;
 
2227
        }
 
2228
 
 
2229
        if (eol > 0) {
 
2230
                dec_outstanding (control);
 
2231
                return;
 
2232
        }
 
2233
 
 
2234
        update_sink_input (control, i);
 
2235
}
 
2236
 
 
2237
static void
 
2238
_pa_context_get_source_output_info_cb (pa_context                  *context,
 
2239
                                       const pa_source_output_info *i,
 
2240
                                       int                          eol,
 
2241
                                       void                        *userdata)
 
2242
{
 
2243
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2244
 
 
2245
        if (eol < 0) {
 
2246
                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 
2247
                        return;
 
2248
                }
 
2249
 
 
2250
                g_warning ("Source output callback failure");
 
2251
                return;
 
2252
        }
 
2253
 
 
2254
        if (eol > 0)  {
 
2255
                dec_outstanding (control);
 
2256
                return;
 
2257
        }
 
2258
 
 
2259
        update_source_output (control, i);
 
2260
}
 
2261
 
 
2262
static void
 
2263
_pa_context_get_client_info_cb (pa_context           *context,
 
2264
                                const pa_client_info *i,
 
2265
                                int                   eol,
 
2266
                                void                 *userdata)
 
2267
{
 
2268
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2269
 
 
2270
        if (eol < 0) {
 
2271
                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 
2272
                        return;
 
2273
                }
 
2274
 
 
2275
                g_warning ("Client callback failure");
 
2276
                return;
 
2277
        }
 
2278
 
 
2279
        if (eol > 0) {
 
2280
                dec_outstanding (control);
 
2281
                return;
 
2282
        }
 
2283
 
 
2284
        update_client (control, i);
 
2285
}
 
2286
 
 
2287
static void
 
2288
_pa_context_get_card_info_by_index_cb (pa_context *context,
 
2289
                                       const pa_card_info *i,
 
2290
                                       int eol,
 
2291
                                       void *userdata)
 
2292
{
 
2293
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2294
 
 
2295
        if (eol < 0) {
 
2296
                if (pa_context_errno (context) == PA_ERR_NOENTITY)
 
2297
                        return;
 
2298
 
 
2299
                g_warning ("Card callback failure");
 
2300
                return;
 
2301
        }
 
2302
 
 
2303
        if (eol > 0) {
 
2304
                dec_outstanding (control);
 
2305
                return;
 
2306
        }
 
2307
 
 
2308
        update_card (control, i);
 
2309
}
 
2310
 
 
2311
static void
 
2312
_pa_context_get_server_info_cb (pa_context           *context,
 
2313
                                const pa_server_info *i,
 
2314
                                void                 *userdata)
 
2315
{
 
2316
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2317
 
 
2318
        if (i == NULL) {
 
2319
                g_warning ("Server info callback failure");
 
2320
                return;
 
2321
        }
 
2322
        g_debug ("get server info");
 
2323
        update_server (control, i);
 
2324
        dec_outstanding (control);
 
2325
}
 
2326
 
 
2327
static void
 
2328
remove_event_role_stream (GvcMixerControl *control)
 
2329
{
 
2330
        g_debug ("Removing event role");
 
2331
}
 
2332
 
 
2333
static void
 
2334
update_event_role_stream (GvcMixerControl                  *control,
 
2335
                          const pa_ext_stream_restore_info *info)
 
2336
{
 
2337
        GvcMixerStream *stream;
 
2338
        gboolean        is_new;
 
2339
        pa_volume_t     max_volume;
 
2340
 
 
2341
        if (strcmp (info->name, "sink-input-by-media-role:event") != 0) {
 
2342
                return;
 
2343
        }
 
2344
 
 
2345
#if 0
 
2346
        g_debug ("Updating event role: name='%s' device='%s'",
 
2347
                 info->name,
 
2348
                 info->device);
 
2349
#endif
 
2350
 
 
2351
        is_new = FALSE;
 
2352
 
 
2353
        if (!control->priv->event_sink_input_is_set) {
 
2354
                pa_channel_map pa_map;
 
2355
                GvcChannelMap *map;
 
2356
 
 
2357
                pa_map.channels = 1;
 
2358
                pa_map.map[0] = PA_CHANNEL_POSITION_MONO;
 
2359
                map = gvc_channel_map_new_from_pa_channel_map (&pa_map);
 
2360
 
 
2361
                stream = gvc_mixer_event_role_new (control->priv->pa_context,
 
2362
                                                   info->device,
 
2363
                                                   map);
 
2364
                control->priv->event_sink_input_id = gvc_mixer_stream_get_id (stream);
 
2365
                control->priv->event_sink_input_is_set = TRUE;
 
2366
 
 
2367
                is_new = TRUE;
 
2368
        } else {
 
2369
                stream = g_hash_table_lookup (control->priv->all_streams,
 
2370
                                              GUINT_TO_POINTER (control->priv->event_sink_input_id));
 
2371
        }
 
2372
 
 
2373
        max_volume = pa_cvolume_max (&info->volume);
 
2374
 
 
2375
        gvc_mixer_stream_set_name (stream, _("System Sounds"));
 
2376
        gvc_mixer_stream_set_icon_name (stream, "unity-sound-panel");
 
2377
        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
 
2378
        gvc_mixer_stream_set_is_muted (stream, info->mute);
 
2379
 
 
2380
        if (is_new) {
 
2381
                add_stream (control, stream);
 
2382
        }
 
2383
}
 
2384
 
 
2385
static void
 
2386
_pa_ext_stream_restore_read_cb (pa_context                       *context,
 
2387
                                const pa_ext_stream_restore_info *i,
 
2388
                                int                               eol,
 
2389
                                void                             *userdata)
 
2390
{
 
2391
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2392
 
 
2393
        if (eol < 0) {
 
2394
                g_debug ("Failed to initialized stream_restore extension: %s",
 
2395
                         pa_strerror (pa_context_errno (context)));
 
2396
                remove_event_role_stream (control);
 
2397
                return;
 
2398
        }
 
2399
 
 
2400
        if (eol > 0) {
 
2401
                dec_outstanding (control);
 
2402
                /* If we don't have an event stream to restore, then
 
2403
                 * set one up with a default 100% volume */
 
2404
                if (!control->priv->event_sink_input_is_set) {
 
2405
                        pa_ext_stream_restore_info info;
 
2406
 
 
2407
                        memset (&info, 0, sizeof(info));
 
2408
                        info.name = "sink-input-by-media-role:event";
 
2409
                        info.volume.channels = 1;
 
2410
                        info.volume.values[0] = PA_VOLUME_NORM;
 
2411
                        update_event_role_stream (control, &info);
 
2412
                }
 
2413
                return;
 
2414
        }
 
2415
 
 
2416
        update_event_role_stream (control, i);
 
2417
}
 
2418
 
 
2419
static void
 
2420
_pa_ext_stream_restore_subscribe_cb (pa_context *context,
 
2421
                                     void       *userdata)
 
2422
{
 
2423
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2424
        pa_operation    *o;
 
2425
 
 
2426
        o = pa_ext_stream_restore_read (context,
 
2427
                                        _pa_ext_stream_restore_read_cb,
 
2428
                                        control);
 
2429
        if (o == NULL) {
 
2430
                g_warning ("pa_ext_stream_restore_read() failed");
 
2431
                return;
 
2432
        }
 
2433
 
 
2434
        pa_operation_unref (o);
 
2435
}
 
2436
 
 
2437
static void
 
2438
req_update_server_info (GvcMixerControl *control,
 
2439
                        int              index)
 
2440
{
 
2441
        pa_operation *o;
 
2442
 
 
2443
        o = pa_context_get_server_info (control->priv->pa_context,
 
2444
                                        _pa_context_get_server_info_cb,
 
2445
                                        control);
 
2446
        if (o == NULL) {
 
2447
                g_warning ("pa_context_get_server_info() failed");
 
2448
                return;
 
2449
        }
 
2450
        pa_operation_unref (o);
 
2451
}
 
2452
 
 
2453
static void
 
2454
req_update_client_info (GvcMixerControl *control,
 
2455
                        int              index)
 
2456
{
 
2457
        pa_operation *o;
 
2458
 
 
2459
        if (index < 0) {
 
2460
                o = pa_context_get_client_info_list (control->priv->pa_context,
 
2461
                                                     _pa_context_get_client_info_cb,
 
2462
                                                     control);
 
2463
        } else {
 
2464
                o = pa_context_get_client_info (control->priv->pa_context,
 
2465
                                                index,
 
2466
                                                _pa_context_get_client_info_cb,
 
2467
                                                control);
 
2468
        }
 
2469
 
 
2470
        if (o == NULL) {
 
2471
                g_warning ("pa_context_client_info_list() failed");
 
2472
                return;
 
2473
        }
 
2474
        pa_operation_unref (o);
 
2475
}
 
2476
 
 
2477
static void
 
2478
req_update_card (GvcMixerControl *control,
 
2479
                 int              index)
 
2480
{
 
2481
        pa_operation *o;
 
2482
 
 
2483
        if (index < 0) {
 
2484
                o = pa_context_get_card_info_list (control->priv->pa_context,
 
2485
                                                   _pa_context_get_card_info_by_index_cb,
 
2486
                                                   control);
 
2487
        } else {
 
2488
                o = pa_context_get_card_info_by_index (control->priv->pa_context,
 
2489
                                                       index,
 
2490
                                                       _pa_context_get_card_info_by_index_cb,
 
2491
                                                       control);
 
2492
        }
 
2493
 
 
2494
        if (o == NULL) {
 
2495
                g_warning ("pa_context_get_card_info_by_index() failed");
 
2496
                return;
 
2497
        }
 
2498
        pa_operation_unref (o);
 
2499
}
 
2500
 
 
2501
static void
 
2502
req_update_sink_info (GvcMixerControl *control,
 
2503
                      int              index)
 
2504
{
 
2505
        pa_operation *o;
 
2506
 
 
2507
        if (index < 0) {
 
2508
                o = pa_context_get_sink_info_list (control->priv->pa_context,
 
2509
                                                   _pa_context_get_sink_info_cb,
 
2510
                                                   control);
 
2511
        } else {
 
2512
                o = pa_context_get_sink_info_by_index (control->priv->pa_context,
 
2513
                                                       index,
 
2514
                                                       _pa_context_get_sink_info_cb,
 
2515
                                                       control);
 
2516
        }
 
2517
 
 
2518
        if (o == NULL) {
 
2519
                g_warning ("pa_context_get_sink_info_list() failed");
 
2520
                return;
 
2521
        }
 
2522
        pa_operation_unref (o);
 
2523
}
 
2524
 
 
2525
static void
 
2526
req_update_source_info (GvcMixerControl *control,
 
2527
                        int              index)
 
2528
{
 
2529
        pa_operation *o;
 
2530
 
 
2531
        if (index < 0) {
 
2532
                o = pa_context_get_source_info_list (control->priv->pa_context,
 
2533
                                                     _pa_context_get_source_info_cb,
 
2534
                                                     control);
 
2535
        } else {
 
2536
                o = pa_context_get_source_info_by_index(control->priv->pa_context,
 
2537
                                                        index,
 
2538
                                                        _pa_context_get_source_info_cb,
 
2539
                                                        control);
 
2540
        }
 
2541
 
 
2542
        if (o == NULL) {
 
2543
                g_warning ("pa_context_get_source_info_list() failed");
 
2544
                return;
 
2545
        }
 
2546
        pa_operation_unref (o);
 
2547
}
 
2548
 
 
2549
static void
 
2550
req_update_sink_input_info (GvcMixerControl *control,
 
2551
                            int              index)
 
2552
{
 
2553
        pa_operation *o;
 
2554
 
 
2555
        if (index < 0) {
 
2556
                o = pa_context_get_sink_input_info_list (control->priv->pa_context,
 
2557
                                                         _pa_context_get_sink_input_info_cb,
 
2558
                                                         control);
 
2559
        } else {
 
2560
                o = pa_context_get_sink_input_info (control->priv->pa_context,
 
2561
                                                    index,
 
2562
                                                    _pa_context_get_sink_input_info_cb,
 
2563
                                                    control);
 
2564
        }
 
2565
 
 
2566
        if (o == NULL) {
 
2567
                g_warning ("pa_context_get_sink_input_info_list() failed");
 
2568
                return;
 
2569
        }
 
2570
        pa_operation_unref (o);
 
2571
}
 
2572
 
 
2573
static void
 
2574
req_update_source_output_info (GvcMixerControl *control,
 
2575
                               int              index)
 
2576
{
 
2577
        pa_operation *o;
 
2578
 
 
2579
        if (index < 0) {
 
2580
                o = pa_context_get_source_output_info_list (control->priv->pa_context,
 
2581
                                                            _pa_context_get_source_output_info_cb,
 
2582
                                                            control);
 
2583
        } else {
 
2584
                o = pa_context_get_source_output_info (control->priv->pa_context,
 
2585
                                                       index,
 
2586
                                                       _pa_context_get_source_output_info_cb,
 
2587
                                                       control);
 
2588
        }
 
2589
 
 
2590
        if (o == NULL) {
 
2591
                g_warning ("pa_context_get_source_output_info_list() failed");
 
2592
                return;
 
2593
        }
 
2594
        pa_operation_unref (o);
 
2595
}
 
2596
 
 
2597
static void
 
2598
remove_client (GvcMixerControl *control,
 
2599
               guint            index)
 
2600
{
 
2601
        g_hash_table_remove (control->priv->clients,
 
2602
                             GUINT_TO_POINTER (index));
 
2603
}
 
2604
 
 
2605
static void
 
2606
remove_card (GvcMixerControl *control,
 
2607
             guint            index)
 
2608
{
 
2609
 
 
2610
        GList *devices, *d;
 
2611
 
 
2612
        devices = g_list_concat (g_hash_table_get_values (control->priv->ui_inputs),
 
2613
                                 g_hash_table_get_values (control->priv->ui_outputs));
 
2614
 
 
2615
        for (d = devices; d != NULL; d = d->next) {
 
2616
                GvcMixerCard *card;
 
2617
                GvcMixerUIDevice *device = d->data;
 
2618
 
 
2619
                g_object_get (G_OBJECT (device), "card", &card, NULL);
 
2620
 
 
2621
                if (gvc_mixer_card_get_index (card) == index) {
 
2622
                        g_signal_emit (G_OBJECT (control),
 
2623
                                       signals[gvc_mixer_ui_device_is_output (device) ? OUTPUT_REMOVED : INPUT_REMOVED],
 
2624
                                       0,
 
2625
                                       gvc_mixer_ui_device_get_id (device));
 
2626
                        g_debug ("Card removal remove device %s",
 
2627
                                 gvc_mixer_ui_device_get_description (device));
 
2628
                        g_hash_table_remove (gvc_mixer_ui_device_is_output (device) ? control->priv->ui_outputs : control->priv->ui_inputs,
 
2629
                                             GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)));
 
2630
                }
 
2631
        }
 
2632
 
 
2633
        g_list_free (devices);
 
2634
 
 
2635
        g_hash_table_remove (control->priv->cards,
 
2636
                             GUINT_TO_POINTER (index));
 
2637
 
 
2638
        g_signal_emit (G_OBJECT (control),
 
2639
                       signals[CARD_REMOVED],
 
2640
                       0,
 
2641
                       index);
 
2642
}
 
2643
 
 
2644
static void
 
2645
remove_sink (GvcMixerControl *control,
 
2646
             guint            index)
 
2647
{
 
2648
        GvcMixerStream   *stream;
 
2649
        GvcMixerUIDevice *device;
 
2650
 
 
2651
        g_debug ("Removing sink: index=%u", index);
 
2652
 
 
2653
        stream = g_hash_table_lookup (control->priv->sinks,
 
2654
                                      GUINT_TO_POINTER (index));
 
2655
        if (stream == NULL)
 
2656
                return;
 
2657
 
 
2658
        device = gvc_mixer_control_lookup_device_from_stream (control, stream);
 
2659
 
 
2660
        if (device != NULL) {
 
2661
                gvc_mixer_ui_device_invalidate_stream (device);
 
2662
                if (!gvc_mixer_ui_device_has_ports (device)) {
 
2663
                        g_signal_emit (G_OBJECT (control),
 
2664
                                       signals[OUTPUT_REMOVED],
 
2665
                                       0,
 
2666
                                       gvc_mixer_ui_device_get_id (device));
 
2667
                } else {
 
2668
                        GList *devices, *d;
 
2669
 
 
2670
                        devices = g_hash_table_get_values (control->priv->ui_outputs);
 
2671
 
 
2672
                        for (d = devices; d != NULL; d = d->next) {
 
2673
                                gint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
 
2674
                                device = d->data;
 
2675
                                g_object_get (G_OBJECT (device),
 
2676
                                             "stream-id", &stream_id,
 
2677
                                              NULL);
 
2678
                                if (stream_id == gvc_mixer_stream_get_id (stream))
 
2679
                                        gvc_mixer_ui_device_invalidate_stream (device);
 
2680
                        }
 
2681
 
 
2682
                        g_list_free (devices);
 
2683
                }
 
2684
        }
 
2685
 
 
2686
        g_hash_table_remove (control->priv->sinks,
 
2687
                             GUINT_TO_POINTER (index));
 
2688
 
 
2689
        remove_stream (control, stream);
 
2690
}
 
2691
 
 
2692
static void
 
2693
remove_source (GvcMixerControl *control,
 
2694
               guint            index)
 
2695
{
 
2696
        GvcMixerStream   *stream;
 
2697
        GvcMixerUIDevice *device;
 
2698
 
 
2699
        g_debug ("Removing source: index=%u", index);
 
2700
 
 
2701
        stream = g_hash_table_lookup (control->priv->sources,
 
2702
                                      GUINT_TO_POINTER (index));
 
2703
        if (stream == NULL)
 
2704
                return;
 
2705
 
 
2706
        device = gvc_mixer_control_lookup_device_from_stream (control, stream);
 
2707
 
 
2708
        if (device != NULL) {
 
2709
                gvc_mixer_ui_device_invalidate_stream (device);
 
2710
                if (!gvc_mixer_ui_device_has_ports (device)) {
 
2711
                        g_signal_emit (G_OBJECT (control),
 
2712
                                       signals[INPUT_REMOVED],
 
2713
                                       0,
 
2714
                                       gvc_mixer_ui_device_get_id (device));
 
2715
                } else {
 
2716
                        GList *devices, *d;
 
2717
 
 
2718
                        devices = g_hash_table_get_values (control->priv->ui_inputs);
 
2719
 
 
2720
                        for (d = devices; d != NULL; d = d->next) {
 
2721
                                gint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
 
2722
                                device = d->data;
 
2723
                                g_object_get (G_OBJECT (device),
 
2724
                                             "stream-id", &stream_id,
 
2725
                                              NULL);
 
2726
                                if (stream_id == gvc_mixer_stream_get_id (stream))
 
2727
                                        gvc_mixer_ui_device_invalidate_stream (device);
 
2728
                        }
 
2729
 
 
2730
                        g_list_free (devices);
 
2731
                }
 
2732
        }
 
2733
 
 
2734
        g_hash_table_remove (control->priv->sources,
 
2735
                             GUINT_TO_POINTER (index));
 
2736
 
 
2737
        remove_stream (control, stream);
 
2738
}
 
2739
 
 
2740
static void
 
2741
remove_sink_input (GvcMixerControl *control,
 
2742
                   guint            index)
 
2743
{
 
2744
        GvcMixerStream *stream;
 
2745
 
 
2746
        g_debug ("Removing sink input: index=%u", index);
 
2747
 
 
2748
        stream = g_hash_table_lookup (control->priv->sink_inputs,
 
2749
                                      GUINT_TO_POINTER (index));
 
2750
        if (stream == NULL) {
 
2751
                return;
 
2752
        }
 
2753
        g_hash_table_remove (control->priv->sink_inputs,
 
2754
                             GUINT_TO_POINTER (index));
 
2755
 
 
2756
        remove_stream (control, stream);
 
2757
}
 
2758
 
 
2759
static void
 
2760
remove_source_output (GvcMixerControl *control,
 
2761
                      guint            index)
 
2762
{
 
2763
        GvcMixerStream *stream;
 
2764
 
 
2765
        g_debug ("Removing source output: index=%u", index);
 
2766
 
 
2767
        stream = g_hash_table_lookup (control->priv->source_outputs,
 
2768
                                      GUINT_TO_POINTER (index));
 
2769
        if (stream == NULL) {
 
2770
                return;
 
2771
        }
 
2772
        g_hash_table_remove (control->priv->source_outputs,
 
2773
                             GUINT_TO_POINTER (index));
 
2774
 
 
2775
        remove_stream (control, stream);
 
2776
}
 
2777
 
 
2778
static void
 
2779
_pa_context_subscribe_cb (pa_context                  *context,
 
2780
                          pa_subscription_event_type_t t,
 
2781
                          uint32_t                     index,
 
2782
                          void                        *userdata)
 
2783
{
 
2784
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2785
 
 
2786
        switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
 
2787
        case PA_SUBSCRIPTION_EVENT_SINK:
 
2788
                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
2789
                        remove_sink (control, index);
 
2790
                } else {
 
2791
                        req_update_sink_info (control, index);
 
2792
                }
 
2793
                break;
 
2794
 
 
2795
        case PA_SUBSCRIPTION_EVENT_SOURCE:
 
2796
                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
2797
                        remove_source (control, index);
 
2798
                } else {
 
2799
                        req_update_source_info (control, index);
 
2800
                }
 
2801
                break;
 
2802
 
 
2803
        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
 
2804
                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
2805
                        remove_sink_input (control, index);
 
2806
                } else {
 
2807
                        req_update_sink_input_info (control, index);
 
2808
                }
 
2809
                break;
 
2810
 
 
2811
        case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
 
2812
                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
2813
                        remove_source_output (control, index);
 
2814
                } else {
 
2815
                        req_update_source_output_info (control, index);
 
2816
                }
 
2817
                break;
 
2818
 
 
2819
        case PA_SUBSCRIPTION_EVENT_CLIENT:
 
2820
                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
2821
                        remove_client (control, index);
 
2822
                } else {
 
2823
                        req_update_client_info (control, index);
 
2824
                }
 
2825
                break;
 
2826
 
 
2827
        case PA_SUBSCRIPTION_EVENT_SERVER:
 
2828
                req_update_server_info (control, index);
 
2829
                break;
 
2830
 
 
2831
        case PA_SUBSCRIPTION_EVENT_CARD:
 
2832
                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
2833
                        remove_card (control, index);
 
2834
                } else {
 
2835
                        req_update_card (control, index);
 
2836
                }
 
2837
                break;
 
2838
        }
 
2839
}
 
2840
 
 
2841
static void
 
2842
gvc_mixer_control_ready (GvcMixerControl *control)
 
2843
{
 
2844
        pa_operation *o;
 
2845
 
 
2846
        pa_context_set_subscribe_callback (control->priv->pa_context,
 
2847
                                           _pa_context_subscribe_cb,
 
2848
                                           control);
 
2849
        o = pa_context_subscribe (control->priv->pa_context,
 
2850
                                  (pa_subscription_mask_t)
 
2851
                                  (PA_SUBSCRIPTION_MASK_SINK|
 
2852
                                   PA_SUBSCRIPTION_MASK_SOURCE|
 
2853
                                   PA_SUBSCRIPTION_MASK_SINK_INPUT|
 
2854
                                   PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
 
2855
                                   PA_SUBSCRIPTION_MASK_CLIENT|
 
2856
                                   PA_SUBSCRIPTION_MASK_SERVER|
 
2857
                                   PA_SUBSCRIPTION_MASK_CARD),
 
2858
                                  NULL,
 
2859
                                  NULL);
 
2860
 
 
2861
        if (o == NULL) {
 
2862
                g_warning ("pa_context_subscribe() failed");
 
2863
                return;
 
2864
        }
 
2865
        pa_operation_unref (o);
 
2866
 
 
2867
        req_update_server_info (control, -1);
 
2868
        req_update_card (control, -1);
 
2869
        req_update_client_info (control, -1);
 
2870
        req_update_sink_info (control, -1);
 
2871
        req_update_source_info (control, -1);
 
2872
        req_update_sink_input_info (control, -1);
 
2873
        req_update_source_output_info (control, -1);
 
2874
 
 
2875
 
 
2876
        control->priv->n_outstanding = 6;
 
2877
 
 
2878
        /* This call is not always supported */
 
2879
        o = pa_ext_stream_restore_read (control->priv->pa_context,
 
2880
                                        _pa_ext_stream_restore_read_cb,
 
2881
                                        control);
 
2882
        if (o != NULL) {
 
2883
                pa_operation_unref (o);
 
2884
                control->priv->n_outstanding++;
 
2885
 
 
2886
                pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context,
 
2887
                                                        _pa_ext_stream_restore_subscribe_cb,
 
2888
                                                        control);
 
2889
 
 
2890
                o = pa_ext_stream_restore_subscribe (control->priv->pa_context,
 
2891
                                                     1,
 
2892
                                                     NULL,
 
2893
                                                     NULL);
 
2894
                if (o != NULL) {
 
2895
                        pa_operation_unref (o);
 
2896
                }
 
2897
 
 
2898
        } else {
 
2899
                g_debug ("Failed to initialized stream_restore extension: %s",
 
2900
                         pa_strerror (pa_context_errno (control->priv->pa_context)));
 
2901
        }
 
2902
}
 
2903
 
 
2904
static void
 
2905
gvc_mixer_new_pa_context (GvcMixerControl *self)
 
2906
{
 
2907
        pa_proplist     *proplist;
 
2908
 
 
2909
        g_return_if_fail (self);
 
2910
        g_return_if_fail (!self->priv->pa_context);
 
2911
 
 
2912
        proplist = pa_proplist_new ();
 
2913
        pa_proplist_sets (proplist,
 
2914
                          PA_PROP_APPLICATION_NAME,
 
2915
                          self->priv->name);
 
2916
        pa_proplist_sets (proplist,
 
2917
                          PA_PROP_APPLICATION_ID,
 
2918
                          "org.gnome.VolumeControl");
 
2919
        pa_proplist_sets (proplist,
 
2920
                          PA_PROP_APPLICATION_ICON_NAME,
 
2921
                          "unity-sound-panel");
 
2922
        pa_proplist_sets (proplist,
 
2923
                          PA_PROP_APPLICATION_VERSION,
 
2924
                          PACKAGE_VERSION);
 
2925
 
 
2926
        self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist);
 
2927
 
 
2928
        pa_proplist_free (proplist);
 
2929
        g_assert (self->priv->pa_context);
 
2930
}
 
2931
 
 
2932
static void
 
2933
remove_all_streams (GvcMixerControl *control, GHashTable *hash_table)
 
2934
{
 
2935
        GHashTableIter iter;
 
2936
        gpointer key, value;
 
2937
 
 
2938
        g_hash_table_iter_init (&iter, hash_table);
 
2939
        while (g_hash_table_iter_next (&iter, &key, &value)) {
 
2940
                remove_stream (control, value);
 
2941
                g_hash_table_iter_remove (&iter);
 
2942
        }
 
2943
}
 
2944
 
 
2945
static gboolean
 
2946
idle_reconnect (gpointer data)
 
2947
{
 
2948
        GvcMixerControl *control = GVC_MIXER_CONTROL (data);
 
2949
        GHashTableIter iter;
 
2950
        gpointer key, value;
 
2951
 
 
2952
        g_return_val_if_fail (control, FALSE);
 
2953
 
 
2954
        if (control->priv->pa_context) {
 
2955
                pa_context_unref (control->priv->pa_context);
 
2956
                control->priv->pa_context = NULL;
 
2957
                gvc_mixer_new_pa_context (control);
 
2958
        }
 
2959
 
 
2960
        remove_all_streams (control, control->priv->sinks);
 
2961
        remove_all_streams (control, control->priv->sources);
 
2962
        remove_all_streams (control, control->priv->sink_inputs);
 
2963
        remove_all_streams (control, control->priv->source_outputs);
 
2964
 
 
2965
        g_hash_table_iter_init (&iter, control->priv->clients);
 
2966
        while (g_hash_table_iter_next (&iter, &key, &value))
 
2967
                g_hash_table_iter_remove (&iter);
 
2968
 
 
2969
        gvc_mixer_control_open (control); /* cannot fail */
 
2970
 
 
2971
        control->priv->reconnect_id = 0;
 
2972
        return FALSE;
 
2973
}
 
2974
 
 
2975
static void
 
2976
_pa_context_state_cb (pa_context *context,
 
2977
                      void       *userdata)
 
2978
{
 
2979
        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 
2980
 
 
2981
        switch (pa_context_get_state (context)) {
 
2982
        case PA_CONTEXT_UNCONNECTED:
 
2983
        case PA_CONTEXT_CONNECTING:
 
2984
        case PA_CONTEXT_AUTHORIZING:
 
2985
        case PA_CONTEXT_SETTING_NAME:
 
2986
                break;
 
2987
 
 
2988
        case PA_CONTEXT_READY:
 
2989
                gvc_mixer_control_ready (control);
 
2990
                break;
 
2991
 
 
2992
        case PA_CONTEXT_FAILED:
 
2993
                control->priv->state = GVC_STATE_FAILED;
 
2994
                g_signal_emit (control, signals[STATE_CHANGED], 0, GVC_STATE_FAILED);
 
2995
                if (control->priv->reconnect_id == 0)
 
2996
                        control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control);
 
2997
                break;
 
2998
 
 
2999
        case PA_CONTEXT_TERMINATED:
 
3000
        default:
 
3001
                /* FIXME: */
 
3002
                break;
 
3003
        }
 
3004
}
 
3005
 
 
3006
gboolean
 
3007
gvc_mixer_control_open (GvcMixerControl *control)
 
3008
{
 
3009
        int res;
 
3010
 
 
3011
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 
3012
        g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
 
3013
        g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE);
 
3014
 
 
3015
        pa_context_set_state_callback (control->priv->pa_context,
 
3016
                                       _pa_context_state_cb,
 
3017
                                       control);
 
3018
 
 
3019
        control->priv->state = GVC_STATE_CONNECTING;
 
3020
        g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CONNECTING);
 
3021
        res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, NULL);
 
3022
        if (res < 0) {
 
3023
                g_warning ("Failed to connect context: %s",
 
3024
                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 
3025
        }
 
3026
 
 
3027
        return res;
 
3028
}
 
3029
 
 
3030
gboolean
 
3031
gvc_mixer_control_close (GvcMixerControl *control)
 
3032
{
 
3033
        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 
3034
        g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
 
3035
 
 
3036
        pa_context_disconnect (control->priv->pa_context);
 
3037
 
 
3038
        control->priv->state = GVC_STATE_CLOSED;
 
3039
        g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CLOSED);
 
3040
        return TRUE;
 
3041
}
 
3042
 
 
3043
static void
 
3044
gvc_mixer_control_dispose (GObject *object)
 
3045
{
 
3046
        GvcMixerControl *control = GVC_MIXER_CONTROL (object);
 
3047
 
 
3048
        if (control->priv->reconnect_id != 0) {
 
3049
                g_source_remove (control->priv->reconnect_id);
 
3050
                control->priv->reconnect_id = 0;
 
3051
        }
 
3052
 
 
3053
        if (control->priv->pa_context != NULL) {
 
3054
                pa_context_unref (control->priv->pa_context);
 
3055
                control->priv->pa_context = NULL;
 
3056
        }
 
3057
 
 
3058
        if (control->priv->default_source_name != NULL) {
 
3059
                g_free (control->priv->default_source_name);
 
3060
                control->priv->default_source_name = NULL;
 
3061
        }
 
3062
        if (control->priv->default_sink_name != NULL) {
 
3063
                g_free (control->priv->default_sink_name);
 
3064
                control->priv->default_sink_name = NULL;
 
3065
        }
 
3066
 
 
3067
        if (control->priv->pa_mainloop != NULL) {
 
3068
                pa_glib_mainloop_free (control->priv->pa_mainloop);
 
3069
                control->priv->pa_mainloop = NULL;
 
3070
        }
 
3071
 
 
3072
        if (control->priv->all_streams != NULL) {
 
3073
                g_hash_table_destroy (control->priv->all_streams);
 
3074
                control->priv->all_streams = NULL;
 
3075
        }
 
3076
 
 
3077
        if (control->priv->sinks != NULL) {
 
3078
                g_hash_table_destroy (control->priv->sinks);
 
3079
                control->priv->sinks = NULL;
 
3080
        }
 
3081
        if (control->priv->sources != NULL) {
 
3082
                g_hash_table_destroy (control->priv->sources);
 
3083
                control->priv->sources = NULL;
 
3084
        }
 
3085
        if (control->priv->sink_inputs != NULL) {
 
3086
                g_hash_table_destroy (control->priv->sink_inputs);
 
3087
                control->priv->sink_inputs = NULL;
 
3088
        }
 
3089
        if (control->priv->source_outputs != NULL) {
 
3090
                g_hash_table_destroy (control->priv->source_outputs);
 
3091
                control->priv->source_outputs = NULL;
 
3092
        }
 
3093
        if (control->priv->clients != NULL) {
 
3094
                g_hash_table_destroy (control->priv->clients);
 
3095
                control->priv->clients = NULL;
 
3096
        }
 
3097
        if (control->priv->cards != NULL) {
 
3098
                g_hash_table_destroy (control->priv->cards);
 
3099
                control->priv->cards = NULL;
 
3100
        }
 
3101
        if (control->priv->ui_outputs != NULL) {
 
3102
                g_hash_table_destroy (control->priv->ui_outputs);
 
3103
                control->priv->ui_outputs = NULL;
 
3104
        }
 
3105
        if (control->priv->ui_inputs != NULL) {
 
3106
                g_hash_table_destroy (control->priv->ui_inputs);
 
3107
                control->priv->ui_inputs = NULL;
 
3108
        }
 
3109
 
 
3110
        G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object);
 
3111
}
 
3112
 
 
3113
static void
 
3114
gvc_mixer_control_set_property (GObject       *object,
 
3115
                                guint          prop_id,
 
3116
                                const GValue  *value,
 
3117
                                GParamSpec    *pspec)
 
3118
{
 
3119
        GvcMixerControl *self = GVC_MIXER_CONTROL (object);
 
3120
 
 
3121
        switch (prop_id) {
 
3122
        case PROP_NAME:
 
3123
                g_free (self->priv->name);
 
3124
                self->priv->name = g_value_dup_string (value);
 
3125
                g_object_notify (G_OBJECT (self), "name");
 
3126
                break;
 
3127
        default:
 
3128
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
3129
                break;
 
3130
        }
 
3131
}
 
3132
 
 
3133
static void
 
3134
gvc_mixer_control_get_property (GObject     *object,
 
3135
                                guint        prop_id,
 
3136
                                GValue      *value,
 
3137
                                GParamSpec  *pspec)
 
3138
{
 
3139
        GvcMixerControl *self = GVC_MIXER_CONTROL (object);
 
3140
 
 
3141
        switch (prop_id) {
 
3142
        case PROP_NAME:
 
3143
                g_value_set_string (value, self->priv->name);
 
3144
                break;
 
3145
        default:
 
3146
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
3147
                break;
 
3148
        }
 
3149
}
 
3150
 
 
3151
 
 
3152
static GObject *
 
3153
gvc_mixer_control_constructor (GType                  type,
 
3154
                               guint                  n_construct_properties,
 
3155
                               GObjectConstructParam *construct_params)
 
3156
{
 
3157
        GObject         *object;
 
3158
        GvcMixerControl *self;
 
3159
 
 
3160
        object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params);
 
3161
 
 
3162
        self = GVC_MIXER_CONTROL (object);
 
3163
 
 
3164
        gvc_mixer_new_pa_context (self);
 
3165
        self->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
 
3166
 
 
3167
        return object;
 
3168
}
 
3169
 
 
3170
static void
 
3171
gvc_mixer_control_class_init (GvcMixerControlClass *klass)
 
3172
{
 
3173
        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
 
3174
 
 
3175
        object_class->constructor = gvc_mixer_control_constructor;
 
3176
        object_class->dispose = gvc_mixer_control_dispose;
 
3177
        object_class->finalize = gvc_mixer_control_finalize;
 
3178
        object_class->set_property = gvc_mixer_control_set_property;
 
3179
        object_class->get_property = gvc_mixer_control_get_property;
 
3180
 
 
3181
        g_object_class_install_property (object_class,
 
3182
                                         PROP_NAME,
 
3183
                                         g_param_spec_string ("name",
 
3184
                                                              "Name",
 
3185
                                                              "Name to display for this mixer control",
 
3186
                                                              NULL,
 
3187
                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
 
3188
 
 
3189
        signals [STATE_CHANGED] =
 
3190
                g_signal_new ("state-changed",
 
3191
                              G_TYPE_FROM_CLASS (klass),
 
3192
                              G_SIGNAL_RUN_LAST,
 
3193
                              G_STRUCT_OFFSET (GvcMixerControlClass, state_changed),
 
3194
                              NULL, NULL,
 
3195
                              g_cclosure_marshal_VOID__UINT,
 
3196
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3197
        signals [STREAM_ADDED] =
 
3198
                g_signal_new ("stream-added",
 
3199
                              G_TYPE_FROM_CLASS (klass),
 
3200
                              G_SIGNAL_RUN_LAST,
 
3201
                              G_STRUCT_OFFSET (GvcMixerControlClass, stream_added),
 
3202
                              NULL, NULL,
 
3203
                              g_cclosure_marshal_VOID__UINT,
 
3204
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3205
        signals [STREAM_REMOVED] =
 
3206
                g_signal_new ("stream-removed",
 
3207
                              G_TYPE_FROM_CLASS (klass),
 
3208
                              G_SIGNAL_RUN_LAST,
 
3209
                              G_STRUCT_OFFSET (GvcMixerControlClass, stream_removed),
 
3210
                              NULL, NULL,
 
3211
                              g_cclosure_marshal_VOID__UINT,
 
3212
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3213
        signals [CARD_ADDED] =
 
3214
                g_signal_new ("card-added",
 
3215
                              G_TYPE_FROM_CLASS (klass),
 
3216
                              G_SIGNAL_RUN_LAST,
 
3217
                              G_STRUCT_OFFSET (GvcMixerControlClass, card_added),
 
3218
                              NULL, NULL,
 
3219
                              g_cclosure_marshal_VOID__UINT,
 
3220
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3221
        signals [CARD_REMOVED] =
 
3222
                g_signal_new ("card-removed",
 
3223
                              G_TYPE_FROM_CLASS (klass),
 
3224
                              G_SIGNAL_RUN_LAST,
 
3225
                              G_STRUCT_OFFSET (GvcMixerControlClass, card_removed),
 
3226
                              NULL, NULL,
 
3227
                              g_cclosure_marshal_VOID__UINT,
 
3228
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3229
        signals [DEFAULT_SINK_CHANGED] =
 
3230
                g_signal_new ("default-sink-changed",
 
3231
                              G_TYPE_FROM_CLASS (klass),
 
3232
                              G_SIGNAL_RUN_LAST,
 
3233
                              G_STRUCT_OFFSET (GvcMixerControlClass, default_sink_changed),
 
3234
                              NULL, NULL,
 
3235
                              g_cclosure_marshal_VOID__UINT,
 
3236
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3237
        signals [DEFAULT_SOURCE_CHANGED] =
 
3238
                g_signal_new ("default-source-changed",
 
3239
                              G_TYPE_FROM_CLASS (klass),
 
3240
                              G_SIGNAL_RUN_LAST,
 
3241
                              G_STRUCT_OFFSET (GvcMixerControlClass, default_source_changed),
 
3242
                              NULL, NULL,
 
3243
                              g_cclosure_marshal_VOID__UINT,
 
3244
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3245
        signals [ACTIVE_OUTPUT_UPDATE] =
 
3246
                g_signal_new ("active-output-update",
 
3247
                              G_TYPE_FROM_CLASS (klass),
 
3248
                              G_SIGNAL_RUN_LAST,
 
3249
                              G_STRUCT_OFFSET (GvcMixerControlClass, active_output_update),
 
3250
                              NULL, NULL,
 
3251
                              g_cclosure_marshal_VOID__UINT,
 
3252
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3253
        signals [ACTIVE_INPUT_UPDATE] =
 
3254
                g_signal_new ("active-input-update",
 
3255
                              G_TYPE_FROM_CLASS (klass),
 
3256
                              G_SIGNAL_RUN_LAST,
 
3257
                              G_STRUCT_OFFSET (GvcMixerControlClass, active_input_update),
 
3258
                              NULL, NULL,
 
3259
                              g_cclosure_marshal_VOID__UINT,
 
3260
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3261
        signals [OUTPUT_ADDED] =
 
3262
                g_signal_new ("output-added",
 
3263
                              G_TYPE_FROM_CLASS (klass),
 
3264
                              G_SIGNAL_RUN_LAST,
 
3265
                              G_STRUCT_OFFSET (GvcMixerControlClass, output_added),
 
3266
                              NULL, NULL,
 
3267
                              g_cclosure_marshal_VOID__UINT,
 
3268
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3269
        signals [INPUT_ADDED] =
 
3270
                g_signal_new ("input-added",
 
3271
                              G_TYPE_FROM_CLASS (klass),
 
3272
                              G_SIGNAL_RUN_LAST,
 
3273
                              G_STRUCT_OFFSET (GvcMixerControlClass, input_added),
 
3274
                              NULL, NULL,
 
3275
                              g_cclosure_marshal_VOID__UINT,
 
3276
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3277
        signals [OUTPUT_REMOVED] =
 
3278
                g_signal_new ("output-removed",
 
3279
                              G_TYPE_FROM_CLASS (klass),
 
3280
                              G_SIGNAL_RUN_LAST,
 
3281
                              G_STRUCT_OFFSET (GvcMixerControlClass, output_removed),
 
3282
                              NULL, NULL,
 
3283
                              g_cclosure_marshal_VOID__UINT,
 
3284
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3285
        signals [INPUT_REMOVED] =
 
3286
                g_signal_new ("input-removed",
 
3287
                              G_TYPE_FROM_CLASS (klass),
 
3288
                              G_SIGNAL_RUN_LAST,
 
3289
                              G_STRUCT_OFFSET (GvcMixerControlClass, input_removed),
 
3290
                              NULL, NULL,
 
3291
                              g_cclosure_marshal_VOID__UINT,
 
3292
                              G_TYPE_NONE, 1, G_TYPE_UINT);
 
3293
        g_type_class_add_private (klass, sizeof (GvcMixerControlPrivate));
 
3294
}
 
3295
 
 
3296
 
 
3297
static void
 
3298
gvc_mixer_control_init (GvcMixerControl *control)
 
3299
{
 
3300
        control->priv = GVC_MIXER_CONTROL_GET_PRIVATE (control);
 
3301
 
 
3302
        control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ());
 
3303
        g_assert (control->priv->pa_mainloop);
 
3304
 
 
3305
        control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop);
 
3306
        g_assert (control->priv->pa_api);
 
3307
 
 
3308
        control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 
3309
        control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 
3310
        control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 
3311
        control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 
3312
        control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 
3313
        control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 
3314
        control->priv->ui_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 
3315
        control->priv->ui_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 
3316
 
 
3317
        control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
 
3318
 
 
3319
        control->priv->state = GVC_STATE_CLOSED;
 
3320
}
 
3321
 
 
3322
static void
 
3323
gvc_mixer_control_finalize (GObject *object)
 
3324
{
 
3325
        GvcMixerControl *mixer_control;
 
3326
 
 
3327
        g_return_if_fail (object != NULL);
 
3328
        g_return_if_fail (GVC_IS_MIXER_CONTROL (object));
 
3329
 
 
3330
        mixer_control = GVC_MIXER_CONTROL (object);
 
3331
        g_free (mixer_control->priv->name);
 
3332
        mixer_control->priv->name = NULL;
 
3333
 
 
3334
        g_return_if_fail (mixer_control->priv != NULL);
 
3335
        G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object);
 
3336
}
 
3337
 
 
3338
GvcMixerControl *
 
3339
gvc_mixer_control_new (const char *name)
 
3340
{
 
3341
        GObject *control;
 
3342
        control = g_object_new (GVC_TYPE_MIXER_CONTROL,
 
3343
                                "name", name,
 
3344
                                NULL);
 
3345
        return GVC_MIXER_CONTROL (control);
 
3346
}
 
3347
 
 
3348
gdouble
 
3349
gvc_mixer_control_get_vol_max_norm (GvcMixerControl *control)
 
3350
{
 
3351
        return (gdouble) PA_VOLUME_NORM;
 
3352
}
 
3353
 
 
3354
gdouble
 
3355
gvc_mixer_control_get_vol_max_amplified (GvcMixerControl *control)
 
3356
{
 
3357
        return (gdouble) PA_VOLUME_UI_MAX;
 
3358
}