2 * Copyright (C) 2011 Collabora Ltd.
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.
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.
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
24 #include <pulse/pulseaudio.h>
25 #include <pulse/glib-mainloop.h>
27 #include "empathy-mic-monitor.h"
30 #include <libempathy/empathy-utils.h>
32 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
33 #include <libempathy/empathy-debug.h>
43 static guint signals
[LAST_SIGNAL
] = {0};
45 struct _EmpathyMicMonitorPrivate
47 pa_glib_mainloop
*loop
;
52 G_DEFINE_TYPE (EmpathyMicMonitor
, empathy_mic_monitor
, G_TYPE_OBJECT
);
54 typedef void (*OperationFunc
) (EmpathyMicMonitor
*, GSimpleAsyncResult
*);
59 GSimpleAsyncResult
*result
;
63 operation_new (OperationFunc func
,
64 GSimpleAsyncResult
*result
)
66 Operation
*o
= g_slice_new0 (Operation
);
75 operation_free (Operation
*o
,
80 g_simple_async_result_set_error (o
->result
,
81 G_IO_ERROR
, G_IO_ERROR_CANCELLED
,
82 "The microphone monitor was disposed");
83 g_simple_async_result_complete (o
->result
);
84 g_object_unref (o
->result
);
87 g_slice_free (Operation
, o
);
91 operations_run (EmpathyMicMonitor
*self
)
93 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
94 pa_context_state_t state
= pa_context_get_state (priv
->context
);
97 if (state
!= PA_CONTEXT_READY
)
100 for (l
= priv
->operations
->head
; l
!= NULL
; l
= l
->next
)
102 Operation
*o
= l
->data
;
104 o
->func (self
, o
->result
);
106 operation_free (o
, FALSE
);
109 g_queue_clear (priv
->operations
);
113 empathy_mic_monitor_source_output_info_cb (pa_context
*context
,
114 const pa_source_output_info
*info
,
118 EmpathyMicMonitor
*self
= userdata
;
123 g_signal_emit (self
, signals
[MICROPHONE_CHANGED
], 0,
124 info
->index
, info
->source
);
128 empathy_mic_monitor_source_info_cb (pa_context
*context
,
129 const pa_source_info
*info
,
133 EmpathyMicMonitor
*self
= userdata
;
139 is_monitor
= (info
->monitor_of_sink
!= PA_INVALID_INDEX
);
141 g_signal_emit (self
, signals
[MICROPHONE_ADDED
], 0,
142 info
->index
, info
->name
, info
->description
, is_monitor
);
146 empathy_mic_monitor_pa_event_cb (pa_context
*context
,
147 pa_subscription_event_type_t type
,
151 EmpathyMicMonitor
*self
= userdata
;
153 if ((type
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
154 && (type
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_CHANGE
)
156 /* Microphone in the source output has changed */
157 pa_context_get_source_output_info (context
, idx
,
158 empathy_mic_monitor_source_output_info_cb
, self
);
160 else if ((type
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
161 && (type
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
)
163 /* A mic has been removed */
164 g_signal_emit (self
, signals
[MICROPHONE_REMOVED
], 0, idx
);
166 else if ((type
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
167 && (type
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
)
169 /* A mic has been plugged in */
170 pa_context_get_source_info_by_index (context
, idx
,
171 empathy_mic_monitor_source_info_cb
, self
);
176 empathy_mic_monitor_pa_subscribe_cb (pa_context
*context
,
181 DEBUG ("Failed to subscribe to PulseAudio events");
185 empathy_mic_monitor_pa_state_change_cb (pa_context
*context
,
188 EmpathyMicMonitor
*self
= userdata
;
189 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
190 pa_context_state_t state
= pa_context_get_state (priv
->context
);
192 if (state
== PA_CONTEXT_READY
)
194 /* Listen to pulseaudio events so we know when sources are
195 * added and when the microphone is changed. */
196 pa_context_set_subscribe_callback (priv
->context
,
197 empathy_mic_monitor_pa_event_cb
, self
);
198 pa_context_subscribe (priv
->context
,
199 PA_SUBSCRIPTION_MASK_SOURCE
| PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT
,
200 empathy_mic_monitor_pa_subscribe_cb
, NULL
);
202 operations_run (self
);
207 empathy_mic_monitor_init (EmpathyMicMonitor
*self
)
209 EmpathyMicMonitorPrivate
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
210 EMPATHY_TYPE_MIC_MONITOR
, EmpathyMicMonitorPrivate
);
216 empathy_mic_monitor_constructed (GObject
*obj
)
218 EmpathyMicMonitor
*self
= EMPATHY_MIC_MONITOR (obj
);
219 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
221 /* PulseAudio stuff: We need to create a dummy pa_glib_mainloop* so
222 * Pulse can use the mainloop that GTK has created for us. */
223 priv
->loop
= pa_glib_mainloop_new (NULL
);
224 priv
->context
= pa_context_new (pa_glib_mainloop_get_api (priv
->loop
),
225 "EmpathyMicMonitor");
227 /* Finally listen for state changes so we know when we've
229 pa_context_set_state_callback (priv
->context
,
230 empathy_mic_monitor_pa_state_change_cb
, obj
);
231 pa_context_connect (priv
->context
, NULL
, 0, NULL
);
233 priv
->operations
= g_queue_new ();
237 empathy_mic_monitor_dispose (GObject
*obj
)
239 EmpathyMicMonitor
*self
= EMPATHY_MIC_MONITOR (obj
);
240 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
242 g_queue_foreach (priv
->operations
, (GFunc
) operation_free
,
243 GUINT_TO_POINTER (TRUE
));
244 g_queue_free (priv
->operations
);
246 if (priv
->context
!= NULL
)
247 pa_context_unref (priv
->context
);
248 priv
->context
= NULL
;
250 if (priv
->loop
!= NULL
)
251 pa_glib_mainloop_free (priv
->loop
);
254 G_OBJECT_CLASS (empathy_mic_monitor_parent_class
)->dispose (obj
);
258 empathy_mic_monitor_class_init (EmpathyMicMonitorClass
*klass
)
260 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
262 object_class
->constructed
= empathy_mic_monitor_constructed
;
263 object_class
->dispose
= empathy_mic_monitor_dispose
;
265 signals
[MICROPHONE_ADDED
] = g_signal_new ("microphone-added",
266 G_TYPE_FROM_CLASS (klass
),
270 g_cclosure_marshal_generic
,
271 G_TYPE_NONE
, 4, G_TYPE_UINT
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
273 signals
[MICROPHONE_REMOVED
] = g_signal_new ("microphone-removed",
274 G_TYPE_FROM_CLASS (klass
),
278 g_cclosure_marshal_generic
,
279 G_TYPE_NONE
, 1, G_TYPE_UINT
);
281 signals
[MICROPHONE_CHANGED
] = g_signal_new ("microphone-changed",
282 G_TYPE_FROM_CLASS (klass
),
286 g_cclosure_marshal_generic
,
287 G_TYPE_NONE
, 2, G_TYPE_UINT
, G_TYPE_UINT
);
289 g_type_class_add_private (object_class
, sizeof (EmpathyMicMonitorPrivate
));
293 empathy_mic_monitor_new (void)
295 return g_object_new (EMPATHY_TYPE_MIC_MONITOR
,
299 /* operation: list microphones */
301 operation_list_microphones_free (gpointer data
)
303 GQueue
*queue
= data
;
306 for (l
= queue
->head
; l
!= NULL
; l
= l
->next
)
308 EmpathyMicrophone
*mic
= l
->data
;
311 g_free (mic
->description
);
312 g_slice_free (EmpathyMicrophone
, mic
);
315 g_queue_free (queue
);
319 operation_list_microphones_cb (pa_context
*context
,
320 const pa_source_info
*info
,
324 GSimpleAsyncResult
*result
= userdata
;
325 EmpathyMicrophone
*mic
;
330 g_simple_async_result_complete (result
);
331 g_object_unref (result
);
335 mic
= g_slice_new0 (EmpathyMicrophone
);
336 mic
->index
= info
->index
;
337 mic
->name
= g_strdup (info
->name
);
338 mic
->description
= g_strdup (info
->description
);
339 mic
->is_monitor
= (info
->monitor_of_sink
!= PA_INVALID_INDEX
);
341 /* add it to the queue */
342 queue
= g_simple_async_result_get_op_res_gpointer (result
);
343 g_queue_push_tail (queue
, mic
);
347 operation_list_microphones (EmpathyMicMonitor
*self
,
348 GSimpleAsyncResult
*result
)
350 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
352 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
354 g_simple_async_result_set_op_res_gpointer (result
, g_queue_new (),
355 operation_list_microphones_free
);
357 pa_context_get_source_info_list (priv
->context
,
358 operation_list_microphones_cb
, result
);
362 empathy_mic_monitor_list_microphones_async (EmpathyMicMonitor
*self
,
363 GAsyncReadyCallback callback
,
366 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
367 Operation
*operation
;
368 GSimpleAsyncResult
*simple
;
370 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
371 empathy_mic_monitor_list_microphones_async
);
373 operation
= operation_new (operation_list_microphones
, simple
);
374 g_queue_push_tail (priv
->operations
, operation
);
377 operations_run (self
);
381 empathy_mic_monitor_list_microphones_finish (EmpathyMicMonitor
*src
,
382 GAsyncResult
*result
,
385 GSimpleAsyncResult
*simple
= G_SIMPLE_ASYNC_RESULT (result
);
388 if (g_simple_async_result_propagate_error (simple
, error
))
391 g_return_val_if_fail (g_simple_async_result_is_valid (result
,
392 G_OBJECT (src
), empathy_mic_monitor_list_microphones_async
),
395 queue
= g_simple_async_result_get_op_res_gpointer (simple
);
399 /* operation: change microphone */
402 guint source_output_idx
;
404 } ChangeMicrophoneData
;
407 operation_change_microphone_cb (pa_context
*context
,
411 GSimpleAsyncResult
*result
= userdata
;
415 g_simple_async_result_set_error (result
, G_IO_ERROR
, G_IO_ERROR_FAILED
,
416 "Failed to change microphone. Reason unknown.");
419 g_simple_async_result_complete (result
);
420 g_object_unref (result
);
424 operation_change_microphone (EmpathyMicMonitor
*self
,
425 GSimpleAsyncResult
*result
)
427 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
428 ChangeMicrophoneData
*data
;
430 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
432 data
= g_simple_async_result_get_op_res_gpointer (result
);
434 pa_context_move_source_output_by_index (priv
->context
,
435 data
->source_output_idx
, data
->source_idx
,
436 operation_change_microphone_cb
, result
);
438 g_simple_async_result_set_op_res_gpointer (result
, NULL
, NULL
);
439 g_slice_free (ChangeMicrophoneData
, data
);
443 empathy_mic_monitor_change_microphone_async (EmpathyMicMonitor
*self
,
444 guint source_output_idx
,
446 GAsyncReadyCallback callback
,
449 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
450 GSimpleAsyncResult
*simple
;
451 Operation
*operation
;
452 ChangeMicrophoneData
*data
;
454 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
455 empathy_mic_monitor_change_microphone_async
);
457 if (source_output_idx
== PA_INVALID_INDEX
)
459 g_simple_async_result_set_error (simple
, G_IO_ERROR
, G_IO_ERROR_FAILED
,
460 "Invalid source output index");
461 g_simple_async_result_complete_in_idle (simple
);
462 g_object_unref (simple
);
466 data
= g_slice_new0 (ChangeMicrophoneData
);
467 data
->source_idx
= source_idx
;
468 data
->source_output_idx
= source_output_idx
;
469 g_simple_async_result_set_op_res_gpointer (simple
, data
, NULL
);
471 operation
= operation_new (operation_change_microphone
, simple
);
472 g_queue_push_tail (priv
->operations
, operation
);
475 operations_run (self
);
479 empathy_mic_monitor_change_microphone_finish (EmpathyMicMonitor
*self
,
480 GAsyncResult
*result
,
483 empathy_implement_finish_void (self
,
484 empathy_mic_monitor_change_microphone_async
);
487 /* operation: get current mic */
489 empathy_mic_monitor_get_current_mic_cb (pa_context
*context
,
490 const pa_source_output_info
*info
,
494 GSimpleAsyncResult
*result
= userdata
;
499 if (g_simple_async_result_get_op_res_gpointer (result
) != NULL
)
502 g_simple_async_result_set_op_res_gpointer (result
,
503 GUINT_TO_POINTER (info
->source
), NULL
);
504 g_simple_async_result_complete (result
);
505 g_object_unref (result
);
509 operation_get_current_mic (EmpathyMicMonitor
*self
,
510 GSimpleAsyncResult
*result
)
512 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
513 guint source_output_idx
;
515 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
517 source_output_idx
= GPOINTER_TO_UINT (
518 g_simple_async_result_get_op_res_gpointer (result
));
520 /* unset this so we can use it in the cb */
521 g_simple_async_result_set_op_res_gpointer (result
, NULL
, NULL
);
523 pa_context_get_source_output_info (priv
->context
, source_output_idx
,
524 empathy_mic_monitor_get_current_mic_cb
, result
);
528 empathy_mic_monitor_get_current_mic_async (EmpathyMicMonitor
*self
,
529 guint source_output_idx
,
530 GAsyncReadyCallback callback
,
533 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
534 Operation
*operation
;
535 GSimpleAsyncResult
*simple
;
537 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
538 empathy_mic_monitor_get_current_mic_async
);
540 g_simple_async_result_set_op_res_gpointer (simple
,
541 GUINT_TO_POINTER (source_output_idx
), NULL
);
543 operation
= operation_new (operation_get_current_mic
, simple
);
544 g_queue_push_tail (priv
->operations
, operation
);
546 operations_run (self
);
550 empathy_mic_monitor_get_current_mic_finish (EmpathyMicMonitor
*self
,
551 GAsyncResult
*result
,
554 GSimpleAsyncResult
*simple
= G_SIMPLE_ASYNC_RESULT (result
);
556 if (g_simple_async_result_propagate_error (simple
, error
))
557 return PA_INVALID_INDEX
;
559 g_return_val_if_fail (g_simple_async_result_is_valid (result
,
560 G_OBJECT (self
), empathy_mic_monitor_get_current_mic_async
),
563 return GPOINTER_TO_UINT (
564 g_simple_async_result_get_op_res_gpointer (simple
));
567 /* operation: get default */
569 empathy_mic_monitor_get_default_cb (pa_context
*context
,
570 const pa_server_info
*info
,
573 GSimpleAsyncResult
*result
= userdata
;
575 /* TODO: it would be nice in future, for consistency, if this gave
576 * the source idx instead of the name. */
577 g_simple_async_result_set_op_res_gpointer (result
,
578 g_strdup (info
->default_source_name
), g_free
);
579 g_simple_async_result_complete (result
);
580 g_object_unref (result
);
584 operation_get_default (EmpathyMicMonitor
*self
,
585 GSimpleAsyncResult
*result
)
587 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
589 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
591 /* unset this so we can use it in the cb */
592 g_simple_async_result_set_op_res_gpointer (result
, NULL
, NULL
);
594 pa_context_get_server_info (priv
->context
, empathy_mic_monitor_get_default_cb
,
599 empathy_mic_monitor_get_default_async (EmpathyMicMonitor
*self
,
600 GAsyncReadyCallback callback
,
603 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
604 Operation
*operation
;
605 GSimpleAsyncResult
*simple
;
607 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
608 empathy_mic_monitor_get_default_async
);
610 operation
= operation_new (operation_get_default
, simple
);
611 g_queue_push_tail (priv
->operations
, operation
);
613 operations_run (self
);
617 empathy_mic_monitor_get_default_finish (EmpathyMicMonitor
*self
,
618 GAsyncResult
*result
,
621 empathy_implement_finish_return_pointer (self
,
622 empathy_mic_monitor_get_default_async
);
625 /* operation: set default */
627 empathy_mic_monitor_set_default_cb (pa_context
*c
,
631 GSimpleAsyncResult
*result
= userdata
;
635 g_simple_async_result_set_error (result
,
636 G_IO_ERROR
, G_IO_ERROR_FAILED
,
637 "The operation failed for an unknown reason");
640 g_simple_async_result_complete (result
);
641 g_object_unref (result
);
645 operation_set_default (EmpathyMicMonitor
*self
,
646 GSimpleAsyncResult
*result
)
648 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
651 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
653 name
= g_simple_async_result_get_op_res_gpointer (result
);
655 pa_context_set_default_source (priv
->context
, name
,
656 empathy_mic_monitor_set_default_cb
, result
);
660 empathy_mic_monitor_set_default_async (EmpathyMicMonitor
*self
,
662 GAsyncReadyCallback callback
,
665 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
666 Operation
*operation
;
667 GSimpleAsyncResult
*simple
;
669 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
670 empathy_mic_monitor_set_default_async
);
672 g_simple_async_result_set_op_res_gpointer (simple
, g_strdup (name
), g_free
);
674 operation
= operation_new (operation_set_default
, simple
);
675 g_queue_push_tail (priv
->operations
, operation
);
677 operations_run (self
);
681 empathy_mic_monitor_set_default_finish (EmpathyMicMonitor
*self
,
682 GAsyncResult
*result
,
685 empathy_implement_finish_void (self
,
686 empathy_mic_monitor_set_default_async
);