~ubuntu-branches/ubuntu/vivid/gnome-flashback/vivid

« back to all changes in this revision

Viewing changes to gnome-flashback/libsound-applet/gvc/gvc-mixer-control.c

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