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 }