1 /*
2 Copyright 2011 Canonical Ltd.
3
4 Authors:
5 Conor Curran <conor.curran@canonical.com>
6
7 This program is free software: you can redistribute it and/or modify it
8 under the terms of the GNU General Public License version 3, as published
9 by the Free Software Foundation.
10
11 This program is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranties of
13 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14 PURPOSE. See the GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /**Notes
21 *
22 * Approach now is to set up the communication channels, query the server
23 * fetch its default sink/source. If this fails then fetch the list of sinks/sources
24 * and take the first one which is not the auto-null sink.
25 * TODO: need to handle the situation where one chink in this linear chain breaks
26 * i.e. start off the process again and count the attempts (note different to
27 reconnect attempts)
28 */
29 #include <pulse/gccmacro.h>
30 #include <pulse/glib-mainloop.h>
31 #include <pulse/error.h>
32
33 #include "pulseaudio-mgr.h"
34 #include "config.h"
35
36 #define RECONNECT_DELAY 5
37
38
39 static void pm_context_state_callback(pa_context *c, void *userdata);
40 static void pm_subscribed_events_callback (pa_context *c,
41 enum pa_subscription_event_type t,
42 uint32_t index,
43 void* userdata);
44 static void pm_server_info_callback (pa_context *c,
45 const pa_server_info *info,
46 void *userdata);
47 static void pm_default_sink_info_callback (pa_context *c,
48 const pa_sink_info *info,
49 int eol,
50 void *userdata);
51 static void pm_default_source_info_callback (pa_context *c,
52 const pa_source_info *info,
53 int eol,
54 void *userdata);
55 static void pm_sink_info_callback (pa_context *c,
56 const pa_sink_info *sink,
57 int eol,
58 void *userdata);
59 static void pm_source_info_callback (pa_context *c,
60 const pa_source_info *info,
61 int eol,
62 void *userdata);
63 static void pm_update_source_info_callback (pa_context *c,
64 const pa_source_info *info,
65 int eol,
66 void *userdata);
67 static void pm_sink_input_info_callback (pa_context *c,
68 const pa_sink_input_info *info,
69 int eol,
70 void *userdata);
71 static void pm_update_device (pa_context *c,
72 const pa_sink_info *info,
73 int eol,
74 void *userdata);
75 static void pm_toggle_mute_for_every_sink_callback (pa_context *c,
76 const pa_sink_info *sink,
77 int eol,
78 void* userdata);
79 static void pm_source_output_info_callback (pa_context *c,
80 const pa_source_output_info *info,
81 int eol,
82 void *userdata);
83
84 static gboolean reconnect_to_pulse (gpointer user_data);
85
86 static gint connection_attempts = 0;
87 static gint reconnect_idle_id = 0;
88 static pa_context *pulse_context = NULL;
89 static pa_glib_mainloop *pa_main_loop = NULL;
90
91 /**
92 Entry Point
93 **/
94 void
95 pm_establish_pulse_connection (Device* device)
96 {
97 pa_main_loop = pa_glib_mainloop_new (g_main_context_default ());
98 g_assert (pa_main_loop);
99 reconnect_to_pulse ((gpointer)device);
100 }
101
102 /**
103 close_pulse_activites()
104 Gracefully close our connection with the Pulse async library.
105 **/
106 void close_pulse_activites()
107 {
108 if (pulse_context != NULL) {
109 pa_context_unref(pulse_context);
110 pulse_context = NULL;
111 }
112 pa_glib_mainloop_free(pa_main_loop);
113 pa_main_loop = NULL;
114 }
115
116 /**
117 reconnect_to_pulse (gpointer user_data)
118 Method which connects to the pulse server and is used to track reconnects.
119 */
120 static gboolean
121 reconnect_to_pulse (gpointer user_data)
122 {
123 g_debug("Attempt a pulse connection");
124 // reset
125 g_return_val_if_fail (IS_DEVICE (user_data), FALSE);
126
127 connection_attempts += 1;
128 if (pulse_context != NULL) {
129 pa_context_unref(pulse_context);
130 pulse_context = NULL;
131 }
132
133 pa_proplist *proplist;
134
135 proplist = pa_proplist_new ();
136 pa_proplist_sets (proplist,
137 PA_PROP_APPLICATION_NAME,
138 "Indicator Sound");
139 pa_proplist_sets (proplist,
140 PA_PROP_APPLICATION_ID,
141 "com.canonical.indicator.sound");
142 pa_proplist_sets (proplist,
143 PA_PROP_APPLICATION_ICON_NAME,
144 "multimedia-volume-control");
145 pa_proplist_sets (proplist,
146 PA_PROP_APPLICATION_VERSION,
147 PACKAGE_VERSION);
148
149 pulse_context = pa_context_new_with_proplist (pa_glib_mainloop_get_api( pa_main_loop ),
150 NULL,
151 proplist);
152 pa_proplist_free (proplist);
153 g_assert(pulse_context);
154 pa_context_set_state_callback (pulse_context,
155 pm_context_state_callback,
156 user_data);
157 int result = pa_context_connect (pulse_context,
158 NULL,
159 (pa_context_flags_t)PA_CONTEXT_NOFAIL,
160 NULL);
161
162 if (result < 0) {
163 g_warning ("Failed to connect context: %s",
164 pa_strerror (pa_context_errno (pulse_context)));
165 }
166 if (connection_attempts > 5){
167 return FALSE;
168 }
169 else{
170 return TRUE;
171 }
172 }
173
174 void
CID 10620 - PASS_BY_VALUE
Passing parameter new_volume of size 132 bytes by value.
175 pm_update_volume (gint sink_index, pa_cvolume new_volume)
176 {
177 // LP: #850662
178 if (sink_index < 0 || pulse_context == NULL){
179 return;
180 }
181 pa_operation_unref (pa_context_set_sink_volume_by_index (pulse_context,
182 sink_index,
183 &new_volume,
184 NULL,
185 NULL) );
186 }
187
188 void
189 pm_update_mute (gboolean update)
190 {
191 pa_operation_unref (pa_context_get_sink_info_list (pulse_context,
192 pm_toggle_mute_for_every_sink_callback,
193 GINT_TO_POINTER (update)));
194 }
195
196 void
197 pm_update_mic_gain (gint source_index, pa_cvolume new_gain)
198 {
199 // LP: #850662
200 if (source_index < 0 || pulse_context == NULL){
201 return;
202 }
203 pa_operation_unref (pa_context_set_source_volume_by_index (pulse_context,
204 source_index,
205 &new_gain,
206 NULL,
207 NULL) );
208 }
209
210 void
211 pm_update_mic_mute (gint source_index, gint mute_update)
212 {
213 // LP: #850662
214 if (source_index < 0){
215 return;
216 }
217 pa_operation_unref (pa_context_set_source_mute_by_index (pulse_context,
218 source_index,
219 mute_update,
220 NULL,
221 NULL));
222 }
223 /**********************************************************************************************************************/
224 // Pulse-Audio asychronous call-backs
225 /**********************************************************************************************************************/
226
227
228 static void
229 pm_subscribed_events_callback (pa_context *c,
230 enum pa_subscription_event_type t,
231 uint32_t index,
232 void* userdata)
233 {
234 if (IS_DEVICE (userdata) == FALSE){
235 g_critical ("subscribed events callback - our userdata is not what we think it should be");
236 return;
237 }
238 Device* sink = DEVICE (userdata);
239
240 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
241 case PA_SUBSCRIPTION_EVENT_SINK:
242
243 // We don't care about any other sink other than the active one.
244 if (index != device_get_sink_index (sink))
245 return;
246
247 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
248 device_sink_deactivated (sink);
249
250 }
251 else{
252 pa_operation_unref (pa_context_get_sink_info_by_index (c,
253 index,
254 pm_update_device,
255 userdata) );
256 }
257 break;
258 case PA_SUBSCRIPTION_EVENT_SOURCE:
259 g_debug ("Looks like source event of some description - index = %i", index);
260 // We don't care about any other sink other than the active one.
261 if (index != device_get_source_index (sink))
262 return;
263 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
264 g_debug ("Source removal event - index = %i", index);
265 device_deactivate_voip_source (sink, FALSE);
266 }
267 else{
268 pa_operation_unref (pa_context_get_source_info_by_index (c,
269 index,
270 pm_update_source_info_callback,
271 userdata) );
272 }
273 break;
274 case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
275 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
276 g_debug ("some new sink input event ? - index = %i", index);
277 // Maybe blocking state ?.
278 pa_operation_unref (pa_context_get_sink_input_info (c,
279 index,
280 pm_sink_input_info_callback, userdata));
281 }
282 break;
283 case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
284 g_debug ("source output event");
285 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
286 gint cached_source_output_index = device_get_voip_source_output_index (sink);
287 if (index == cached_source_output_index){
288 g_debug ("Just saw a source output removal event - index = %i and cached index = %i", index, cached_source_output_index);
289 device_deactivate_voip_client (sink);
290 }
291 }
292 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
293 g_debug ("some new source output event ? - index = %i", index);
294 // Determine if its a VOIP app.
295 pa_operation_unref (pa_context_get_source_output_info (c,
296 index,
297 pm_source_output_info_callback, userdata));
298 }
299 break;
300 case PA_SUBSCRIPTION_EVENT_SERVER:
301 g_debug("PA_SUBSCRIPTION_EVENT_SERVER event triggered.");
302 pa_operation *o;
303 if (!(o = pa_context_get_server_info (c, pm_server_info_callback, userdata))) {
304 g_warning("subscribed_events_callback - pa_context_get_server_info() failed");
305 return;
306 }
307 pa_operation_unref(o);
308 break;
309 }
310 }
311
312
313
314 static void
315 pm_context_state_callback (pa_context *c, void *userdata)
316 {
317 switch (pa_context_get_state(c)) {
318 case PA_CONTEXT_UNCONNECTED:
319 g_debug("unconnected");
320 break;
321 case PA_CONTEXT_CONNECTING:
322 g_debug("connecting - waiting for the server to become available");
323 break;
324 case PA_CONTEXT_AUTHORIZING:
325 g_debug ("Authorizing");
326 break;
327 case PA_CONTEXT_SETTING_NAME:
328 g_debug ("Setting name");
329 break;
330 case PA_CONTEXT_FAILED:
331 g_warning("PA_CONTEXT_FAILED - Is PulseAudio Daemon running ?");
332 device_sink_deactivated (DEVICE (userdata));
333 if (reconnect_idle_id == 0){
334 reconnect_idle_id = g_timeout_add_seconds (RECONNECT_DELAY,
335 reconnect_to_pulse,
336 (gpointer)userdata);
337 }
338 break;
339 case PA_CONTEXT_TERMINATED:
340 g_debug ("Terminated");
341 break;
342 case PA_CONTEXT_READY:
343 connection_attempts = 0;
344 g_debug("PA_CONTEXT_READY");
345 if (reconnect_idle_id != 0){
346 g_source_remove (reconnect_idle_id);
347 reconnect_idle_id = 0;
348 }
349 pa_operation *o;
350
351 pa_context_set_subscribe_callback(c, pm_subscribed_events_callback, userdata);
352
353 if (!(o = pa_context_subscribe (c, (pa_subscription_mask_t)
354 (PA_SUBSCRIPTION_MASK_SINK|
355 PA_SUBSCRIPTION_MASK_SOURCE|
356 PA_SUBSCRIPTION_MASK_SINK_INPUT|
357 PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
358 PA_SUBSCRIPTION_MASK_SERVER), NULL, NULL))) {
359 g_warning("pa_context_subscribe() failed");
360
361 }
362
363 if (!(o = pa_context_get_server_info (c, pm_server_info_callback, userdata))) {
364 g_warning("Initial - pa_context_get_server_info() failed");
365 }
366 pa_operation_unref(o);
367
368 break;
369 }
370 }
371
372 /**
373 After startup we go straight for the server info to see if it has details of
374 the default sink and source. Normally these are valid, if there is none set
375 fetch the list of each and try to determine the sink.
376 **/
377 static void
378 pm_server_info_callback (pa_context *c,
379 const pa_server_info *info,
380 void *userdata)
381 {
382 pa_operation *operation;
383 g_debug ("server info callback");
384
385 if (info == NULL) {
386 g_warning("No PA server - get the hell out of here");
387 device_sink_deactivated (DEVICE (userdata));
388 return;
389 }
390 // Go for the default sink
391 if (info->default_sink_name != NULL) {
392 g_debug ("default sink name from the server ain't null'");
393 if (!(operation = pa_context_get_sink_info_by_name (c,
394 info->default_sink_name,
395 pm_default_sink_info_callback,
396 userdata) )) {
397 g_warning("pa_context_get_sink_info_by_namet() failed");
398 device_sink_deactivated (DEVICE (userdata));
399 pa_operation_unref(operation);
400 return;
401 }
402 } // If there is no default sink, try to determine a sink from the list of sinks
403 else if (!(operation = pa_context_get_sink_info_list(c,
404 pm_sink_info_callback,
405 userdata))) {
406 g_warning("pa_context_get_sink_info_list() failed");
407 device_sink_deactivated (DEVICE (userdata));
408 pa_operation_unref(operation);
409 return;
410 }
411 // And the source
412 if (info->default_source_name != NULL) {
413 g_debug ("default source name from the server is not null'");
414 if (!(operation = pa_context_get_source_info_by_name (c,
415 info->default_source_name,
416 pm_default_source_info_callback,
417 userdata) )) {
418 g_warning("pa_context_get_default_source_info() failed");
419 // TODO: call some input deactivate method on active sink
420 pa_operation_unref(operation);
421 return;
422 }
423 }
424 else if (!(operation = pa_context_get_source_info_list(c,
425 pm_source_info_callback,
426 userdata))) {
427 g_warning("pa_context_get_sink_info_list() failed");
428 // TODO: call some input deactivate method for the source
429 }
430 pa_operation_unref(operation);
431 }
432
433 // If the server doesn't have a default sink to give us
434 // we should attempt to pick up the first of the list of sinks which doesn't have
435 // the name 'auto_null' (that was all really I was doing before)
436 static void
437 pm_sink_info_callback (pa_context *c,
438 const pa_sink_info *sink,
439 int eol,
440 void* userdata)
441 {
442 if (eol > 0) {
443 return;
444 }
445 else {
446 if (IS_DEVICE (userdata) == FALSE || sink == NULL){
447 g_warning ("sink info callback - our user data is not what we think it should be or the sink parameter is null");
448 return;
449 }
450 Device* a_sink = DEVICE (userdata);
451 if (device_is_sink_populated (a_sink) == FALSE &&
452 g_ascii_strncasecmp("auto_null", sink->name, 9) != 0){
453 device_sink_populate (a_sink, sink);
454 }
455 }
456 }
457
458 static void
459 pm_default_sink_info_callback (pa_context *c,
460 const pa_sink_info *info,
461 int eol,
462 void *userdata)
463 {
464 if (eol > 0) {
465 return;
466 }
467 else {
468 if (IS_DEVICE (userdata) == FALSE || info == NULL){
469 g_warning ("Default sink info callback - our user data is not what we think it should be or the info parameter is null");
470 return;
471 }
472 // Only repopulate if there is a change with regards the index
473 if (device_get_sink_index (DEVICE (userdata)) == info->index)
474 return;
475
476 g_debug ("Pulse Server has handed us a new default sink");
477 device_sink_populate (DEVICE (userdata), info);
478 }
479 }
480
481 static void
482 pm_sink_input_info_callback (pa_context *c,
483 const pa_sink_input_info *info,
484 int eol,
485 void *userdata)
486 {
487 if (eol > 0) {
488 return;
489 }
490 else {
491 if (info == NULL || IS_DEVICE (userdata) == FALSE) {
492 g_warning("Sink input info callback : SINK INPUT INFO IS NULL or our user_data is not what we think it should be");
493 return;
494 }
495 Device* a_sink = DEVICE (userdata);
496 // And finally check for the mute blocking state
497 if (device_get_sink_index (a_sink) == info->sink){
498 device_determine_blocking_state (a_sink);
499 }
500 }
501 }
502
503 static void
504 pm_source_output_info_callback (pa_context *c,
505 const pa_source_output_info *info,
506 int eol,
507 void *userdata)
508 {
509 if (eol > 0) {
510 return;
511 }
512 else {
513 if (info == NULL || IS_DEVICE (userdata) == FALSE) {
514 g_warning("Source output callback: SOURCE OUTPUT INFO IS NULL or our user_data is not what we think it should be");
515 return;
516 }
517
518 // Check if this is Voip sink input
519 gint result = pa_proplist_contains (info->proplist, PA_PROP_MEDIA_ROLE);
520 Device* a_sink = DEVICE (userdata);
521
522 if (result == 1){
523 //g_debug ("Source output info has media role property");
524 const char* value = pa_proplist_gets (info->proplist, PA_PROP_MEDIA_ROLE);
525 //g_debug ("prop role = %s", value);
526 if (g_strcmp0 (value, "phone") == 0 || g_strcmp0 (value, "production") == 0) {
527 g_debug ("We have a VOIP/PRODUCTION ! - index = %i", info->index);
528 device_activate_voip_item (a_sink, (gint)info->index, (gint)info->client);
529 // TODO to start with we will assume our source is the same as what this 'client'
530 // is pointing at. This should probably be more intelligent :
531 // query for the list of source output info's and going on the name of the client
532 // from the sink input ensure our voip item is using the right source.
533 }
534 }
535 }
536 }
537
538 static void
539 pm_update_device (pa_context *c,
540 const pa_sink_info *info,
541 int eol,
542 void *userdata)
543 {
544 if (eol > 0) {
545 return;
546 }
547 else{
548 if (IS_DEVICE (userdata) == FALSE || info == NULL){
549 g_warning ("update_device - our user data is not what we think it should be or the info parameter is null");
550 return;
551 }
552 device_sink_update (DEVICE(userdata), info);
553 }
554 }
555
556 static void
557 pm_toggle_mute_for_every_sink_callback (pa_context *c,
558 const pa_sink_info *sink,
559 int eol,
560 void* userdata)
561 {
562 if (eol > 0) {
563 return;
564 }
565 else {
566 if (sink == NULL) {
567 g_warning ("toggle_mute cb - sink parameter is null - why ?");
568 return;
569 }
570 pa_operation_unref (pa_context_set_sink_mute_by_index (c,
571 sink->index,
572 GPOINTER_TO_INT(userdata),
573 NULL,
574 NULL));
575 }
576 }
577
578 // Source info related callbacks
579 static void
580 pm_default_source_info_callback (pa_context *c,
581 const pa_source_info *info,
582 int eol,
583 void *userdata)
584 {
585 if (eol > 0) {
586 return;
587 }
588 else {
589 if (IS_DEVICE (userdata) == FALSE || info == NULL){
590 g_warning ("Default source info callback - our user data is not what we think it should be or the source info parameter is null");
591 return;
592 }
593 // If there is an index change we need to change our cached source
594 if (device_get_source_index (DEVICE (userdata)) == info->index)
595 return;
596 g_debug ("Pulse Server has handed us a new default source");
597 device_deactivate_voip_source (DEVICE (userdata), TRUE);
598 device_update_voip_input_source (DEVICE (userdata), info);
599 }
600 }
601
602 static void
603 pm_source_info_callback (pa_context *c,
604 const pa_source_info *info,
605 int eol,
606 void *userdata)
607 {
608 if (eol > 0) {
609 return;
610 }
611 else {
612 if (IS_DEVICE (userdata) == FALSE || info == NULL){
613 g_warning ("source info callback - our user data is not what we think it should be or the source info parameter is null");
614 return;
615 }
616 // For now we will take the first available
617 if (device_is_voip_source_populated (DEVICE (userdata)) == FALSE){
618 device_update_voip_input_source (DEVICE (userdata), info);
619 }
620 }
621 }
622
623 static void
624 pm_update_source_info_callback (pa_context *c,
625 const pa_source_info *info,
626 int eol,
627 void *userdata)
628 {
629 if (eol > 0) {
630 return;
631 }
632 else {
633 if (IS_DEVICE (userdata) == FALSE || info == NULL ){
634 g_warning ("source info update callback - our user data is not what we think it should be or the source info paramter is null");
635 return;
636 }
637 g_debug ("Got a source update for %s , index %i", info->name, info->index);
638 device_update_voip_input_source (DEVICE (userdata), info);
639 }
640 }