No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | gvc/gvc-mixer-control.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None |
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 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 *
21 */
22
23 #include "config.h"
24
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <unistd.h>
28
29 #include <glib.h>
30 #include <glib/gi18n-lib.h>
31
32 #include <pulse/pulseaudio.h>
33 #include <pulse/glib-mainloop.h>
34 #include <pulse/ext-stream-restore.h>
35
36 #include "gvc-mixer-control.h"
37 #include "gvc-mixer-sink.h"
38 #include "gvc-mixer-source.h"
39 #include "gvc-mixer-sink-input.h"
40 #include "gvc-mixer-source-output.h"
41 #include "gvc-mixer-event-role.h"
42 #include "gvc-mixer-card.h"
43 #include "gvc-mixer-card-private.h"
44 #include "gvc-channel-map-private.h"
45 #include "gvc-mixer-control-private.h"
46
47 #define GVC_MIXER_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlPrivate))
48
49 #define RECONNECT_DELAY 5
50
51 enum {
52 PROP_0,
53 PROP_NAME
54 };
55
56 struct GvcMixerControlPrivate
57 {
58 pa_glib_mainloop *pa_mainloop;
59 pa_mainloop_api *pa_api;
60 pa_context *pa_context;
61 int n_outstanding;
62 guint reconnect_id;
63 char *name;
64
65 gboolean default_sink_is_set;
66 guint default_sink_id;
67 char *default_sink_name;
68 gboolean default_source_is_set;
69 guint default_source_id;
70 char *default_source_name;
71
72 gboolean event_sink_input_is_set;
73 guint event_sink_input_id;
74
75 GHashTable *all_streams;
76 GHashTable *sinks; /* fixed outputs */
77 GHashTable *sources; /* fixed inputs */
78 GHashTable *sink_inputs; /* routable output streams */
79 GHashTable *source_outputs; /* routable input streams */
80 GHashTable *clients;
81 GHashTable *cards;
82
83 GvcMixerStream *new_default_stream; /* new default stream, used in gvc_mixer_control_set_default_sink () */
84
85 GvcMixerControlState state;
86 };
87
88 enum {
89 STATE_CHANGED,
90 STREAM_ADDED,
91 STREAM_REMOVED,
92 CARD_ADDED,
93 CARD_REMOVED,
94 DEFAULT_SINK_CHANGED,
95 DEFAULT_SOURCE_CHANGED,
96 LAST_SIGNAL
97 };
98
99 static guint signals [LAST_SIGNAL] = { 0, };
100
101 static void gvc_mixer_control_class_init (GvcMixerControlClass *klass);
102 static void gvc_mixer_control_init (GvcMixerControl *mixer_control);
103 static void gvc_mixer_control_finalize (GObject *object);
104
105 G_DEFINE_TYPE (GvcMixerControl, gvc_mixer_control, G_TYPE_OBJECT)
106
107 pa_context *
108 gvc_mixer_control_get_pa_context (GvcMixerControl *control)
109 {
110 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
111 return control->priv->pa_context;
112 }
113
114 /**
115 * gvc_mixer_control_get_event_sink_input:
116 * @control:
117 *
118 * Returns: (transfer none):
119 */
120 GvcMixerStream *
121 gvc_mixer_control_get_event_sink_input (GvcMixerControl *control)
122 {
123 GvcMixerStream *stream;
124
125 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
126
127 stream = g_hash_table_lookup (control->priv->all_streams,
128 GUINT_TO_POINTER (control->priv->event_sink_input_id));
129
130 return stream;
131 }
132
133 static void
134 gvc_mixer_control_stream_restore_cb (pa_context *c,
135 const pa_ext_stream_restore_info *info,
136 int eol,
137 void *userdata)
138 {
139 pa_operation *o;
140 GvcMixerControl *control = (GvcMixerControl *) userdata;
141 pa_ext_stream_restore_info new_info;
142
143 if (eol || control->priv->new_default_stream == NULL)
144 return;
145
146 new_info.name = info->name;
147 new_info.channel_map = info->channel_map;
148 new_info.volume = info->volume;
149 new_info.mute = info->mute;
150
151 new_info.device = gvc_mixer_stream_get_name (control->priv->new_default_stream);
152
153 o = pa_ext_stream_restore_write (control->priv->pa_context,
154 PA_UPDATE_REPLACE,
155 &new_info, 1,
156 TRUE, NULL, NULL);
157
158 if (o == NULL) {
159 g_warning ("pa_ext_stream_restore_write() failed: %s",
160 pa_strerror (pa_context_errno (control->priv->pa_context)));
161 return;
162 }
163
164 g_debug ("Changed default device for %s to %s", info->name, info->device);
165
166 pa_operation_unref (o);
167 }
168
169 gboolean
170 gvc_mixer_control_set_default_sink (GvcMixerControl *control,
171 GvcMixerStream *stream)
172 {
173 pa_operation *o;
174
175 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
176 g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
177
178 o = pa_context_set_default_sink (control->priv->pa_context,
179 gvc_mixer_stream_get_name (stream),
180 NULL,
181 NULL);
182 if (o == NULL) {
183 g_warning ("pa_context_set_default_sink() failed: %s",
184 pa_strerror (pa_context_errno (control->priv->pa_context)));
185 return FALSE;
186 }
187
188 pa_operation_unref (o);
189
190 control->priv->new_default_stream = stream;
191 g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_stream);
192
193 o = pa_ext_stream_restore_read (control->priv->pa_context,
194 gvc_mixer_control_stream_restore_cb,
195 control);
196
197 if (o == NULL) {
198 g_warning ("pa_ext_stream_restore_read() failed: %s",
199 pa_strerror (pa_context_errno (control->priv->pa_context)));
200 return FALSE;
201 }
202
203 pa_operation_unref (o);
204
205 return TRUE;
206 }
207
208 gboolean
209 gvc_mixer_control_set_default_source (GvcMixerControl *control,
210 GvcMixerStream *stream)
211 {
212 pa_operation *o;
213
214 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
215 g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
216
217 o = pa_context_set_default_source (control->priv->pa_context,
218 gvc_mixer_stream_get_name (stream),
219 NULL,
220 NULL);
221 if (o == NULL) {
222 g_warning ("pa_context_set_default_source() failed");
223 return FALSE;
224 }
225
226 pa_operation_unref (o);
227
228 return TRUE;
229 }
230
231 /**
232 * gvc_mixer_control_get_default_sink:
233 * @control:
234 *
235 * Returns: (transfer none):
236 */
237 GvcMixerStream *
238 gvc_mixer_control_get_default_sink (GvcMixerControl *control)
239 {
240 GvcMixerStream *stream;
241
242 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
243
244 if (control->priv->default_sink_is_set) {
245 stream = g_hash_table_lookup (control->priv->all_streams,
246 GUINT_TO_POINTER (control->priv->default_sink_id));
247 } else {
248 stream = NULL;
249 }
250
251 return stream;
252 }
253
254 /**
255 * gvc_mixer_control_get_default_source:
256 * @control:
257 *
258 * Returns: (transfer none):
259 */
260 GvcMixerStream *
261 gvc_mixer_control_get_default_source (GvcMixerControl *control)
262 {
263 GvcMixerStream *stream;
264
265 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
266
267 if (control->priv->default_source_is_set) {
268 stream = g_hash_table_lookup (control->priv->all_streams,
269 GUINT_TO_POINTER (control->priv->default_source_id));
270 } else {
271 stream = NULL;
272 }
273
274 return stream;
275 }
276
277 static gpointer
278 gvc_mixer_control_lookup_id (GHashTable *hash_table,
279 guint id)
280 {
281 return g_hash_table_lookup (hash_table,
282 GUINT_TO_POINTER (id));
283 }
284
285 /**
286 * gvc_mixer_control_lookup_stream_id:
287 * @control:
288 * @id:
289 *
290 * Returns: (transfer none):
291 */
292 GvcMixerStream *
293 gvc_mixer_control_lookup_stream_id (GvcMixerControl *control,
294 guint id)
295 {
296 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
297
298 return gvc_mixer_control_lookup_id (control->priv->all_streams, id);
299 }
300
301 /**
302 * gvc_mixer_control_lookup_card_id:
303 * @control:
304 * @id:
305 *
306 * Returns: (transfer none):
307 */
308 GvcMixerCard *
309 gvc_mixer_control_lookup_card_id (GvcMixerControl *control,
310 guint id)
311 {
312 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
313
314 return gvc_mixer_control_lookup_id (control->priv->cards, id);
315 }
316
317 static void
318 listify_hash_values_hfunc (gpointer key,
319 gpointer value,
320 gpointer user_data)
321 {
322 GSList **list = user_data;
323
324 *list = g_slist_prepend (*list, value);
325 }
326
327 static int
328 gvc_name_collate (const char *namea,
329 const char *nameb)
330 {
331 if (nameb == NULL && namea == NULL)
332 return 0;
333 if (nameb == NULL)
334 return 1;
335 if (namea == NULL)
336 return -1;
337
338 return g_utf8_collate (namea, nameb);
339 }
340
341 static int
342 gvc_card_collate (GvcMixerCard *a,
343 GvcMixerCard *b)
344 {
345 const char *namea;
346 const char *nameb;
347
348 g_return_val_if_fail (a == NULL || GVC_IS_MIXER_CARD (a), 0);
349 g_return_val_if_fail (b == NULL || GVC_IS_MIXER_CARD (b), 0);
350
351 namea = gvc_mixer_card_get_name (a);
352 nameb = gvc_mixer_card_get_name (b);
353
354 return gvc_name_collate (namea, nameb);
355 }
356
357 /**
358 * gvc_mixer_control_get_cards:
359 * @control:
360 *
361 * Returns: (transfer container) (element-type Gvc.MixerCard):
362 */
363 GSList *
364 gvc_mixer_control_get_cards (GvcMixerControl *control)
365 {
366 GSList *retval;
367
368 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
369
370 retval = NULL;
371 g_hash_table_foreach (control->priv->cards,
372 listify_hash_values_hfunc,
373 &retval);
374 return g_slist_sort (retval, (GCompareFunc) gvc_card_collate);
375 }
376
377 static int
378 gvc_stream_collate (GvcMixerStream *a,
379 GvcMixerStream *b)
380 {
381 const char *namea;
382 const char *nameb;
383
384 g_return_val_if_fail (a == NULL || GVC_IS_MIXER_STREAM (a), 0);
385 g_return_val_if_fail (b == NULL || GVC_IS_MIXER_STREAM (b), 0);
386
387 namea = gvc_mixer_stream_get_name (a);
388 nameb = gvc_mixer_stream_get_name (b);
389
390 return gvc_name_collate (namea, nameb);
391 }
392
393 /**
394 * gvc_mixer_control_get_streams:
395 * @control:
396 *
397 * Returns: (transfer container) (element-type Gvc.MixerStream):
398 */
399 GSList *
400 gvc_mixer_control_get_streams (GvcMixerControl *control)
401 {
402 GSList *retval;
403
404 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
405
406 retval = NULL;
407 g_hash_table_foreach (control->priv->all_streams,
408 listify_hash_values_hfunc,
409 &retval);
410 return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
411 }
412
413 /**
414 * gvc_mixer_control_get_sinks:
415 * @control:
416 *
417 * Returns: (transfer container) (element-type Gvc.MixerSink):
418 */
419 GSList *
420 gvc_mixer_control_get_sinks (GvcMixerControl *control)
421 {
422 GSList *retval;
423
424 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
425
426 retval = NULL;
427 g_hash_table_foreach (control->priv->sinks,
428 listify_hash_values_hfunc,
429 &retval);
430 return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
431 }
432
433 /**
434 * gvc_mixer_control_get_sources:
435 * @control:
436 *
437 * Returns: (transfer container) (element-type Gvc.MixerSource):
438 */
439 GSList *
440 gvc_mixer_control_get_sources (GvcMixerControl *control)
441 {
442 GSList *retval;
443
444 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
445
446 retval = NULL;
447 g_hash_table_foreach (control->priv->sources,
448 listify_hash_values_hfunc,
449 &retval);
450 return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
451 }
452
453 /**
454 * gvc_mixer_control_get_sink_inputs:
455 * @control:
456 *
457 * Returns: (transfer container) (element-type Gvc.MixerSinkInput):
458 */
459 GSList *
460 gvc_mixer_control_get_sink_inputs (GvcMixerControl *control)
461 {
462 GSList *retval;
463
464 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
465
466 retval = NULL;
467 g_hash_table_foreach (control->priv->sink_inputs,
468 listify_hash_values_hfunc,
469 &retval);
470 return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
471 }
472
473 /**
474 * gvc_mixer_control_get_source_outputs:
475 * @control:
476 *
477 * Returns: (transfer container) (element-type Gvc.MixerSourceOutput):
478 */
479 GSList *
480 gvc_mixer_control_get_source_outputs (GvcMixerControl *control)
481 {
482 GSList *retval;
483
484 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
485
486 retval = NULL;
487 g_hash_table_foreach (control->priv->source_outputs,
488 listify_hash_values_hfunc,
489 &retval);
490 return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
491 }
492
493 static void
494 dec_outstanding (GvcMixerControl *control)
495 {
496 if (control->priv->n_outstanding <= 0) {
497 return;
498 }
499
500 if (--control->priv->n_outstanding <= 0) {
501 control->priv->state = GVC_STATE_READY;
502 g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_READY);
503 }
504 }
505
506 GvcMixerControlState
507 gvc_mixer_control_get_state (GvcMixerControl *control)
508 {
509 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
510
511 return control->priv->state;
512 }
513
514
515 static void
516 _set_default_source (GvcMixerControl *control,
517 GvcMixerStream *stream)
518 {
519 guint new_id;
520
521 if (stream == NULL) {
522 control->priv->default_source_id = 0;
523 control->priv->default_source_is_set = FALSE;
524 g_signal_emit (control,
525 signals[DEFAULT_SOURCE_CHANGED],
526 0,
527 PA_INVALID_INDEX);
528 return;
529 }
530
531 new_id = gvc_mixer_stream_get_id (stream);
532
533 if (control->priv->default_source_id != new_id) {
534 control->priv->default_source_id = new_id;
535 control->priv->default_source_is_set = TRUE;
536 g_signal_emit (control,
537 signals[DEFAULT_SOURCE_CHANGED],
538 0,
539 new_id);
540 }
541 }
542
543 static void
544 _set_default_sink (GvcMixerControl *control,
545 GvcMixerStream *stream)
546 {
547 guint new_id;
548
549 if (stream == NULL) {
550 /* Don't tell front-ends about an unset default
551 * sink if it's already unset */
552 if (control->priv->default_sink_is_set == FALSE)
553 return;
554 control->priv->default_sink_id = 0;
555 control->priv->default_sink_is_set = FALSE;
556 g_signal_emit (control,
557 signals[DEFAULT_SINK_CHANGED],
558 0,
559 PA_INVALID_INDEX);
560 return;
561 }
562
563 new_id = gvc_mixer_stream_get_id (stream);
564
565 if (control->priv->default_sink_id != new_id) {
566 control->priv->default_sink_id = new_id;
567 control->priv->default_sink_is_set = TRUE;
568 g_signal_emit (control,
569 signals[DEFAULT_SINK_CHANGED],
570 0,
571 new_id);
572 }
573 }
574
575 static gboolean
576 _stream_has_name (gpointer key,
577 GvcMixerStream *stream,
578 const char *name)
579 {
580 const char *t_name;
581
582 t_name = gvc_mixer_stream_get_name (stream);
583
584 if (t_name != NULL
585 && name != NULL
586 && strcmp (t_name, name) == 0) {
587 return TRUE;
588 }
589
590 return FALSE;
591 }
592
593 static GvcMixerStream *
594 find_stream_for_name (GvcMixerControl *control,
595 const char *name)
596 {
597 GvcMixerStream *stream;
598
599 stream = g_hash_table_find (control->priv->all_streams,
600 (GHRFunc)_stream_has_name,
601 (char *)name);
602 return stream;
603 }
604
605 static void
606 update_default_source_from_name (GvcMixerControl *control,
607 const char *name)
608 {
609 gboolean changed = FALSE;
610
611 if ((control->priv->default_source_name == NULL
612 && name != NULL)
613 || (control->priv->default_source_name != NULL
614 && name == NULL)
615 || (name != NULL && strcmp (control->priv->default_source_name, name) != 0)) {
616 changed = TRUE;
617 }
618
619 if (changed) {
620 GvcMixerStream *stream;
621
622 g_free (control->priv->default_source_name);
623 control->priv->default_source_name = g_strdup (name);
624
625 stream = find_stream_for_name (control, name);
626 _set_default_source (control, stream);
627 }
628 }
629
630 static void
631 update_default_sink_from_name (GvcMixerControl *control,
632 const char *name)
633 {
634 gboolean changed = FALSE;
635
636 if ((control->priv->default_sink_name == NULL
637 && name != NULL)
638 || (control->priv->default_sink_name != NULL
639 && name == NULL)
640 || (name != NULL && strcmp (control->priv->default_sink_name, name) != 0)) {
641 changed = TRUE;
642 }
643
644 if (changed) {
645 GvcMixerStream *stream;
646 g_free (control->priv->default_sink_name);
647 control->priv->default_sink_name = g_strdup (name);
648
649 stream = find_stream_for_name (control, name);
650 _set_default_sink (control, stream);
651 }
652 }
653
654 static void
655 update_server (GvcMixerControl *control,
656 const pa_server_info *info)
657 {
658 if (info->default_source_name != NULL) {
659 update_default_source_from_name (control, info->default_source_name);
660 }
661 if (info->default_sink_name != NULL) {
662 update_default_sink_from_name (control, info->default_sink_name);
663 }
664 }
665
666 static void
667 remove_stream (GvcMixerControl *control,
668 GvcMixerStream *stream)
669 {
670 guint id;
671
672 g_object_ref (stream);
673
674 id = gvc_mixer_stream_get_id (stream);
675
676 if (id == control->priv->default_sink_id) {
677 _set_default_sink (control, NULL);
678 } else if (id == control->priv->default_source_id) {
679 _set_default_source (control, NULL);
680 }
681
682 g_hash_table_remove (control->priv->all_streams,
683 GUINT_TO_POINTER (id));
684 g_signal_emit (G_OBJECT (control),
685 signals[STREAM_REMOVED],
686 0,
687 gvc_mixer_stream_get_id (stream));
688 g_object_unref (stream);
689 }
690
691 static void
692 add_stream (GvcMixerControl *control,
693 GvcMixerStream *stream)
694 {
695 g_hash_table_insert (control->priv->all_streams,
696 GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)),
697 stream);
698 g_signal_emit (G_OBJECT (control),
699 signals[STREAM_ADDED],
700 0,
701 gvc_mixer_stream_get_id (stream));
702 }
703
704 static void
705 set_icon_name_from_proplist (GvcMixerStream *stream,
706 pa_proplist *l,
707 const char *default_icon_name)
708 {
709 const char *t;
710
711 if ((t = pa_proplist_gets (l, PA_PROP_DEVICE_ICON_NAME))) {
712 goto finish;
713 }
714
715 if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ICON_NAME))) {
716 goto finish;
717 }
718
719 if ((t = pa_proplist_gets (l, PA_PROP_WINDOW_ICON_NAME))) {
720 goto finish;
721 }
722
723 if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ICON_NAME))) {
724 goto finish;
725 }
726
727 if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
728
729 if (strcmp (t, "video") == 0 ||
730 strcmp (t, "phone") == 0) {
731 goto finish;
732 }
733
734 if (strcmp (t, "music") == 0) {
735 t = "audio";
736 goto finish;
737 }
738
739 if (strcmp (t, "game") == 0) {
740 t = "applications-games";
741 goto finish;
742 }
743
744 if (strcmp (t, "event") == 0) {
745 t = "dialog-information";
746 goto finish;
747 }
748 }
749
750 t = default_icon_name;
751
752 finish:
753 gvc_mixer_stream_set_icon_name (stream, t);
754 }
755
756 static void
757 update_sink (GvcMixerControl *control,
758 const pa_sink_info *info)
759 {
760 GvcMixerStream *stream;
761 gboolean is_new;
762 pa_volume_t max_volume;
763 GvcChannelMap *map;
764 char map_buff[PA_CHANNEL_MAP_SNPRINT_MAX];
765
766 pa_channel_map_snprint (map_buff, PA_CHANNEL_MAP_SNPRINT_MAX, &info->channel_map);
767 #if 1
768 g_debug ("Updating sink: index=%u name='%s' description='%s' map='%s'",
769 info->index,
770 info->name,
771 info->description,
772 map_buff);
773 #endif
774
775 map = NULL;
776 is_new = FALSE;
777 stream = g_hash_table_lookup (control->priv->sinks,
778 GUINT_TO_POINTER (info->index));
779 if (stream == NULL) {
780 #if PA_MICRO > 15
781 GList *list = NULL;
782 guint i;
783 #endif /* PA_MICRO > 15 */
784
785 map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
786 stream = gvc_mixer_sink_new (control->priv->pa_context,
787 info->index,
788 map);
789 #if PA_MICRO > 15
790 for (i = 0; i < info->n_ports; i++) {
791 GvcMixerStreamPort *port;
792
793 port = g_new0 (GvcMixerStreamPort, 1);
794 port->port = g_strdup (info->ports[i]->name);
795 port->human_port = g_strdup (info->ports[i]->description);
796 port->priority = info->ports[i]->priority;
797 list = g_list_prepend (list, port);
798 }
799 gvc_mixer_stream_set_ports (stream, list);
800 #endif /* PA_MICRO > 15 */
801 g_object_unref (map);
802 is_new = TRUE;
803 } else if (gvc_mixer_stream_is_running (stream)) {
804 /* Ignore events if volume changes are outstanding */
805 g_debug ("Ignoring event, volume changes are outstanding");
806 return;
807 }
808
809 max_volume = pa_cvolume_max (&info->volume);
810 gvc_mixer_stream_set_name (stream, info->name);
811 gvc_mixer_stream_set_card_index (stream, info->card);
812 gvc_mixer_stream_set_description (stream, info->description);
813 set_icon_name_from_proplist (stream, info->proplist, "audio-card");
814 gvc_mixer_stream_set_volume (stream, (guint)max_volume);
815 gvc_mixer_stream_set_is_muted (stream, info->mute);
816 gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME));
817 gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
818 #if PA_MICRO > 15
819 if (info->active_port != NULL)
820 gvc_mixer_stream_set_port (stream, info->active_port->name);
821 #endif /* PA_MICRO > 15 */
822
823 if (is_new) {
824 g_hash_table_insert (control->priv->sinks,
825 GUINT_TO_POINTER (info->index),
826 g_object_ref (stream));
827 add_stream (control, stream);
828 }
829
830 if (control->priv->default_sink_name != NULL
831 && info->name != NULL
832 && strcmp (control->priv->default_sink_name, info->name) == 0) {
833 _set_default_sink (control, stream);
834 }
835
836 if (map == NULL)
837 map = (GvcChannelMap *) gvc_mixer_stream_get_channel_map (stream);
838 gvc_channel_map_volume_changed (map, &info->volume, FALSE);
839 }
840
841 static void
842 update_source (GvcMixerControl *control,
843 const pa_source_info *info)
844 {
845 GvcMixerStream *stream;
846 gboolean is_new;
847 pa_volume_t max_volume;
848
849 #if 1
850 g_debug ("Updating source: index=%u name='%s' description='%s'",
851 info->index,
852 info->name,
853 info->description);
854 #endif
855
856 /* completely ignore monitors, they're not real sources */
857 if (info->monitor_of_sink != PA_INVALID_INDEX) {
858 return;
859 }
860
861 is_new = FALSE;
862
863 stream = g_hash_table_lookup (control->priv->sources,
864 GUINT_TO_POINTER (info->index));
865 if (stream == NULL) {
866 #if PA_MICRO > 15
867 GList *list = NULL;
868 guint i;
869 #endif /* PA_MICRO > 15 */
870 GvcChannelMap *map;
871
872 map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
873 stream = gvc_mixer_source_new (control->priv->pa_context,
874 info->index,
875 map);
876 #if PA_MICRO > 15
877 for (i = 0; i < info->n_ports; i++) {
878 GvcMixerStreamPort *port;
879
880 port = g_new0 (GvcMixerStreamPort, 1);
881 port->port = g_strdup (info->ports[i]->name);
882 port->human_port = g_strdup (info->ports[i]->description);
883 port->priority = info->ports[i]->priority;
884 list = g_list_prepend (list, port);
885 }
886 gvc_mixer_stream_set_ports (stream, list);
887 #endif /* PA_MICRO > 15 */
888
889 g_object_unref (map);
890 is_new = TRUE;
891 } else if (gvc_mixer_stream_is_running (stream)) {
892 /* Ignore events if volume changes are outstanding */
893 g_debug ("Ignoring event, volume changes are outstanding");
894 return;
895 }
896
897 max_volume = pa_cvolume_max (&info->volume);
898
899 gvc_mixer_stream_set_name (stream, info->name);
900 gvc_mixer_stream_set_card_index (stream, info->card);
901 gvc_mixer_stream_set_description (stream, info->description);
902 set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
903 gvc_mixer_stream_set_volume (stream, (guint)max_volume);
904 gvc_mixer_stream_set_is_muted (stream, info->mute);
905 gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME));
906 gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
907 #if PA_MICRO > 15
908 if (info->active_port != NULL)
909 gvc_mixer_stream_set_port (stream, info->active_port->name);
910 #endif /* PA_MICRO > 15 */
911
912 if (is_new) {
913 g_hash_table_insert (control->priv->sources,
914 GUINT_TO_POINTER (info->index),
915 g_object_ref (stream));
916 add_stream (control, stream);
917 }
918
919 if (control->priv->default_source_name != NULL
920 && info->name != NULL
921 && strcmp (control->priv->default_source_name, info->name) == 0) {
922 _set_default_source (control, stream);
923 }
924 }
925
926 static void
927 set_is_event_stream_from_proplist (GvcMixerStream *stream,
928 pa_proplist *l)
929 {
930 const char *t;
931 gboolean is_event_stream;
932
933 is_event_stream = FALSE;
934
935 if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
936 if (g_str_equal (t, "event"))
937 is_event_stream = TRUE;
938 }
939
940 gvc_mixer_stream_set_is_event_stream (stream, is_event_stream);
941 }
942
943 static void
944 set_application_id_from_proplist (GvcMixerStream *stream,
945 pa_proplist *l)
946 {
947 const char *t;
948
949 if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ID))) {
950 gvc_mixer_stream_set_application_id (stream, t);
951 }
952 }
953
954 static void
955 update_sink_input (GvcMixerControl *control,
956 const pa_sink_input_info *info)
957 {
958 GvcMixerStream *stream;
959 gboolean is_new;
960 pa_volume_t max_volume;
961 const char *name;
962
963 #if 0
964 g_debug ("Updating sink input: index=%u name='%s' client=%u sink=%u",
965 info->index,
966 info->name,
967 info->client,
968 info->sink);
969 #endif
970
971 is_new = FALSE;
972
973 stream = g_hash_table_lookup (control->priv->sink_inputs,
974 GUINT_TO_POINTER (info->index));
975 if (stream == NULL) {
976 GvcChannelMap *map;
977 map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
978 stream = gvc_mixer_sink_input_new (control->priv->pa_context,
979 info->index,
980 map);
981 g_object_unref (map);
982 is_new = TRUE;
983 } else if (gvc_mixer_stream_is_running (stream)) {
984 /* Ignore events if volume changes are outstanding */
985 g_debug ("Ignoring event, volume changes are outstanding");
986 return;
987 }
988
989 max_volume = pa_cvolume_max (&info->volume);
990
991 name = (const char *)g_hash_table_lookup (control->priv->clients,
992 GUINT_TO_POINTER (info->client));
993 gvc_mixer_stream_set_name (stream, name);
994 gvc_mixer_stream_set_description (stream, info->name);
995
996 set_application_id_from_proplist (stream, info->proplist);
997 set_is_event_stream_from_proplist (stream, info->proplist);
998 set_icon_name_from_proplist (stream, info->proplist, "applications-multimedia");
999 gvc_mixer_stream_set_volume (stream, (guint)max_volume);
1000 gvc_mixer_stream_set_is_muted (stream, info->mute);
1001 gvc_mixer_stream_set_is_virtual (stream, info->client == PA_INVALID_INDEX);
1002
1003 if (is_new) {
1004 g_hash_table_insert (control->priv->sink_inputs,
1005 GUINT_TO_POINTER (info->index),
1006 g_object_ref (stream));
1007 add_stream (control, stream);
1008 }
1009 }
1010
1011 static void
1012 update_source_output (GvcMixerControl *control,
1013 const pa_source_output_info *info)
1014 {
1015 GvcMixerStream *stream;
1016 gboolean is_new;
1017 const char *name;
1018
1019 #if 1
1020 g_debug ("Updating source output: index=%u name='%s' client=%u source=%u",
1021 info->index,
1022 info->name,
1023 info->client,
1024 info->source);
1025 #endif
1026
1027 is_new = FALSE;
1028 stream = g_hash_table_lookup (control->priv->source_outputs,
1029 GUINT_TO_POINTER (info->index));
1030 if (stream == NULL) {
1031 GvcChannelMap *map;
1032 map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
1033 stream = gvc_mixer_source_output_new (control->priv->pa_context,
1034 info->index,
1035 map);
1036 g_object_unref (map);
1037 is_new = TRUE;
1038 }
1039
1040 name = (const char *)g_hash_table_lookup (control->priv->clients,
1041 GUINT_TO_POINTER (info->client));
1042
1043 gvc_mixer_stream_set_name (stream, name);
1044 gvc_mixer_stream_set_description (stream, info->name);
1045 set_application_id_from_proplist (stream, info->proplist);
1046 set_is_event_stream_from_proplist (stream, info->proplist);
1047 set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
1048
1049 if (is_new) {
1050 g_hash_table_insert (control->priv->source_outputs,
1051 GUINT_TO_POINTER (info->index),
1052 g_object_ref (stream));
1053 add_stream (control, stream);
1054 }
1055 }
1056
1057 static void
1058 update_client (GvcMixerControl *control,
1059 const pa_client_info *info)
1060 {
1061 #if 1
1062 g_debug ("Updating client: index=%u name='%s'",
1063 info->index,
1064 info->name);
1065 #endif
1066 g_hash_table_insert (control->priv->clients,
1067 GUINT_TO_POINTER (info->index),
1068 g_strdup (info->name));
1069 }
1070
1071 static char *
1072 card_num_streams_to_status (guint sinks,
1073 guint sources)
1074 {
1075 char *sinks_str;
1076 char *sources_str;
1077 char *ret;
1078
1079 if (sinks == 0 && sources == 0) {
1080 /* translators:
1081 * The device has been disabled */
1082 return g_strdup (_("Disabled"));
1083 }
1084 if (sinks == 0) {
1085 sinks_str = NULL;
1086 } else {
1087 /* translators:
1088 * The number of sound outputs on a particular device */
1089 sinks_str = g_strdup_printf (ngettext ("%u Output",
1090 "%u Outputs",
1091 sinks),
1092 sinks);
1093 }
1094 if (sources == 0) {
1095 sources_str = NULL;
1096 } else {
1097 /* translators:
1098 * The number of sound inputs on a particular device */
1099 sources_str = g_strdup_printf (ngettext ("%u Input",
1100 "%u Inputs",
1101 sources),
1102 sources);
1103 }
1104 if (sources_str == NULL)
1105 return sinks_str;
1106 if (sinks_str == NULL)
1107 return sources_str;
1108 ret = g_strdup_printf ("%s / %s", sinks_str, sources_str);
1109 g_free (sinks_str);
1110 g_free (sources_str);
1111 return ret;
1112 }
1113
1114 static void
1115 update_card (GvcMixerControl *control,
1116 const pa_card_info *info)
1117 {
1118 GvcMixerCard *card;
1119 gboolean is_new = FALSE;
1120 #if 1
1121 guint i;
1122 const char *key;
1123 void *state;
1124
1125 g_debug ("Udpating card %s (index: %u driver: %s):",
1126 info->name, info->index, info->driver);
1127
1128 for (i = 0; i < info->n_profiles; i++) {
1129 struct pa_card_profile_info pi = info->profiles[i];
1130 gboolean is_default;
1131
1132 is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0);
1133 g_debug ("\tProfile '%s': %d sources %d sinks%s",
1134 pi.name, pi.n_sources, pi.n_sinks,
1135 is_default ? " (Current)" : "");
1136 }
1137 state = NULL;
1138 key = pa_proplist_iterate (info->proplist, &state);
1139 while (key != NULL) {
1140 g_debug ("\tProperty: '%s' = '%s'",
1141 key, pa_proplist_gets (info->proplist, key));
1142 key = pa_proplist_iterate (info->proplist, &state);
1143 }
1144 #endif
1145 card = g_hash_table_lookup (control->priv->cards,
1146 GUINT_TO_POINTER (info->index));
1147 if (card == NULL) {
1148 GList *list = NULL;
1149
1150 for (i = 0; i < info->n_profiles; i++) {
1151 struct pa_card_profile_info pi = info->profiles[i];
1152 GvcMixerCardProfile *profile;
1153
1154 profile = g_new0 (GvcMixerCardProfile, 1);
1155 profile->profile = g_strdup (pi.name);
1156 profile->human_profile = g_strdup (pi.description);
1157 profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources);
1158 profile->n_sinks = pi.n_sinks;
1159 profile->n_sources = pi.n_sources;
1160 profile->priority = pi.priority;
1161 list = g_list_prepend (list, profile);
1162 }
1163 card = gvc_mixer_card_new (control->priv->pa_context,
1164 info->index);
1165 gvc_mixer_card_set_profiles (card, list);
1166 is_new = TRUE;
1167 }
1168
1169 gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description"));
1170 gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name"));
1171 gvc_mixer_card_set_profile (card, info->active_profile->name);
1172
1173 if (is_new) {
1174 g_hash_table_insert (control->priv->cards,
1175 GUINT_TO_POINTER (info->index),
1176 g_object_ref (card));
1177 }
1178 g_signal_emit (G_OBJECT (control),
1179 signals[CARD_ADDED],
1180 0,
1181 info->index);
1182 }
1183
1184 static void
1185 _pa_context_get_sink_info_cb (pa_context *context,
1186 const pa_sink_info *i,
1187 int eol,
1188 void *userdata)
1189 {
1190 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1191
1192 if (eol < 0) {
1193 if (pa_context_errno (context) == PA_ERR_NOENTITY) {
1194 return;
1195 }
1196
1197 g_warning ("Sink callback failure");
1198 return;
1199 }
1200
1201 if (eol > 0) {
1202 dec_outstanding (control);
1203 return;
1204 }
1205
1206 update_sink (control, i);
1207 }
1208
1209 static void
1210 _pa_context_get_source_info_cb (pa_context *context,
1211 const pa_source_info *i,
1212 int eol,
1213 void *userdata)
1214 {
1215 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1216
1217 if (eol < 0) {
1218 if (pa_context_errno (context) == PA_ERR_NOENTITY) {
1219 return;
1220 }
1221
1222 g_warning ("Source callback failure");
1223 return;
1224 }
1225
1226 if (eol > 0) {
1227 dec_outstanding (control);
1228 return;
1229 }
1230
1231 update_source (control, i);
1232 }
1233
1234 static void
1235 _pa_context_get_sink_input_info_cb (pa_context *context,
1236 const pa_sink_input_info *i,
1237 int eol,
1238 void *userdata)
1239 {
1240 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1241
1242 if (eol < 0) {
1243 if (pa_context_errno (context) == PA_ERR_NOENTITY) {
1244 return;
1245 }
1246
1247 g_warning ("Sink input callback failure");
1248 return;
1249 }
1250
1251 if (eol > 0) {
1252 dec_outstanding (control);
1253 return;
1254 }
1255
1256 update_sink_input (control, i);
1257 }
1258
1259 static void
1260 _pa_context_get_source_output_info_cb (pa_context *context,
1261 const pa_source_output_info *i,
1262 int eol,
1263 void *userdata)
1264 {
1265 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1266
1267 if (eol < 0) {
1268 if (pa_context_errno (context) == PA_ERR_NOENTITY) {
1269 return;
1270 }
1271
1272 g_warning ("Source output callback failure");
1273 return;
1274 }
1275
1276 if (eol > 0) {
1277 dec_outstanding (control);
1278 return;
1279 }
1280
1281 update_source_output (control, i);
1282 }
1283
1284 static void
1285 _pa_context_get_client_info_cb (pa_context *context,
1286 const pa_client_info *i,
1287 int eol,
1288 void *userdata)
1289 {
1290 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1291
1292 if (eol < 0) {
1293 if (pa_context_errno (context) == PA_ERR_NOENTITY) {
1294 return;
1295 }
1296
1297 g_warning ("Client callback failure");
1298 return;
1299 }
1300
1301 if (eol > 0) {
1302 dec_outstanding (control);
1303 return;
1304 }
1305
1306 update_client (control, i);
1307 }
1308
1309 static void
1310 _pa_context_get_card_info_by_index_cb (pa_context *context,
1311 const pa_card_info *i,
1312 int eol,
1313 void *userdata)
1314 {
1315 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1316
1317 if (eol < 0) {
1318 if (pa_context_errno (context) == PA_ERR_NOENTITY)
1319 return;
1320
1321 g_warning ("Card callback failure");
1322 return;
1323 }
1324
1325 if (eol > 0) {
1326 dec_outstanding (control);
1327 return;
1328 }
1329
1330 update_card (control, i);
1331 }
1332
1333 static void
1334 _pa_context_get_server_info_cb (pa_context *context,
1335 const pa_server_info *i,
1336 void *userdata)
1337 {
1338 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1339
1340 if (i == NULL) {
1341 g_warning ("Server info callback failure");
1342 return;
1343 }
1344
1345 update_server (control, i);
1346 dec_outstanding (control);
1347 }
1348
1349 static void
1350 remove_event_role_stream (GvcMixerControl *control)
1351 {
1352 g_debug ("Removing event role");
1353 }
1354
1355 static void
1356 update_event_role_stream (GvcMixerControl *control,
1357 const pa_ext_stream_restore_info *info)
1358 {
1359 GvcMixerStream *stream;
1360 gboolean is_new;
1361 pa_volume_t max_volume;
1362
1363 if (strcmp (info->name, "sink-input-by-media-role:event") != 0) {
1364 return;
1365 }
1366
1367 #if 0
1368 g_debug ("Updating event role: name='%s' device='%s'",
1369 info->name,
1370 info->device);
1371 #endif
1372
1373 is_new = FALSE;
1374
1375 if (!control->priv->event_sink_input_is_set) {
1376 pa_channel_map pa_map;
1377 GvcChannelMap *map;
1378
1379 pa_map.channels = 1;
1380 pa_map.map[0] = PA_CHANNEL_POSITION_MONO;
1381 map = gvc_channel_map_new_from_pa_channel_map (&pa_map);
1382
1383 stream = gvc_mixer_event_role_new (control->priv->pa_context,
1384 info->device,
1385 map);
1386 control->priv->event_sink_input_id = gvc_mixer_stream_get_id (stream);
1387 control->priv->event_sink_input_is_set = TRUE;
1388
1389 is_new = TRUE;
1390 } else {
1391 stream = g_hash_table_lookup (control->priv->all_streams,
1392 GUINT_TO_POINTER (control->priv->event_sink_input_id));
1393 }
1394
1395 max_volume = pa_cvolume_max (&info->volume);
1396
1397 gvc_mixer_stream_set_name (stream, _("System Sounds"));
1398 gvc_mixer_stream_set_icon_name (stream, "multimedia-volume-control");
1399 gvc_mixer_stream_set_volume (stream, (guint)max_volume);
1400 gvc_mixer_stream_set_is_muted (stream, info->mute);
1401
1402 if (is_new) {
1403 add_stream (control, stream);
1404 }
1405 }
1406
1407 static void
1408 _pa_ext_stream_restore_read_cb (pa_context *context,
1409 const pa_ext_stream_restore_info *i,
1410 int eol,
1411 void *userdata)
1412 {
1413 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1414
1415 if (eol < 0) {
1416 g_debug ("Failed to initialized stream_restore extension: %s",
1417 pa_strerror (pa_context_errno (context)));
1418 remove_event_role_stream (control);
1419 return;
1420 }
1421
1422 if (eol > 0) {
1423 dec_outstanding (control);
1424 /* If we don't have an event stream to restore, then
1425 * set one up with a default 100% volume */
1426 if (!control->priv->event_sink_input_is_set) {
1427 pa_ext_stream_restore_info info;
1428
1429 memset (&info, 0, sizeof(info));
1430 info.name = "sink-input-by-media-role:event";
1431 info.volume.channels = 1;
1432 info.volume.values[0] = PA_VOLUME_NORM;
1433 update_event_role_stream (control, &info);
1434 }
1435 return;
1436 }
1437
1438 update_event_role_stream (control, i);
1439 }
1440
1441 static void
1442 _pa_ext_stream_restore_subscribe_cb (pa_context *context,
1443 void *userdata)
1444 {
1445 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1446 pa_operation *o;
1447
1448 o = pa_ext_stream_restore_read (context,
1449 _pa_ext_stream_restore_read_cb,
1450 control);
1451 if (o == NULL) {
1452 g_warning ("pa_ext_stream_restore_read() failed");
1453 return;
1454 }
1455
1456 pa_operation_unref (o);
1457 }
1458
1459 static void
1460 req_update_server_info (GvcMixerControl *control,
1461 int index)
1462 {
1463 pa_operation *o;
1464
1465 o = pa_context_get_server_info (control->priv->pa_context,
1466 _pa_context_get_server_info_cb,
1467 control);
1468 if (o == NULL) {
1469 g_warning ("pa_context_get_server_info() failed");
1470 return;
1471 }
1472 pa_operation_unref (o);
1473 }
1474
1475 static void
1476 req_update_client_info (GvcMixerControl *control,
1477 int index)
1478 {
1479 pa_operation *o;
1480
1481 if (index < 0) {
1482 o = pa_context_get_client_info_list (control->priv->pa_context,
1483 _pa_context_get_client_info_cb,
1484 control);
1485 } else {
1486 o = pa_context_get_client_info (control->priv->pa_context,
1487 index,
1488 _pa_context_get_client_info_cb,
1489 control);
1490 }
1491
1492 if (o == NULL) {
1493 g_warning ("pa_context_client_info_list() failed");
1494 return;
1495 }
1496 pa_operation_unref (o);
1497 }
1498
1499 static void
1500 req_update_card (GvcMixerControl *control,
1501 int index)
1502 {
1503 pa_operation *o;
1504
1505 if (index < 0) {
1506 o = pa_context_get_card_info_list (control->priv->pa_context,
1507 _pa_context_get_card_info_by_index_cb,
1508 control);
1509 } else {
1510 o = pa_context_get_card_info_by_index (control->priv->pa_context,
1511 index,
1512 _pa_context_get_card_info_by_index_cb,
1513 control);
1514 }
1515
1516 if (o == NULL) {
1517 g_warning ("pa_context_get_card_info_by_index() failed");
1518 return;
1519 }
1520 pa_operation_unref (o);
1521 }
1522
1523 static void
1524 req_update_sink_info (GvcMixerControl *control,
1525 int index)
1526 {
1527 pa_operation *o;
1528
1529 if (index < 0) {
1530 o = pa_context_get_sink_info_list (control->priv->pa_context,
1531 _pa_context_get_sink_info_cb,
1532 control);
1533 } else {
1534 o = pa_context_get_sink_info_by_index (control->priv->pa_context,
1535 index,
1536 _pa_context_get_sink_info_cb,
1537 control);
1538 }
1539
1540 if (o == NULL) {
1541 g_warning ("pa_context_get_sink_info_list() failed");
1542 return;
1543 }
1544 pa_operation_unref (o);
1545 }
1546
1547 static void
1548 req_update_source_info (GvcMixerControl *control,
1549 int index)
1550 {
1551 pa_operation *o;
1552
1553 if (index < 0) {
1554 o = pa_context_get_source_info_list (control->priv->pa_context,
1555 _pa_context_get_source_info_cb,
1556 control);
1557 } else {
1558 o = pa_context_get_source_info_by_index(control->priv->pa_context,
1559 index,
1560 _pa_context_get_source_info_cb,
1561 control);
1562 }
1563
1564 if (o == NULL) {
1565 g_warning ("pa_context_get_source_info_list() failed");
1566 return;
1567 }
1568 pa_operation_unref (o);
1569 }
1570
1571 static void
1572 req_update_sink_input_info (GvcMixerControl *control,
1573 int index)
1574 {
1575 pa_operation *o;
1576
1577 if (index < 0) {
1578 o = pa_context_get_sink_input_info_list (control->priv->pa_context,
1579 _pa_context_get_sink_input_info_cb,
1580 control);
1581 } else {
1582 o = pa_context_get_sink_input_info (control->priv->pa_context,
1583 index,
1584 _pa_context_get_sink_input_info_cb,
1585 control);
1586 }
1587
1588 if (o == NULL) {
1589 g_warning ("pa_context_get_sink_input_info_list() failed");
1590 return;
1591 }
1592 pa_operation_unref (o);
1593 }
1594
1595 static void
1596 req_update_source_output_info (GvcMixerControl *control,
1597 int index)
1598 {
1599 pa_operation *o;
1600
1601 if (index < 0) {
1602 o = pa_context_get_source_output_info_list (control->priv->pa_context,
1603 _pa_context_get_source_output_info_cb,
1604 control);
1605 } else {
1606 o = pa_context_get_source_output_info (control->priv->pa_context,
1607 index,
1608 _pa_context_get_source_output_info_cb,
1609 control);
1610 }
1611
1612 if (o == NULL) {
1613 g_warning ("pa_context_get_source_output_info_list() failed");
1614 return;
1615 }
1616 pa_operation_unref (o);
1617 }
1618
1619 static void
1620 remove_client (GvcMixerControl *control,
1621 guint index)
1622 {
1623 g_hash_table_remove (control->priv->clients,
1624 GUINT_TO_POINTER (index));
1625 }
1626
1627 static void
1628 remove_card (GvcMixerControl *control,
1629 guint index)
1630 {
1631 g_hash_table_remove (control->priv->cards,
1632 GUINT_TO_POINTER (index));
1633
1634 g_signal_emit (G_OBJECT (control),
1635 signals[CARD_REMOVED],
1636 0,
1637 index);
1638 }
1639
1640 static void
1641 remove_sink (GvcMixerControl *control,
1642 guint index)
1643 {
1644 GvcMixerStream *stream;
1645
1646 #if 0
1647 g_debug ("Removing sink: index=%u", index);
1648 #endif
1649
1650 stream = g_hash_table_lookup (control->priv->sinks,
1651 GUINT_TO_POINTER (index));
1652 if (stream == NULL) {
1653 return;
1654 }
1655 g_hash_table_remove (control->priv->sinks,
1656 GUINT_TO_POINTER (index));
1657
1658 remove_stream (control, stream);
1659 }
1660
1661 static void
1662 remove_source (GvcMixerControl *control,
1663 guint index)
1664 {
1665 GvcMixerStream *stream;
1666
1667 #if 0
1668 g_debug ("Removing source: index=%u", index);
1669 #endif
1670
1671 stream = g_hash_table_lookup (control->priv->sources,
1672 GUINT_TO_POINTER (index));
1673 if (stream == NULL) {
1674 return;
1675 }
1676 g_hash_table_remove (control->priv->sources,
1677 GUINT_TO_POINTER (index));
1678
1679 remove_stream (control, stream);
1680 }
1681
1682 static void
1683 remove_sink_input (GvcMixerControl *control,
1684 guint index)
1685 {
1686 GvcMixerStream *stream;
1687
1688 #if 0
1689 g_debug ("Removing sink input: index=%u", index);
1690 #endif
1691 stream = g_hash_table_lookup (control->priv->sink_inputs,
1692 GUINT_TO_POINTER (index));
1693 if (stream == NULL) {
1694 return;
1695 }
1696 g_hash_table_remove (control->priv->sink_inputs,
1697 GUINT_TO_POINTER (index));
1698
1699 remove_stream (control, stream);
1700 }
1701
1702 static void
1703 remove_source_output (GvcMixerControl *control,
1704 guint index)
1705 {
1706 GvcMixerStream *stream;
1707
1708 #if 0
1709 g_debug ("Removing source output: index=%u", index);
1710 #endif
1711
1712 stream = g_hash_table_lookup (control->priv->source_outputs,
1713 GUINT_TO_POINTER (index));
1714 if (stream == NULL) {
1715 return;
1716 }
1717 g_hash_table_remove (control->priv->source_outputs,
1718 GUINT_TO_POINTER (index));
1719
1720 remove_stream (control, stream);
1721 }
1722
1723 static void
1724 _pa_context_subscribe_cb (pa_context *context,
1725 pa_subscription_event_type_t t,
1726 uint32_t index,
1727 void *userdata)
1728 {
1729 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1730
1731 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
1732 case PA_SUBSCRIPTION_EVENT_SINK:
1733 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
1734 remove_sink (control, index);
1735 } else {
1736 req_update_sink_info (control, index);
1737 }
1738 break;
1739
1740 case PA_SUBSCRIPTION_EVENT_SOURCE:
1741 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
1742 remove_source (control, index);
1743 } else {
1744 req_update_source_info (control, index);
1745 }
1746 break;
1747
1748 case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
1749 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
1750 remove_sink_input (control, index);
1751 } else {
1752 req_update_sink_input_info (control, index);
1753 }
1754 break;
1755
1756 case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
1757 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
1758 remove_source_output (control, index);
1759 } else {
1760 req_update_source_output_info (control, index);
1761 }
1762 break;
1763
1764 case PA_SUBSCRIPTION_EVENT_CLIENT:
1765 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
1766 remove_client (control, index);
1767 } else {
1768 req_update_client_info (control, index);
1769 }
1770 break;
1771
1772 case PA_SUBSCRIPTION_EVENT_SERVER:
1773 req_update_server_info (control, index);
1774 break;
1775
1776 case PA_SUBSCRIPTION_EVENT_CARD:
1777 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
1778 remove_card (control, index);
1779 } else {
1780 req_update_card (control, index);
1781 }
1782 break;
1783 }
1784 }
1785
1786 static void
1787 gvc_mixer_control_ready (GvcMixerControl *control)
1788 {
1789 pa_operation *o;
1790
1791 pa_context_set_subscribe_callback (control->priv->pa_context,
1792 _pa_context_subscribe_cb,
1793 control);
1794 o = pa_context_subscribe (control->priv->pa_context,
1795 (pa_subscription_mask_t)
1796 (PA_SUBSCRIPTION_MASK_SINK|
1797 PA_SUBSCRIPTION_MASK_SOURCE|
1798 PA_SUBSCRIPTION_MASK_SINK_INPUT|
1799 PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
1800 PA_SUBSCRIPTION_MASK_CLIENT|
1801 PA_SUBSCRIPTION_MASK_SERVER|
1802 PA_SUBSCRIPTION_MASK_CARD),
1803 NULL,
1804 NULL);
1805
1806 if (o == NULL) {
1807 g_warning ("pa_context_subscribe() failed");
1808 return;
1809 }
1810 pa_operation_unref (o);
1811
1812 req_update_server_info (control, -1);
1813 req_update_client_info (control, -1);
1814 req_update_sink_info (control, -1);
1815 req_update_source_info (control, -1);
1816 req_update_sink_input_info (control, -1);
1817 req_update_source_output_info (control, -1);
1818 req_update_card (control, -1);
1819
1820 control->priv->n_outstanding = 6;
1821
1822 /* This call is not always supported */
1823 o = pa_ext_stream_restore_read (control->priv->pa_context,
1824 _pa_ext_stream_restore_read_cb,
1825 control);
1826 if (o != NULL) {
1827 pa_operation_unref (o);
1828 control->priv->n_outstanding++;
1829
1830 pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context,
1831 _pa_ext_stream_restore_subscribe_cb,
1832 control);
1833
1834 o = pa_ext_stream_restore_subscribe (control->priv->pa_context,
1835 1,
1836 NULL,
1837 NULL);
1838 if (o != NULL) {
1839 pa_operation_unref (o);
1840 }
1841
1842 } else {
1843 g_debug ("Failed to initialized stream_restore extension: %s",
1844 pa_strerror (pa_context_errno (control->priv->pa_context)));
1845 }
1846 }
1847
1848 static void
1849 gvc_mixer_new_pa_context (GvcMixerControl *self)
1850 {
1851 pa_proplist *proplist;
1852
1853 g_return_if_fail (self);
1854 g_return_if_fail (!self->priv->pa_context);
1855
1856 proplist = pa_proplist_new ();
1857 pa_proplist_sets (proplist,
1858 PA_PROP_APPLICATION_NAME,
1859 self->priv->name);
1860 pa_proplist_sets (proplist,
1861 PA_PROP_APPLICATION_ID,
1862 "org.gnome.VolumeControl");
1863 pa_proplist_sets (proplist,
1864 PA_PROP_APPLICATION_ICON_NAME,
1865 "multimedia-volume-control");
1866 pa_proplist_sets (proplist,
1867 PA_PROP_APPLICATION_VERSION,
1868 PACKAGE_VERSION);
1869
1870 self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist);
1871
1872 pa_proplist_free (proplist);
1873 g_assert (self->priv->pa_context);
1874 }
1875
1876 static void
1877 remove_all_streams (GvcMixerControl *control, GHashTable *hash_table)
1878 {
1879 GHashTableIter iter;
1880 gpointer key, value;
1881
1882 g_hash_table_iter_init (&iter, hash_table);
1883 while (g_hash_table_iter_next (&iter, &key, &value)) {
1884 remove_stream (control, value);
1885 g_hash_table_iter_remove (&iter);
1886 }
1887 }
1888
1889 static gboolean
1890 idle_reconnect (gpointer data)
1891 {
1892 GvcMixerControl *control = GVC_MIXER_CONTROL (data);
1893 GHashTableIter iter;
1894 gpointer key, value;
1895
1896 g_return_val_if_fail (control, FALSE);
1897
1898 if (control->priv->pa_context) {
1899 pa_context_unref (control->priv->pa_context);
1900 control->priv->pa_context = NULL;
1901 gvc_mixer_new_pa_context (control);
1902 }
1903
1904 remove_all_streams (control, control->priv->sinks);
1905 remove_all_streams (control, control->priv->sources);
1906 remove_all_streams (control, control->priv->sink_inputs);
1907 remove_all_streams (control, control->priv->source_outputs);
1908
1909 g_hash_table_iter_init (&iter, control->priv->clients);
1910 while (g_hash_table_iter_next (&iter, &key, &value))
1911 g_hash_table_iter_remove (&iter);
1912
1913 gvc_mixer_control_open (control); /* cannot fail */
1914
1915 control->priv->reconnect_id = 0;
1916 return FALSE;
1917 }
1918
1919 static void
1920 _pa_context_state_cb (pa_context *context,
1921 void *userdata)
1922 {
1923 GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
1924
1925 switch (pa_context_get_state (context)) {
1926 case PA_CONTEXT_UNCONNECTED:
1927 case PA_CONTEXT_CONNECTING:
1928 case PA_CONTEXT_AUTHORIZING:
1929 case PA_CONTEXT_SETTING_NAME:
1930 break;
1931
1932 case PA_CONTEXT_READY:
1933 gvc_mixer_control_ready (control);
1934 break;
1935
1936 case PA_CONTEXT_FAILED:
1937 control->priv->state = GVC_STATE_FAILED;
1938 g_signal_emit (control, signals[STATE_CHANGED], 0, GVC_STATE_FAILED);
1939 if (control->priv->reconnect_id == 0)
1940 control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control);
1941 break;
1942
1943 case PA_CONTEXT_TERMINATED:
1944 default:
1945 /* FIXME: */
1946 break;
1947 }
1948 }
1949
1950 gboolean
1951 gvc_mixer_control_open (GvcMixerControl *control)
1952 {
1953 int res;
1954
1955 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
1956 g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
1957 g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE);
1958
1959 pa_context_set_state_callback (control->priv->pa_context,
1960 _pa_context_state_cb,
1961 control);
1962
1963 control->priv->state = GVC_STATE_CONNECTING;
1964 g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CONNECTING);
1965 res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, NULL);
1966 if (res < 0) {
1967 g_warning ("Failed to connect context: %s",
1968 pa_strerror (pa_context_errno (control->priv->pa_context)));
1969 }
1970
1971 return res;
1972 }
1973
1974 gboolean
1975 gvc_mixer_control_close (GvcMixerControl *control)
1976 {
1977 g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
1978 g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
1979
1980 pa_context_disconnect (control->priv->pa_context);
1981
1982 control->priv->state = GVC_STATE_CLOSED;
1983 g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CLOSED);
1984 return TRUE;
1985 }
1986
1987 static void
1988 gvc_mixer_control_dispose (GObject *object)
1989 {
1990 GvcMixerControl *control = GVC_MIXER_CONTROL (object);
1991
1992 if (control->priv->reconnect_id != 0) {
1993 g_source_remove (control->priv->reconnect_id);
1994 control->priv->reconnect_id = 0;
1995 }
1996
1997 if (control->priv->pa_context != NULL) {
1998 pa_context_unref (control->priv->pa_context);
1999 control->priv->pa_context = NULL;
2000 }
2001
2002 if (control->priv->default_source_name != NULL) {
2003 g_free (control->priv->default_source_name);
2004 control->priv->default_source_name = NULL;
2005 }
2006 if (control->priv->default_sink_name != NULL) {
2007 g_free (control->priv->default_sink_name);
2008 control->priv->default_sink_name = NULL;
2009 }
2010
2011 if (control->priv->pa_mainloop != NULL) {
2012 pa_glib_mainloop_free (control->priv->pa_mainloop);
2013 control->priv->pa_mainloop = NULL;
2014 }
2015
2016 if (control->priv->all_streams != NULL) {
2017 g_hash_table_destroy (control->priv->all_streams);
2018 control->priv->all_streams = NULL;
2019 }
2020
2021 if (control->priv->sinks != NULL) {
2022 g_hash_table_destroy (control->priv->sinks);
2023 control->priv->sinks = NULL;
2024 }
2025 if (control->priv->sources != NULL) {
2026 g_hash_table_destroy (control->priv->sources);
2027 control->priv->sources = NULL;
2028 }
2029 if (control->priv->sink_inputs != NULL) {
2030 g_hash_table_destroy (control->priv->sink_inputs);
2031 control->priv->sink_inputs = NULL;
2032 }
2033 if (control->priv->source_outputs != NULL) {
2034 g_hash_table_destroy (control->priv->source_outputs);
2035 control->priv->source_outputs = NULL;
2036 }
2037 if (control->priv->clients != NULL) {
2038 g_hash_table_destroy (control->priv->clients);
2039 control->priv->clients = NULL;
2040 }
2041 if (control->priv->cards != NULL) {
2042 g_hash_table_destroy (control->priv->cards);
2043 control->priv->cards = NULL;
2044 }
2045
2046 G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object);
2047 }
2048
2049 static void
2050 gvc_mixer_control_set_property (GObject *object,
2051 guint prop_id,
2052 const GValue *value,
2053 GParamSpec *pspec)
2054 {
2055 GvcMixerControl *self = GVC_MIXER_CONTROL (object);
2056
2057 switch (prop_id) {
2058 case PROP_NAME:
2059 g_free (self->priv->name);
2060 self->priv->name = g_value_dup_string (value);
2061 g_object_notify (G_OBJECT (self), "name");
2062 break;
2063 default:
2064 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2065 break;
2066 }
2067 }
2068
2069 static void
2070 gvc_mixer_control_get_property (GObject *object,
2071 guint prop_id,
2072 GValue *value,
2073 GParamSpec *pspec)
2074 {
2075 GvcMixerControl *self = GVC_MIXER_CONTROL (object);
2076
2077 switch (prop_id) {
2078 case PROP_NAME:
2079 g_value_set_string (value, self->priv->name);
2080 break;
2081 default:
2082 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2083 break;
2084 }
2085 }
2086
2087
2088 static GObject *
2089 gvc_mixer_control_constructor (GType type,
2090 guint n_construct_properties,
2091 GObjectConstructParam *construct_params)
2092 {
2093 GObject *object;
2094 GvcMixerControl *self;
2095
2096 object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params);
2097
2098 self = GVC_MIXER_CONTROL (object);
2099
2100 gvc_mixer_new_pa_context (self);
2101
2102 return object;
2103 }
2104
2105 static void
2106 gvc_mixer_control_class_init (GvcMixerControlClass *klass)
2107 {
2108 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2109
2110 object_class->constructor = gvc_mixer_control_constructor;
2111 object_class->dispose = gvc_mixer_control_dispose;
2112 object_class->finalize = gvc_mixer_control_finalize;
2113 object_class->set_property = gvc_mixer_control_set_property;
2114 object_class->get_property = gvc_mixer_control_get_property;
2115
2116 g_object_class_install_property (object_class,
2117 PROP_NAME,
2118 g_param_spec_string ("name",
2119 "Name",
2120 "Name to display for this mixer control",
2121 NULL,
2122 G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
2123
2124 signals [STATE_CHANGED] =
2125 g_signal_new ("state-changed",
2126 G_TYPE_FROM_CLASS (klass),
2127 G_SIGNAL_RUN_LAST,
2128 G_STRUCT_OFFSET (GvcMixerControlClass, state_changed),
2129 NULL, NULL, NULL,
2130 G_TYPE_NONE, 1, G_TYPE_UINT);
2131 signals [STREAM_ADDED] =
2132 g_signal_new ("stream-added",
2133 G_TYPE_FROM_CLASS (klass),
2134 G_SIGNAL_RUN_LAST,
2135 G_STRUCT_OFFSET (GvcMixerControlClass, stream_added),
2136 NULL, NULL, NULL,
2137 G_TYPE_NONE, 1, G_TYPE_UINT);
2138 signals [STREAM_REMOVED] =
2139 g_signal_new ("stream-removed",
2140 G_TYPE_FROM_CLASS (klass),
2141 G_SIGNAL_RUN_LAST,
2142 G_STRUCT_OFFSET (GvcMixerControlClass, stream_removed),
2143 NULL, NULL, NULL,
2144 G_TYPE_NONE, 1, G_TYPE_UINT);
2145 signals [CARD_ADDED] =
2146 g_signal_new ("card-added",
2147 G_TYPE_FROM_CLASS (klass),
2148 G_SIGNAL_RUN_LAST,
2149 G_STRUCT_OFFSET (GvcMixerControlClass, card_added),
2150 NULL, NULL, NULL,
2151 G_TYPE_NONE, 1, G_TYPE_UINT);
2152 signals [CARD_REMOVED] =
2153 g_signal_new ("card-removed",
2154 G_TYPE_FROM_CLASS (klass),
2155 G_SIGNAL_RUN_LAST,
2156 G_STRUCT_OFFSET (GvcMixerControlClass, card_removed),
2157 NULL, NULL, NULL,
2158 G_TYPE_NONE, 1, G_TYPE_UINT);
2159 signals [DEFAULT_SINK_CHANGED] =
2160 g_signal_new ("default-sink-changed",
2161 G_TYPE_FROM_CLASS (klass),
2162 G_SIGNAL_RUN_LAST,
2163 G_STRUCT_OFFSET (GvcMixerControlClass, default_sink_changed),
2164 NULL, NULL, NULL,
2165 G_TYPE_NONE, 1, G_TYPE_UINT);
2166 signals [DEFAULT_SOURCE_CHANGED] =
2167 g_signal_new ("default-source-changed",
2168 G_TYPE_FROM_CLASS (klass),
2169 G_SIGNAL_RUN_LAST,
2170 G_STRUCT_OFFSET (GvcMixerControlClass, default_source_changed),
2171 NULL, NULL, NULL,
2172 G_TYPE_NONE, 1, G_TYPE_UINT);
2173
2174 g_type_class_add_private (klass, sizeof (GvcMixerControlPrivate));
2175 }
2176
2177 static void
2178 gvc_mixer_control_init (GvcMixerControl *control)
2179 {
2180 control->priv = GVC_MIXER_CONTROL_GET_PRIVATE (control);
2181
2182 control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ());
2183 g_assert (control->priv->pa_mainloop);
2184
2185 control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop);
2186 g_assert (control->priv->pa_api);
2187
2188 control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
2189 control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
2190 control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
2191 control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
2192 control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
2193 control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
2194
2195 control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
2196
2197 control->priv->state = GVC_STATE_CLOSED;
2198 }
2199
2200 static void
2201 gvc_mixer_control_finalize (GObject *object)
2202 {
2203 GvcMixerControl *mixer_control;
2204
2205 g_return_if_fail (object != NULL);
2206 g_return_if_fail (GVC_IS_MIXER_CONTROL (object));
2207
2208 mixer_control = GVC_MIXER_CONTROL (object);
2209 g_free (mixer_control->priv->name);
2210 mixer_control->priv->name = NULL;
2211
2212 g_return_if_fail (mixer_control->priv != NULL);
2213 G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object);
2214 }
2215
2216 GvcMixerControl *
2217 gvc_mixer_control_new (const char *name)
2218 {
2219 GObject *control;
2220 control = g_object_new (GVC_TYPE_MIXER_CONTROL,
2221 "name", name,
2222 NULL);
2223 return GVC_MIXER_CONTROL (control);
2224 }
2225
2226 /* FIXME: Remove when PA 0.9.23 is used */
2227 #ifndef PA_VOLUME_UI_MAX
2228 #define PA_VOLUME_UI_MAX pa_sw_volume_from_dB(+11.0)
2229 #endif
2230
2231 gdouble
2232 gvc_mixer_control_get_vol_max_norm (GvcMixerControl *control)
2233 {
2234 return (gdouble) PA_VOLUME_NORM;
2235 }
2236
2237 gdouble
2238 gvc_mixer_control_get_vol_max_amplified (GvcMixerControl *control)
2239 {
2240 return (gdouble) PA_VOLUME_UI_MAX;
2241 }