]> git.0d.be Git - empathy.git/blob - src/empathy-mic-monitor.c
ef5b93d0d6bb89b2dd59bea2c27870b1edd0909d
[empathy.git] / src / empathy-mic-monitor.c
1 /*
2  * Copyright (C) 2011 Collabora Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  */
19
20 #include "config.h"
21
22 #include <gtk/gtk.h>
23 #include <pulse/glib-mainloop.h>
24
25 #include "empathy-mic-monitor.h"
26
27 #include "libempathy/empathy-utils.h"
28
29 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
30 #include "libempathy/empathy-debug.h"
31
32 enum
33 {
34   MICROPHONE_ADDED,
35   MICROPHONE_REMOVED,
36   MICROPHONE_CHANGED,
37   LAST_SIGNAL
38 };
39
40 static guint signals[LAST_SIGNAL] = {0};
41
42 struct _EmpathyMicMonitorPrivate
43 {
44   pa_glib_mainloop *loop;
45   pa_context *context;
46   GQueue *operations;
47 };
48
49 G_DEFINE_TYPE (EmpathyMicMonitor, empathy_mic_monitor, G_TYPE_OBJECT);
50
51 typedef void (*OperationFunc) (EmpathyMicMonitor *, GSimpleAsyncResult *);
52
53 typedef struct
54 {
55   OperationFunc func;
56   GSimpleAsyncResult *result;
57 } Operation;
58
59 static Operation *
60 operation_new (OperationFunc func,
61     GSimpleAsyncResult *result)
62 {
63   Operation *o = g_slice_new0 (Operation);
64
65   o->func = func;
66   o->result = result;
67
68   return o;
69 }
70
71 static void
72 operation_free (Operation *o,
73     gboolean cancelled)
74 {
75   if (cancelled)
76     {
77       g_simple_async_result_set_error (o->result,
78           G_IO_ERROR, G_IO_ERROR_CANCELLED,
79           "The microphone monitor was disposed");
80       g_simple_async_result_complete (o->result);
81       g_object_unref (o->result);
82     }
83
84   g_slice_free (Operation, o);
85 }
86
87 static void
88 operations_run (EmpathyMicMonitor *self)
89 {
90   EmpathyMicMonitorPrivate *priv = self->priv;
91   pa_context_state_t state = pa_context_get_state (priv->context);
92   GList *l;
93
94   if (state != PA_CONTEXT_READY)
95     return;
96
97   for (l = priv->operations->head; l != NULL; l = l->next)
98     {
99       Operation *o = l->data;
100
101       o->func (self, o->result);
102
103       operation_free (o, FALSE);
104     }
105
106   g_queue_clear (priv->operations);
107 }
108
109 static void
110 empathy_mic_monitor_source_output_info_cb (pa_context *context,
111     const pa_source_output_info *info,
112     int eol,
113     void *userdata)
114 {
115   EmpathyMicMonitor *self = userdata;
116
117   if (eol)
118     return;
119
120   g_signal_emit (self, signals[MICROPHONE_CHANGED], 0,
121       info->index, info->source);
122 }
123
124 static void
125 empathy_mic_monitor_source_info_cb (pa_context *context,
126     const pa_source_info *info,
127     int eol,
128     void *userdata)
129 {
130   EmpathyMicMonitor *self = userdata;
131   gboolean is_monitor;
132
133   if (eol)
134     return;
135
136   is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX);
137
138   g_signal_emit (self, signals[MICROPHONE_ADDED], 0,
139       info->index, info->name, info->description, is_monitor);
140 }
141
142 static void
143 empathy_mic_monitor_pa_event_cb (pa_context *context,
144     pa_subscription_event_type_t type,
145     uint32_t idx,
146     void *userdata)
147 {
148   EmpathyMicMonitor *self = userdata;
149
150   if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
151       && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE)
152     {
153       /* Microphone in the source output has changed */
154       pa_context_get_source_output_info (context, idx,
155           empathy_mic_monitor_source_output_info_cb, self);
156     }
157   else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE
158       && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
159     {
160       /* A mic has been removed */
161       g_signal_emit (self, signals[MICROPHONE_REMOVED], 0, idx);
162     }
163   else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE
164       && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)
165     {
166       /* A mic has been plugged in */
167       pa_context_get_source_info_by_index (context, idx,
168           empathy_mic_monitor_source_info_cb, self);
169     }
170 }
171
172 static void
173 empathy_mic_monitor_pa_subscribe_cb (pa_context *context,
174     int success,
175     void *userdata)
176 {
177   if (!success)
178     DEBUG ("Failed to subscribe to PulseAudio events");
179 }
180
181 static void
182 empathy_mic_monitor_pa_state_change_cb (pa_context *context,
183     void *userdata)
184 {
185   EmpathyMicMonitor *self = userdata;
186   EmpathyMicMonitorPrivate *priv = self->priv;
187   pa_context_state_t state = pa_context_get_state (priv->context);
188
189   if (state == PA_CONTEXT_READY)
190     {
191       /* Listen to pulseaudio events so we know when sources are
192        * added and when the microphone is changed. */
193       pa_context_set_subscribe_callback (priv->context,
194           empathy_mic_monitor_pa_event_cb, self);
195       pa_context_subscribe (priv->context,
196           PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT,
197           empathy_mic_monitor_pa_subscribe_cb, NULL);
198
199       operations_run (self);
200     }
201 }
202
203 static void
204 empathy_mic_monitor_init (EmpathyMicMonitor *self)
205 {
206   EmpathyMicMonitorPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
207     EMPATHY_TYPE_MIC_MONITOR, EmpathyMicMonitorPrivate);
208
209   self->priv = priv;
210 }
211
212 static void
213 empathy_mic_monitor_constructed (GObject *obj)
214 {
215   EmpathyMicMonitor *self = EMPATHY_MIC_MONITOR (obj);
216   EmpathyMicMonitorPrivate *priv = self->priv;
217
218   /* PulseAudio stuff: We need to create a dummy pa_glib_mainloop* so
219    * Pulse can use the mainloop that GTK has created for us. */
220   priv->loop = pa_glib_mainloop_new (NULL);
221   priv->context = pa_context_new (pa_glib_mainloop_get_api (priv->loop),
222       "EmpathyMicMonitor");
223
224   /* Finally listen for state changes so we know when we've
225    * connected. */
226   pa_context_set_state_callback (priv->context,
227       empathy_mic_monitor_pa_state_change_cb, obj);
228   pa_context_connect (priv->context, NULL, 0, NULL);
229
230   priv->operations = g_queue_new ();
231 }
232
233 static void
234 empathy_mic_monitor_dispose (GObject *obj)
235 {
236   EmpathyMicMonitor *self = EMPATHY_MIC_MONITOR (obj);
237   EmpathyMicMonitorPrivate *priv = self->priv;
238
239   g_queue_foreach (priv->operations, (GFunc) operation_free,
240       GUINT_TO_POINTER (TRUE));
241   g_queue_free (priv->operations);
242
243   if (priv->context != NULL)
244     pa_context_unref (priv->context);
245   priv->context = NULL;
246
247   if (priv->loop != NULL)
248     pa_glib_mainloop_free (priv->loop);
249   priv->loop = NULL;
250
251   G_OBJECT_CLASS (empathy_mic_monitor_parent_class)->dispose (obj);
252 }
253
254 static void
255 empathy_mic_monitor_class_init (EmpathyMicMonitorClass *klass)
256 {
257   GObjectClass *object_class = G_OBJECT_CLASS (klass);
258
259   object_class->constructed = empathy_mic_monitor_constructed;
260   object_class->dispose = empathy_mic_monitor_dispose;
261
262   signals[MICROPHONE_ADDED] = g_signal_new ("microphone-added",
263     G_TYPE_FROM_CLASS (klass),
264     G_SIGNAL_RUN_LAST,
265     0,
266     NULL, NULL,
267     g_cclosure_marshal_generic,
268     G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
269
270   signals[MICROPHONE_REMOVED] = g_signal_new ("microphone-removed",
271     G_TYPE_FROM_CLASS (klass),
272     G_SIGNAL_RUN_LAST,
273     0,
274     NULL, NULL,
275     g_cclosure_marshal_generic,
276     G_TYPE_NONE, 1, G_TYPE_UINT);
277
278   signals[MICROPHONE_CHANGED] = g_signal_new ("microphone-changed",
279     G_TYPE_FROM_CLASS (klass),
280     G_SIGNAL_RUN_LAST,
281     0,
282     NULL, NULL,
283     g_cclosure_marshal_generic,
284     G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
285
286   g_type_class_add_private (object_class, sizeof (EmpathyMicMonitorPrivate));
287 }
288
289 EmpathyMicMonitor *
290 empathy_mic_monitor_new (void)
291 {
292   return g_object_new (EMPATHY_TYPE_MIC_MONITOR,
293       NULL);
294 }
295
296 /* operation: list microphones */
297 static void
298 operation_list_microphones_free (gpointer data)
299 {
300   GQueue *queue = data;
301   GList *l;
302
303   for (l = queue->head; l != NULL; l = l->next)
304     {
305       EmpathyMicrophone *mic = l->data;
306
307       g_free (mic->name);
308       g_free (mic->description);
309       g_slice_free (EmpathyMicrophone, mic);
310     }
311
312   g_queue_free (queue);
313 }
314
315 static void
316 operation_list_microphones_cb (pa_context *context,
317     const pa_source_info *info,
318     int eol,
319     void *userdata)
320 {
321   GSimpleAsyncResult *result = userdata;
322   EmpathyMicrophone *mic;
323   GQueue *queue;
324
325   if (eol)
326     {
327       g_simple_async_result_complete (result);
328       g_object_unref (result);
329       return;
330     }
331
332   mic = g_slice_new0 (EmpathyMicrophone);
333   mic->index = info->index;
334   mic->name = g_strdup (info->name);
335   mic->description = g_strdup (info->description);
336   mic->is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX);
337
338   /* add it to the queue */
339   queue = g_simple_async_result_get_op_res_gpointer (result);
340   g_queue_push_tail (queue, mic);
341 }
342
343 static void
344 operation_list_microphones (EmpathyMicMonitor *self,
345     GSimpleAsyncResult *result)
346 {
347   EmpathyMicMonitorPrivate *priv = self->priv;
348
349   g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY);
350
351   g_simple_async_result_set_op_res_gpointer (result, g_queue_new (),
352       operation_list_microphones_free);
353
354   pa_context_get_source_info_list (priv->context,
355       operation_list_microphones_cb, result);
356 }
357
358 void
359 empathy_mic_monitor_list_microphones_async (EmpathyMicMonitor *self,
360     GAsyncReadyCallback callback,
361     gpointer user_data)
362 {
363 EmpathyMicMonitorPrivate *priv = self->priv;
364   Operation *operation;
365   GSimpleAsyncResult *simple;
366
367   simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
368       empathy_mic_monitor_list_microphones_async);
369
370   operation = operation_new (operation_list_microphones, simple);
371   g_queue_push_tail (priv->operations, operation);
372
373   /* gogogogo */
374   operations_run (self);
375 }
376
377 const GList *
378 empathy_mic_monitor_list_microphones_finish (EmpathyMicMonitor *src,
379     GAsyncResult *result,
380     GError **error)
381 {
382   GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
383   GQueue *queue;
384
385   if (g_simple_async_result_propagate_error (simple, error))
386       return NULL;
387
388   g_return_val_if_fail (g_simple_async_result_is_valid (result,
389           G_OBJECT (src), empathy_mic_monitor_list_microphones_async),
390       NULL);
391
392   queue = g_simple_async_result_get_op_res_gpointer (simple);
393   return queue->head;
394 }
395
396 /* operation: change microphone */
397 typedef struct
398 {
399   guint source_output_idx;
400   guint source_idx;
401 } ChangeMicrophoneData;
402
403 static void
404 operation_change_microphone_cb (pa_context *context,
405     int success,
406     void *userdata)
407 {
408   GSimpleAsyncResult *result = userdata;
409
410   if (!success)
411     {
412       g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_FAILED,
413           "Failed to change microphone. Reason unknown.");
414     }
415
416   g_simple_async_result_complete (result);
417   g_object_unref (result);
418 }
419
420 static void
421 operation_change_microphone (EmpathyMicMonitor *self,
422     GSimpleAsyncResult *result)
423 {
424   EmpathyMicMonitorPrivate *priv = self->priv;
425   ChangeMicrophoneData *data;
426
427   g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY);
428
429   data = g_simple_async_result_get_op_res_gpointer (result);
430
431   pa_context_move_source_output_by_index (priv->context,
432       data->source_output_idx, data->source_idx,
433       operation_change_microphone_cb, result);
434
435   g_simple_async_result_set_op_res_gpointer (result, NULL, NULL);
436   g_slice_free (ChangeMicrophoneData, data);
437 }
438
439 void
440 empathy_mic_monitor_change_microphone_async (EmpathyMicMonitor *self,
441     guint source_output_idx,
442     guint source_idx,
443     GAsyncReadyCallback callback,
444     gpointer user_data)
445 {
446   EmpathyMicMonitorPrivate *priv = self->priv;
447   GSimpleAsyncResult *simple;
448   Operation *operation;
449   ChangeMicrophoneData *data;
450
451   simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
452       empathy_mic_monitor_change_microphone_async);
453
454   if (source_output_idx == PA_INVALID_INDEX)
455     {
456       g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED,
457           "Invalid source output index");
458       g_simple_async_result_complete_in_idle (simple);
459       g_object_unref (simple);
460       return;
461     }
462
463   data = g_slice_new0 (ChangeMicrophoneData);
464   data->source_idx = source_idx;
465   data->source_output_idx = source_output_idx;
466   g_simple_async_result_set_op_res_gpointer (simple, data, NULL);
467
468   operation = operation_new (operation_change_microphone, simple);
469   g_queue_push_tail (priv->operations, operation);
470
471   /* gogogogo */
472   operations_run (self);
473 }
474
475 gboolean
476 empathy_mic_monitor_change_microphone_finish (EmpathyMicMonitor *self,
477     GAsyncResult *result,
478     GError **error)
479 {
480   empathy_implement_finish_void (self,
481       empathy_mic_monitor_change_microphone_async);
482 }
483
484 /* operation: get current mic */
485 static void
486 empathy_mic_monitor_get_current_mic_cb (pa_context *context,
487     const pa_source_output_info *info,
488     int eol,
489     void *userdata)
490 {
491   GSimpleAsyncResult *result = userdata;
492
493   if (eol)
494     return;
495
496   if (g_simple_async_result_get_op_res_gpointer (result) != NULL)
497     return;
498
499   g_simple_async_result_set_op_res_gpointer (result,
500       GUINT_TO_POINTER (info->source), NULL);
501   g_simple_async_result_complete (result);
502   g_object_unref (result);
503 }
504
505 static void
506 operation_get_current_mic (EmpathyMicMonitor *self,
507     GSimpleAsyncResult *result)
508 {
509   EmpathyMicMonitorPrivate *priv = self->priv;
510   guint source_output_idx;
511
512   g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY);
513
514   source_output_idx = GPOINTER_TO_UINT (
515       g_simple_async_result_get_op_res_gpointer (result));
516
517   /* unset this so we can use it in the cb */
518   g_simple_async_result_set_op_res_gpointer (result, NULL, NULL);
519
520   pa_context_get_source_output_info (priv->context, source_output_idx,
521       empathy_mic_monitor_get_current_mic_cb, result);
522 }
523
524 void
525 empathy_mic_monitor_get_current_mic_async (EmpathyMicMonitor *self,
526     guint source_output_idx,
527     GAsyncReadyCallback callback,
528     gpointer user_data)
529 {
530   EmpathyMicMonitorPrivate *priv = self->priv;
531   Operation *operation;
532   GSimpleAsyncResult *simple;
533
534   simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
535       empathy_mic_monitor_get_current_mic_async);
536
537   g_simple_async_result_set_op_res_gpointer (simple,
538       GUINT_TO_POINTER (source_output_idx), NULL);
539
540   operation = operation_new (operation_get_current_mic, simple);
541   g_queue_push_tail (priv->operations, operation);
542
543   operations_run (self);
544 }
545
546 guint
547 empathy_mic_monitor_get_current_mic_finish (EmpathyMicMonitor *self,
548     GAsyncResult *result,
549     GError **error)
550 {
551   GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
552
553   if (g_simple_async_result_propagate_error (simple, error))
554     return PA_INVALID_INDEX;
555
556   g_return_val_if_fail (g_simple_async_result_is_valid (result,
557           G_OBJECT (self), empathy_mic_monitor_get_current_mic_async),
558       PA_INVALID_INDEX);
559
560   return GPOINTER_TO_UINT (
561       g_simple_async_result_get_op_res_gpointer (simple));
562 }
563
564 /* operation: get default */
565 static void
566 empathy_mic_monitor_get_default_cb (pa_context *context,
567     const pa_server_info *info,
568     void *userdata)
569 {
570   GSimpleAsyncResult *result = userdata;
571
572   /* TODO: it would be nice in future, for consistency, if this gave
573    * the source idx instead of the name. */
574   g_simple_async_result_set_op_res_gpointer (result,
575       g_strdup (info->default_source_name), g_free);
576   g_simple_async_result_complete (result);
577   g_object_unref (result);
578 }
579
580 static void
581 operation_get_default (EmpathyMicMonitor *self,
582     GSimpleAsyncResult *result)
583 {
584   EmpathyMicMonitorPrivate *priv = self->priv;
585
586   g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY);
587
588   /* unset this so we can use it in the cb */
589   g_simple_async_result_set_op_res_gpointer (result, NULL, NULL);
590
591   pa_context_get_server_info (priv->context, empathy_mic_monitor_get_default_cb,
592       result);
593 }
594
595 void
596 empathy_mic_monitor_get_default_async (EmpathyMicMonitor *self,
597     GAsyncReadyCallback callback,
598     gpointer user_data)
599 {
600   EmpathyMicMonitorPrivate *priv = self->priv;
601   Operation *operation;
602   GSimpleAsyncResult *simple;
603
604   simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
605       empathy_mic_monitor_get_default_async);
606
607   operation = operation_new (operation_get_default, simple);
608   g_queue_push_tail (priv->operations, operation);
609
610   operations_run (self);
611 }
612
613 const gchar *
614 empathy_mic_monitor_get_default_finish (EmpathyMicMonitor *self,
615     GAsyncResult *result,
616     GError **error)
617 {
618   empathy_implement_finish_return_pointer (self,
619       empathy_mic_monitor_get_default_async);
620 }
621
622 /* operation: set default */
623 static void
624 empathy_mic_monitor_set_default_cb (pa_context *c,
625     int success,
626     void *userdata)
627 {
628   GSimpleAsyncResult *result = userdata;
629
630   if (!success)
631     {
632       g_simple_async_result_set_error (result,
633           G_IO_ERROR, G_IO_ERROR_FAILED,
634           "The operation failed for an unknown reason");
635     }
636
637   g_simple_async_result_complete (result);
638   g_object_unref (result);
639 }
640
641 static void
642 operation_set_default (EmpathyMicMonitor *self,
643     GSimpleAsyncResult *result)
644 {
645   EmpathyMicMonitorPrivate *priv = self->priv;
646   gchar *name;
647
648   g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY);
649
650   name = g_simple_async_result_get_op_res_gpointer (result);
651
652   pa_context_set_default_source (priv->context, name,
653       empathy_mic_monitor_set_default_cb, result);
654 }
655
656 void
657 empathy_mic_monitor_set_default_async (EmpathyMicMonitor *self,
658     const gchar *name,
659     GAsyncReadyCallback callback,
660     gpointer user_data)
661 {
662   EmpathyMicMonitorPrivate *priv = self->priv;
663   Operation *operation;
664   GSimpleAsyncResult *simple;
665
666   simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
667       empathy_mic_monitor_set_default_async);
668
669   g_simple_async_result_set_op_res_gpointer (simple, g_strdup (name), g_free);
670
671   operation = operation_new (operation_set_default, simple);
672   g_queue_push_tail (priv->operations, operation);
673
674   operations_run (self);
675 }
676
677 gboolean
678 empathy_mic_monitor_set_default_finish (EmpathyMicMonitor *self,
679     GAsyncResult *result,
680     GError **error)
681 {
682   empathy_implement_finish_void (self,
683       empathy_mic_monitor_set_default_async);
684 }