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
18 * GtkAction code based on gnome-terminal's TerminalTabsMenu object.
26 #include "empathy-mic-menu.h"
28 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
29 #include <libempathy/empathy-debug.h>
31 struct _EmpathyMicMenuPrivate
33 /* Borrowed ref; the call window actually owns us. */
34 EmpathyCallWindow *window;
36 /* Given away ref; the call window's UI manager now owns this. */
37 GtkActionGroup *action_group;
39 /* An invisible radio action so new microphones are always in the
40 * same radio group. */
41 GtkAction *anchor_action;
43 /* The merge ID used with the UI manager. We need to keep this
44 * around so in _clean we can remove all the items we've added
45 * before and start again. */
48 /* TRUE if we're in _update and so calling _set_active. */
51 /* Queue of GtkRadioActions. */
55 G_DEFINE_TYPE (EmpathyMicMenu, empathy_mic_menu, G_TYPE_OBJECT);
57 #define MONITOR_KEY "empathy-mic-menu-is-monitor"
64 static void empathy_mic_menu_update (EmpathyMicMenu *self);
67 empathy_mic_menu_init (EmpathyMicMenu *self)
69 EmpathyMicMenuPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
70 EMPATHY_TYPE_MIC_MENU, EmpathyMicMenuPrivate);
76 empathy_mic_menu_set_property (GObject *object,
81 EmpathyMicMenu *self = EMPATHY_MIC_MENU (object);
82 EmpathyMicMenuPrivate *priv = self->priv;
87 priv->window = g_value_get_object (value);
90 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
95 empathy_mic_menu_get_property (GObject *object,
100 EmpathyMicMenu *self = EMPATHY_MIC_MENU (object);
101 EmpathyMicMenuPrivate *priv = self->priv;
106 g_value_set_object (value, priv->window);
109 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
114 empathy_mic_menu_clean (EmpathyMicMenu *self)
116 EmpathyMicMenuPrivate *priv = self->priv;
117 GtkUIManager *ui_manager;
119 if (priv->ui_id == 0)
122 ui_manager = empathy_call_window_get_ui_manager (priv->window);
124 gtk_ui_manager_remove_ui (ui_manager, priv->ui_id);
125 gtk_ui_manager_ensure_update (ui_manager);
130 empathy_mic_menu_change_mic_cb (GObject *source_object,
131 GAsyncResult *result,
134 EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object);
135 EmpathyMicMenu *self = user_data;
136 GError *error = NULL;
138 if (!empathy_audio_src_change_microphone_finish (audio, result, &error))
140 DEBUG ("Failed to change microphone: %s", error->message);
141 g_clear_error (&error);
143 /* We call update here because if this change operation failed
144 * and we don't update the menu items, it'll point to the wrong
145 * device. We don't want to call it if the change was successful
146 * because we'll get the notify::microphone signal fired in a
147 * bit and the current value hasn't changed so it'd keep jumping
148 * between these values like there's no tomorrow, etc. */
149 empathy_mic_menu_update (self);
154 empathy_mic_menu_activate_cb (GtkToggleAction *action,
155 EmpathyMicMenu *self)
157 EmpathyMicMenuPrivate *priv = self->priv;
158 EmpathyGstAudioSrc *audio;
164 audio = empathy_call_window_get_audio_src (priv->window);
166 g_object_get (action, "value", &value, NULL);
168 empathy_audio_src_change_microphone_async (audio, value,
169 empathy_mic_menu_change_mic_cb, self);
173 empathy_mic_menu_update (EmpathyMicMenu *self)
175 EmpathyMicMenuPrivate *priv = self->priv;
177 GtkUIManager *ui_manager;
178 EmpathyGstAudioSrc *audio;
181 ui_manager = empathy_call_window_get_ui_manager (priv->window);
183 audio = empathy_call_window_get_audio_src (priv->window);
184 current_mic = empathy_audio_src_get_microphone (audio);
186 empathy_mic_menu_clean (self);
187 priv->ui_id = gtk_ui_manager_new_merge_id (ui_manager);
189 for (l = priv->microphones->head; l != NULL; l = l->next)
191 GtkRadioAction *action = l->data;
192 const gchar *name = gtk_action_get_name (GTK_ACTION (action));
196 g_object_get (action, "value", &value, NULL);
198 active = (value == (gint) current_mic);
202 priv->in_update = TRUE;
203 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
204 priv->in_update = FALSE;
207 /* If action is a monitor then don't show it in the UI, BUT do
208 * display it regardless if it is the current device. This is so
209 * we don't have a rubbish UI by showing monitor devices in
210 * Empathy, but still show the correct device when someone plays
211 * with pavucontrol. */
212 if (g_object_get_data (G_OBJECT (action), MONITOR_KEY) != NULL
216 gtk_ui_manager_add_ui (ui_manager, priv->ui_id,
217 /* TODO: this should probably be passed from the call
218 * window, seeing that it's a reference to
219 * empathy-call-window.ui. */
220 "/menubar1/edit/menumicrophone",
221 name, name, GTK_UI_MANAGER_MENUITEM, FALSE);
226 empathy_mic_menu_add_microphone (EmpathyMicMenu *self,
228 const gchar *description,
232 EmpathyMicMenuPrivate *priv = self->priv;
233 GtkRadioAction *action;
236 action = gtk_radio_action_new (name, description, NULL, NULL, source_idx);
237 gtk_action_group_add_action_with_accel (priv->action_group,
238 GTK_ACTION (action), NULL);
240 /* Set MONITOR_KEY on the action to non-NULL if it's a monitor
241 * because we don't want to show monitors if we can help it. */
244 g_object_set_data (G_OBJECT (action), MONITOR_KEY,
245 GUINT_TO_POINTER (TRUE));
248 group = gtk_radio_action_get_group (GTK_RADIO_ACTION (priv->anchor_action));
249 gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group);
251 g_queue_push_tail (priv->microphones, action);
253 g_signal_connect (action, "activate",
254 G_CALLBACK (empathy_mic_menu_activate_cb), self);
258 empathy_mic_menu_notify_microphone_cb (EmpathyGstAudioSrc *audio,
260 EmpathyMicMenu *self)
262 empathy_mic_menu_update (self);
266 empathy_mic_menu_microphone_added_cb (EmpathyGstAudioSrc *audio,
269 const gchar *description,
271 EmpathyMicMenu *self)
273 empathy_mic_menu_add_microphone (self, name, description,
274 source_idx, is_monitor);
276 empathy_mic_menu_update (self);
280 empathy_mic_menu_microphone_removed_cb (EmpathyGstAudioSrc *audio,
282 EmpathyMicMenu *self)
284 EmpathyMicMenuPrivate *priv = self->priv;
287 for (l = priv->microphones->head; l != NULL; l = l->next)
289 GtkRadioAction *action = l->data;
292 g_object_get (action, "value", &value, NULL);
294 if (value != (gint) source_idx)
300 g_signal_handlers_disconnect_by_func (action,
301 G_CALLBACK (empathy_mic_menu_activate_cb), self);
303 gtk_action_group_remove_action (priv->action_group, GTK_ACTION (action));
304 g_queue_remove (priv->microphones, action);
308 empathy_mic_menu_update (self);
312 empathy_mic_menu_get_microphones_cb (GObject *source_object,
313 GAsyncResult *result,
316 EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object);
317 EmpathyMicMenu *self = user_data;
318 GError *error = NULL;
319 const GList *mics = NULL;
321 mics = empathy_audio_src_get_microphones_finish (audio, result, &error);
325 DEBUG ("Failed to get microphone list: %s", error->message);
326 g_clear_error (&error);
330 for (; mics != NULL; mics = mics->next)
332 EmpathyAudioSrcMicrophone *mic = mics->data;
334 empathy_mic_menu_add_microphone (self, mic->name,
335 mic->description, mic->index, mic->is_monitor);
338 empathy_mic_menu_update (self);
342 empathy_mic_menu_constructed (GObject *obj)
344 EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj);
345 EmpathyMicMenuPrivate *priv = self->priv;
346 GtkUIManager *ui_manager;
347 EmpathyGstAudioSrc *audio;
349 g_assert (EMPATHY_IS_CALL_WINDOW (priv->window));
351 ui_manager = empathy_call_window_get_ui_manager (priv->window);
352 audio = empathy_call_window_get_audio_src (priv->window);
354 g_assert (GTK_IS_UI_MANAGER (ui_manager));
355 g_assert (EMPATHY_IS_GST_AUDIO_SRC (audio));
357 /* Okay let's go go go. */
359 priv->action_group = gtk_action_group_new ("EmpathyMicMenu");
360 gtk_ui_manager_insert_action_group (ui_manager, priv->action_group, -1);
361 /* the UI manager now owns this */
362 g_object_unref (priv->action_group);
364 priv->anchor_action = g_object_new (GTK_TYPE_RADIO_ACTION,
365 "name", "EmpathyMicMenuAnchorAction",
367 gtk_action_group_add_action (priv->action_group, priv->anchor_action);
368 g_object_unref (priv->anchor_action);
370 tp_g_signal_connect_object (audio, "notify::microphone",
371 G_CALLBACK (empathy_mic_menu_notify_microphone_cb), self, 0);
372 tp_g_signal_connect_object (audio, "microphone-added",
373 G_CALLBACK (empathy_mic_menu_microphone_added_cb), self, 0);
374 tp_g_signal_connect_object (audio, "microphone-removed",
375 G_CALLBACK (empathy_mic_menu_microphone_removed_cb), self, 0);
377 priv->microphones = g_queue_new ();
379 empathy_audio_src_get_microphones_async (audio,
380 empathy_mic_menu_get_microphones_cb, self);
384 empathy_mic_menu_dispose (GObject *obj)
386 EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj);
387 EmpathyMicMenuPrivate *priv = self->priv;
389 if (priv->microphones != NULL)
390 g_queue_free (priv->microphones);
391 priv->microphones = NULL;
393 G_OBJECT_CLASS (empathy_mic_menu_parent_class)->dispose (obj);
397 empathy_mic_menu_class_init (EmpathyMicMenuClass *klass)
399 GObjectClass *object_class = G_OBJECT_CLASS (klass);
401 object_class->set_property = empathy_mic_menu_set_property;
402 object_class->get_property = empathy_mic_menu_get_property;
403 object_class->constructed = empathy_mic_menu_constructed;
404 object_class->dispose = empathy_mic_menu_dispose;
406 g_object_class_install_property (object_class, PROP_WINDOW,
407 g_param_spec_object ("window", "window", "window",
408 EMPATHY_TYPE_CALL_WINDOW,
409 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
411 g_type_class_add_private (object_class, sizeof (EmpathyMicMenuPrivate));
415 empathy_mic_menu_new (EmpathyCallWindow *window)
417 return g_object_new (EMPATHY_TYPE_MIC_MENU,