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
21 #include "empathy-mic-monitor.h"
23 #include <pulse/glib-mainloop.h>
24 #include <tp-account-widgets/tpaw-utils.h>
26 #include "empathy-utils.h"
28 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
29 #include "empathy-debug.h"
39 static guint signals
[LAST_SIGNAL
] = {0};
41 struct _EmpathyMicMonitorPrivate
43 pa_glib_mainloop
*loop
;
48 G_DEFINE_TYPE (EmpathyMicMonitor
, empathy_mic_monitor
, G_TYPE_OBJECT
);
50 typedef void (*OperationFunc
) (EmpathyMicMonitor
*, GSimpleAsyncResult
*);
55 GSimpleAsyncResult
*result
;
59 operation_new (OperationFunc func
,
60 GSimpleAsyncResult
*result
)
62 Operation
*o
= g_slice_new0 (Operation
);
71 operation_free (Operation
*o
,
76 g_simple_async_result_set_error (o
->result
,
77 G_IO_ERROR
, G_IO_ERROR_CANCELLED
,
78 "The microphone monitor was disposed");
79 g_simple_async_result_complete (o
->result
);
80 g_object_unref (o
->result
);
83 g_slice_free (Operation
, o
);
87 operations_run (EmpathyMicMonitor
*self
)
89 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
90 pa_context_state_t state
= pa_context_get_state (priv
->context
);
93 if (state
!= PA_CONTEXT_READY
)
96 for (l
= priv
->operations
->head
; l
!= NULL
; l
= l
->next
)
98 Operation
*o
= l
->data
;
100 o
->func (self
, o
->result
);
102 operation_free (o
, FALSE
);
105 g_queue_clear (priv
->operations
);
109 empathy_mic_monitor_source_output_info_cb (pa_context
*context
,
110 const pa_source_output_info
*info
,
114 EmpathyMicMonitor
*self
= userdata
;
119 g_signal_emit (self
, signals
[MICROPHONE_CHANGED
], 0,
120 info
->index
, info
->source
);
124 empathy_mic_monitor_source_info_cb (pa_context
*context
,
125 const pa_source_info
*info
,
129 EmpathyMicMonitor
*self
= userdata
;
135 is_monitor
= (info
->monitor_of_sink
!= PA_INVALID_INDEX
);
137 g_signal_emit (self
, signals
[MICROPHONE_ADDED
], 0,
138 info
->index
, info
->name
, info
->description
, is_monitor
);
142 empathy_mic_monitor_pa_event_cb (pa_context
*context
,
143 pa_subscription_event_type_t type
,
147 EmpathyMicMonitor
*self
= userdata
;
149 if ((type
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
150 && (type
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_CHANGE
)
152 /* Microphone in the source output has changed */
153 pa_context_get_source_output_info (context
, idx
,
154 empathy_mic_monitor_source_output_info_cb
, self
);
156 else if ((type
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
157 && (type
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
)
159 /* A mic has been removed */
160 g_signal_emit (self
, signals
[MICROPHONE_REMOVED
], 0, idx
);
162 else if ((type
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
163 && (type
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
)
165 /* A mic has been plugged in */
166 pa_context_get_source_info_by_index (context
, idx
,
167 empathy_mic_monitor_source_info_cb
, self
);
172 empathy_mic_monitor_pa_subscribe_cb (pa_context
*context
,
177 DEBUG ("Failed to subscribe to PulseAudio events");
181 empathy_mic_monitor_pa_state_change_cb (pa_context
*context
,
184 EmpathyMicMonitor
*self
= userdata
;
185 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
186 pa_context_state_t state
= pa_context_get_state (priv
->context
);
188 if (state
== PA_CONTEXT_READY
)
190 /* Listen to pulseaudio events so we know when sources are
191 * added and when the microphone is changed. */
192 pa_context_set_subscribe_callback (priv
->context
,
193 empathy_mic_monitor_pa_event_cb
, self
);
194 pa_context_subscribe (priv
->context
,
195 PA_SUBSCRIPTION_MASK_SOURCE
| PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT
,
196 empathy_mic_monitor_pa_subscribe_cb
, NULL
);
198 operations_run (self
);
203 empathy_mic_monitor_init (EmpathyMicMonitor
*self
)
205 EmpathyMicMonitorPrivate
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
206 EMPATHY_TYPE_MIC_MONITOR
, EmpathyMicMonitorPrivate
);
212 empathy_mic_monitor_constructed (GObject
*obj
)
214 EmpathyMicMonitor
*self
= EMPATHY_MIC_MONITOR (obj
);
215 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
217 /* PulseAudio stuff: We need to create a dummy pa_glib_mainloop* so
218 * Pulse can use the mainloop that GTK has created for us. */
219 priv
->loop
= pa_glib_mainloop_new (NULL
);
220 priv
->context
= pa_context_new (pa_glib_mainloop_get_api (priv
->loop
),
221 "EmpathyMicMonitor");
223 /* Finally listen for state changes so we know when we've
225 pa_context_set_state_callback (priv
->context
,
226 empathy_mic_monitor_pa_state_change_cb
, obj
);
227 pa_context_connect (priv
->context
, NULL
, 0, NULL
);
229 priv
->operations
= g_queue_new ();
233 empathy_mic_monitor_dispose (GObject
*obj
)
235 EmpathyMicMonitor
*self
= EMPATHY_MIC_MONITOR (obj
);
236 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
238 g_queue_foreach (priv
->operations
, (GFunc
) operation_free
,
239 GUINT_TO_POINTER (TRUE
));
240 g_queue_free (priv
->operations
);
242 if (priv
->context
!= NULL
)
243 pa_context_unref (priv
->context
);
244 priv
->context
= NULL
;
246 if (priv
->loop
!= NULL
)
247 pa_glib_mainloop_free (priv
->loop
);
250 G_OBJECT_CLASS (empathy_mic_monitor_parent_class
)->dispose (obj
);
254 empathy_mic_monitor_class_init (EmpathyMicMonitorClass
*klass
)
256 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
258 object_class
->constructed
= empathy_mic_monitor_constructed
;
259 object_class
->dispose
= empathy_mic_monitor_dispose
;
261 signals
[MICROPHONE_ADDED
] = g_signal_new ("microphone-added",
262 G_TYPE_FROM_CLASS (klass
),
266 g_cclosure_marshal_generic
,
267 G_TYPE_NONE
, 4, G_TYPE_UINT
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
269 signals
[MICROPHONE_REMOVED
] = g_signal_new ("microphone-removed",
270 G_TYPE_FROM_CLASS (klass
),
274 g_cclosure_marshal_generic
,
275 G_TYPE_NONE
, 1, G_TYPE_UINT
);
277 signals
[MICROPHONE_CHANGED
] = g_signal_new ("microphone-changed",
278 G_TYPE_FROM_CLASS (klass
),
282 g_cclosure_marshal_generic
,
283 G_TYPE_NONE
, 2, G_TYPE_UINT
, G_TYPE_UINT
);
285 g_type_class_add_private (object_class
, sizeof (EmpathyMicMonitorPrivate
));
289 empathy_mic_monitor_new (void)
291 return g_object_new (EMPATHY_TYPE_MIC_MONITOR
,
295 /* operation: list microphones */
297 operation_list_microphones_free (gpointer data
)
299 GQueue
*queue
= data
;
302 for (l
= queue
->head
; l
!= NULL
; l
= l
->next
)
304 EmpathyMicrophone
*mic
= l
->data
;
307 g_free (mic
->description
);
308 g_slice_free (EmpathyMicrophone
, mic
);
311 g_queue_free (queue
);
315 operation_list_microphones_cb (pa_context
*context
,
316 const pa_source_info
*info
,
320 GSimpleAsyncResult
*result
= userdata
;
321 EmpathyMicrophone
*mic
;
326 g_simple_async_result_complete (result
);
327 g_object_unref (result
);
331 mic
= g_slice_new0 (EmpathyMicrophone
);
332 mic
->index
= info
->index
;
333 mic
->name
= g_strdup (info
->name
);
334 mic
->description
= g_strdup (info
->description
);
335 mic
->is_monitor
= (info
->monitor_of_sink
!= PA_INVALID_INDEX
);
337 /* add it to the queue */
338 queue
= g_simple_async_result_get_op_res_gpointer (result
);
339 g_queue_push_tail (queue
, mic
);
343 operation_list_microphones (EmpathyMicMonitor
*self
,
344 GSimpleAsyncResult
*result
)
346 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
348 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
350 g_simple_async_result_set_op_res_gpointer (result
, g_queue_new (),
351 operation_list_microphones_free
);
353 pa_context_get_source_info_list (priv
->context
,
354 operation_list_microphones_cb
, result
);
358 empathy_mic_monitor_list_microphones_async (EmpathyMicMonitor
*self
,
359 GAsyncReadyCallback callback
,
362 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
363 Operation
*operation
;
364 GSimpleAsyncResult
*simple
;
366 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
367 empathy_mic_monitor_list_microphones_async
);
369 operation
= operation_new (operation_list_microphones
, simple
);
370 g_queue_push_tail (priv
->operations
, operation
);
373 operations_run (self
);
377 empathy_mic_monitor_list_microphones_finish (EmpathyMicMonitor
*src
,
378 GAsyncResult
*result
,
381 GSimpleAsyncResult
*simple
= G_SIMPLE_ASYNC_RESULT (result
);
384 if (g_simple_async_result_propagate_error (simple
, error
))
387 g_return_val_if_fail (g_simple_async_result_is_valid (result
,
388 G_OBJECT (src
), empathy_mic_monitor_list_microphones_async
),
391 queue
= g_simple_async_result_get_op_res_gpointer (simple
);
395 /* operation: change microphone */
398 guint source_output_idx
;
400 } ChangeMicrophoneData
;
403 operation_change_microphone_cb (pa_context
*context
,
407 GSimpleAsyncResult
*result
= userdata
;
411 g_simple_async_result_set_error (result
, G_IO_ERROR
, G_IO_ERROR_FAILED
,
412 "Failed to change microphone. Reason unknown.");
415 g_simple_async_result_complete (result
);
416 g_object_unref (result
);
420 operation_change_microphone (EmpathyMicMonitor
*self
,
421 GSimpleAsyncResult
*result
)
423 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
424 ChangeMicrophoneData
*data
;
426 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
428 data
= g_simple_async_result_get_op_res_gpointer (result
);
430 pa_context_move_source_output_by_index (priv
->context
,
431 data
->source_output_idx
, data
->source_idx
,
432 operation_change_microphone_cb
, result
);
434 g_simple_async_result_set_op_res_gpointer (result
, NULL
, NULL
);
435 g_slice_free (ChangeMicrophoneData
, data
);
439 empathy_mic_monitor_change_microphone_async (EmpathyMicMonitor
*self
,
440 guint source_output_idx
,
442 GAsyncReadyCallback callback
,
445 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
446 GSimpleAsyncResult
*simple
;
447 Operation
*operation
;
448 ChangeMicrophoneData
*data
;
450 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
451 empathy_mic_monitor_change_microphone_async
);
453 if (source_output_idx
== PA_INVALID_INDEX
)
455 g_simple_async_result_set_error (simple
, G_IO_ERROR
, G_IO_ERROR_FAILED
,
456 "Invalid source output index");
457 g_simple_async_result_complete_in_idle (simple
);
458 g_object_unref (simple
);
462 data
= g_slice_new0 (ChangeMicrophoneData
);
463 data
->source_idx
= source_idx
;
464 data
->source_output_idx
= source_output_idx
;
465 g_simple_async_result_set_op_res_gpointer (simple
, data
, NULL
);
467 operation
= operation_new (operation_change_microphone
, simple
);
468 g_queue_push_tail (priv
->operations
, operation
);
471 operations_run (self
);
475 empathy_mic_monitor_change_microphone_finish (EmpathyMicMonitor
*self
,
476 GAsyncResult
*result
,
479 tpaw_implement_finish_void (self
,
480 empathy_mic_monitor_change_microphone_async
);
483 /* operation: get current mic */
485 empathy_mic_monitor_get_current_mic_cb (pa_context
*context
,
486 const pa_source_output_info
*info
,
490 GSimpleAsyncResult
*result
= userdata
;
495 if (g_simple_async_result_get_op_res_gpointer (result
) != NULL
)
498 g_simple_async_result_set_op_res_gpointer (result
,
499 GUINT_TO_POINTER (info
->source
), NULL
);
500 g_simple_async_result_complete (result
);
501 g_object_unref (result
);
505 operation_get_current_mic (EmpathyMicMonitor
*self
,
506 GSimpleAsyncResult
*result
)
508 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
509 guint source_output_idx
;
511 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
513 source_output_idx
= GPOINTER_TO_UINT (
514 g_simple_async_result_get_op_res_gpointer (result
));
516 /* unset this so we can use it in the cb */
517 g_simple_async_result_set_op_res_gpointer (result
, NULL
, NULL
);
519 pa_context_get_source_output_info (priv
->context
, source_output_idx
,
520 empathy_mic_monitor_get_current_mic_cb
, result
);
524 empathy_mic_monitor_get_current_mic_async (EmpathyMicMonitor
*self
,
525 guint source_output_idx
,
526 GAsyncReadyCallback callback
,
529 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
530 Operation
*operation
;
531 GSimpleAsyncResult
*simple
;
533 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
534 empathy_mic_monitor_get_current_mic_async
);
536 g_simple_async_result_set_op_res_gpointer (simple
,
537 GUINT_TO_POINTER (source_output_idx
), NULL
);
539 operation
= operation_new (operation_get_current_mic
, simple
);
540 g_queue_push_tail (priv
->operations
, operation
);
542 operations_run (self
);
546 empathy_mic_monitor_get_current_mic_finish (EmpathyMicMonitor
*self
,
547 GAsyncResult
*result
,
550 GSimpleAsyncResult
*simple
= G_SIMPLE_ASYNC_RESULT (result
);
552 if (g_simple_async_result_propagate_error (simple
, error
))
553 return PA_INVALID_INDEX
;
555 g_return_val_if_fail (g_simple_async_result_is_valid (result
,
556 G_OBJECT (self
), empathy_mic_monitor_get_current_mic_async
),
559 return GPOINTER_TO_UINT (
560 g_simple_async_result_get_op_res_gpointer (simple
));
563 /* operation: get default */
565 empathy_mic_monitor_get_default_cb (pa_context
*context
,
566 const pa_server_info
*info
,
569 GSimpleAsyncResult
*result
= userdata
;
571 /* TODO: it would be nice in future, for consistency, if this gave
572 * the source idx instead of the name. */
573 g_simple_async_result_set_op_res_gpointer (result
,
574 g_strdup (info
->default_source_name
), g_free
);
575 g_simple_async_result_complete (result
);
576 g_object_unref (result
);
580 operation_get_default (EmpathyMicMonitor
*self
,
581 GSimpleAsyncResult
*result
)
583 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
585 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
587 /* unset this so we can use it in the cb */
588 g_simple_async_result_set_op_res_gpointer (result
, NULL
, NULL
);
590 pa_context_get_server_info (priv
->context
, empathy_mic_monitor_get_default_cb
,
595 empathy_mic_monitor_get_default_async (EmpathyMicMonitor
*self
,
596 GAsyncReadyCallback callback
,
599 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
600 Operation
*operation
;
601 GSimpleAsyncResult
*simple
;
603 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
604 empathy_mic_monitor_get_default_async
);
606 operation
= operation_new (operation_get_default
, simple
);
607 g_queue_push_tail (priv
->operations
, operation
);
609 operations_run (self
);
613 empathy_mic_monitor_get_default_finish (EmpathyMicMonitor
*self
,
614 GAsyncResult
*result
,
617 tpaw_implement_finish_return_pointer (self
,
618 empathy_mic_monitor_get_default_async
);
621 /* operation: set default */
623 empathy_mic_monitor_set_default_cb (pa_context
*c
,
627 GSimpleAsyncResult
*result
= userdata
;
631 g_simple_async_result_set_error (result
,
632 G_IO_ERROR
, G_IO_ERROR_FAILED
,
633 "The operation failed for an unknown reason");
636 g_simple_async_result_complete (result
);
637 g_object_unref (result
);
641 operation_set_default (EmpathyMicMonitor
*self
,
642 GSimpleAsyncResult
*result
)
644 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
647 g_assert_cmpuint (pa_context_get_state (priv
->context
), ==, PA_CONTEXT_READY
);
649 name
= g_simple_async_result_get_op_res_gpointer (result
);
651 pa_context_set_default_source (priv
->context
, name
,
652 empathy_mic_monitor_set_default_cb
, result
);
656 empathy_mic_monitor_set_default_async (EmpathyMicMonitor
*self
,
658 GAsyncReadyCallback callback
,
661 EmpathyMicMonitorPrivate
*priv
= self
->priv
;
662 Operation
*operation
;
663 GSimpleAsyncResult
*simple
;
665 simple
= g_simple_async_result_new (G_OBJECT (self
), callback
, user_data
,
666 empathy_mic_monitor_set_default_async
);
668 g_simple_async_result_set_op_res_gpointer (simple
, g_strdup (name
), g_free
);
670 operation
= operation_new (operation_set_default
, simple
);
671 g_queue_push_tail (priv
->operations
, operation
);
673 operations_run (self
);
677 empathy_mic_monitor_set_default_finish (EmpathyMicMonitor
*self
,
678 GAsyncResult
*result
,
681 tpaw_implement_finish_void (self
,
682 empathy_mic_monitor_set_default_async
);