]> git.0d.be Git - empathy.git/blob - src/empathy-mic-menu.c
call-window: add a Microphone menu to change mic on the fly
[empathy.git] / src / empathy-mic-menu.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  * GtkAction code based on gnome-terminal's TerminalTabsMenu object.
19  * Thanks guys!
20  */
21
22 #include <config.h>
23
24 #include <gtk/gtk.h>
25
26 #include "empathy-mic-menu.h"
27
28 struct _EmpathyMicMenuPrivate
29 {
30   /* Borrowed ref; the call window actually owns us. */
31   EmpathyCallWindow *window;
32
33   /* Given away ref; the call window's UI manager now owns this. */
34   GtkActionGroup *action_group;
35
36   /* An invisible radio action so new microphones are always in the
37    * same radio group. */
38   GtkAction *anchor_action;
39
40   /* The merge ID used with the UI manager. We need to keep this
41    * around so in _clean we can remove all the items we've added
42    * before and start again. */
43   guint ui_id;
44
45   /* TRUE if we're in _update and so calling _set_active. */
46   gboolean in_update;
47
48   /* Queue of GtkRadioActions. */
49   GQueue *microphones;
50 };
51
52 G_DEFINE_TYPE (EmpathyMicMenu, empathy_mic_menu, G_TYPE_OBJECT);
53
54 enum
55 {
56   PROP_WINDOW = 1,
57 };
58
59 static void empathy_mic_menu_update (EmpathyMicMenu *self);
60
61 static void
62 empathy_mic_menu_init (EmpathyMicMenu *self)
63 {
64   EmpathyMicMenuPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
65     EMPATHY_TYPE_MIC_MENU, EmpathyMicMenuPrivate);
66
67   self->priv = priv;
68 }
69
70 static void
71 empathy_mic_menu_set_property (GObject *object,
72     guint property_id,
73     const GValue *value,
74     GParamSpec *pspec)
75 {
76   EmpathyMicMenu *self = EMPATHY_MIC_MENU (object);
77   EmpathyMicMenuPrivate *priv = self->priv;
78
79   switch (property_id)
80     {
81       case PROP_WINDOW:
82         priv->window = g_value_get_object (value);
83         break;
84       default:
85         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
86     }
87 }
88
89 static void
90 empathy_mic_menu_get_property (GObject *object,
91     guint property_id,
92     GValue *value,
93     GParamSpec *pspec)
94 {
95   EmpathyMicMenu *self = EMPATHY_MIC_MENU (object);
96   EmpathyMicMenuPrivate *priv = self->priv;
97
98   switch (property_id)
99     {
100       case PROP_WINDOW:
101         g_value_set_object (value, priv->window);
102         break;
103       default:
104         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
105     }
106 }
107
108 static void
109 empathy_mic_menu_clean (EmpathyMicMenu *self)
110 {
111   EmpathyMicMenuPrivate *priv = self->priv;
112   GtkUIManager *ui_manager;
113
114   if (priv->ui_id == 0)
115     return;
116
117   ui_manager = empathy_call_window_get_ui_manager (priv->window);
118
119   gtk_ui_manager_remove_ui (ui_manager, priv->ui_id);
120   gtk_ui_manager_ensure_update (ui_manager);
121   priv->ui_id = 0;
122 }
123
124 static void
125 empathy_mic_menu_change_mic_cb (GObject *source_object,
126     GAsyncResult *result,
127     gpointer user_data)
128 {
129   EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object);
130   EmpathyMicMenu *self = user_data;
131   GError *error = NULL;
132
133   if (!empathy_audio_src_change_microphone_finish (audio, result, &error))
134     {
135       g_debug ("Failed to change microphone: %s", error->message);
136       g_clear_error (&error);
137
138       /* We call update here because if this change operation failed
139        * and we don't update the menu items, it'll point to the wrong
140        * device. We don't want to call it if the change was successful
141        * because we'll get the notify::microphone signal fired in a
142        * bit and the current value hasn't changed so it'd keep jumping
143        * between these values like there's no tomorrow, etc. */
144       empathy_mic_menu_update (self);
145     }
146 }
147
148 static void
149 empathy_mic_menu_activate_cb (GtkToggleAction *action,
150     EmpathyMicMenu *self)
151 {
152   EmpathyMicMenuPrivate *priv = self->priv;
153   EmpathyGstAudioSrc *audio;
154   gint value;
155
156   if (priv->in_update)
157     return;
158
159   audio = empathy_call_window_get_audio_src (priv->window);
160
161   g_object_get (action, "value", &value, NULL);
162
163   empathy_audio_src_change_microphone_async (audio, value,
164       empathy_mic_menu_change_mic_cb, self);
165 }
166
167 static void
168 empathy_mic_menu_update (EmpathyMicMenu *self)
169 {
170   EmpathyMicMenuPrivate *priv = self->priv;
171   GList *l;
172   GtkUIManager *ui_manager;
173   EmpathyGstAudioSrc *audio;
174   guint current_mic;
175
176   ui_manager = empathy_call_window_get_ui_manager (priv->window);
177
178   audio = empathy_call_window_get_audio_src (priv->window);
179   current_mic = empathy_audio_src_get_microphone (audio);
180
181   empathy_mic_menu_clean (self);
182   priv->ui_id = gtk_ui_manager_new_merge_id (ui_manager);
183
184   for (l = priv->microphones->head; l != NULL; l = l->next)
185     {
186       GtkRadioAction *action = l->data;
187       const gchar *name = gtk_action_get_name (GTK_ACTION (action));
188       gint value;
189
190       g_object_get (action, "value", &value, NULL);
191
192       if (value == (gint) current_mic)
193         {
194           priv->in_update = TRUE;
195           gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
196           priv->in_update = FALSE;
197         }
198
199       gtk_ui_manager_add_ui (ui_manager, priv->ui_id,
200           /* TODO: this should probably be passed from the call
201            * window, seeing that it's a reference to
202            * empathy-call-window.ui. */
203           "/menubar1/edit/menumicrophone",
204           name, name, GTK_UI_MANAGER_MENUITEM, FALSE);
205     }
206 }
207
208 static void
209 empathy_mic_menu_add_microphone (EmpathyMicMenu *self,
210     const gchar *name,
211     const gchar *description,
212     guint source_idx)
213 {
214   EmpathyMicMenuPrivate *priv = self->priv;
215   GtkRadioAction *action;
216   GSList *group;
217
218   action = gtk_radio_action_new (name, description, NULL, NULL, source_idx);
219   gtk_action_group_add_action_with_accel (priv->action_group,
220       GTK_ACTION (action), NULL);
221
222   group = gtk_radio_action_get_group (GTK_RADIO_ACTION (priv->anchor_action));
223   gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group);
224
225   g_queue_push_tail (priv->microphones, action);
226
227   g_signal_connect (action, "activate",
228       G_CALLBACK (empathy_mic_menu_activate_cb), self);
229 }
230
231 static void
232 empathy_mic_menu_notify_microphone_cb (EmpathyGstAudioSrc *audio,
233     GParamSpec *pspec,
234     EmpathyMicMenu *self)
235 {
236   empathy_mic_menu_update (self);
237 }
238
239 static void
240 empathy_mic_menu_microphone_added_cb (EmpathyGstAudioSrc *audio,
241     guint source_idx,
242     const gchar *name,
243     const gchar *description,
244     EmpathyMicMenu *self)
245 {
246   empathy_mic_menu_add_microphone (self, name, description, source_idx);
247
248   empathy_mic_menu_update (self);
249 }
250
251 static void
252 empathy_mic_menu_microphone_removed_cb (EmpathyGstAudioSrc *audio,
253     guint source_idx,
254     EmpathyMicMenu *self)
255 {
256   EmpathyMicMenuPrivate *priv = self->priv;
257   GList *l;
258
259   for (l = priv->microphones->head; l != NULL; l = l->next)
260     {
261       GtkRadioAction *action = l->data;
262       gint value;
263
264       g_object_get (action, "value", &value, NULL);
265
266       if (value != (gint) source_idx)
267         {
268           action = NULL;
269           continue;
270         }
271
272       g_signal_handlers_disconnect_by_func (action,
273           G_CALLBACK (empathy_mic_menu_activate_cb), self);
274
275       gtk_action_group_remove_action (priv->action_group, GTK_ACTION (action));
276       g_queue_remove (priv->microphones, action);
277       break;
278     }
279
280   empathy_mic_menu_update (self);
281 }
282
283 static void
284 empathy_mic_menu_get_microphones_cb (GObject *source_object,
285     GAsyncResult *result,
286     gpointer user_data)
287 {
288   EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object);
289   EmpathyMicMenu *self = user_data;
290   GError *error = NULL;
291   const GList *mics = NULL;
292
293   mics = empathy_audio_src_get_microphones_finish (audio, result, &error);
294
295   if (error != NULL)
296     {
297       g_debug ("Failed to get microphone list: %s", error->message);
298       g_clear_error (&error);
299       return;
300     }
301
302   for (; mics != NULL; mics = mics->next)
303     {
304       EmpathyAudioSrcMicrophone *mic = mics->data;
305
306       empathy_mic_menu_add_microphone (self, mic->name,
307           mic->description, mic->index);
308     }
309
310   empathy_mic_menu_update (self);
311 }
312
313 static void
314 empathy_mic_menu_constructed (GObject *obj)
315 {
316   EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj);
317   EmpathyMicMenuPrivate *priv = self->priv;
318   GtkUIManager *ui_manager;
319   EmpathyGstAudioSrc *audio;
320
321   g_assert (EMPATHY_IS_CALL_WINDOW (priv->window));
322
323   ui_manager = empathy_call_window_get_ui_manager (priv->window);
324   audio = empathy_call_window_get_audio_src (priv->window);
325
326   g_assert (GTK_IS_UI_MANAGER (ui_manager));
327   g_assert (EMPATHY_IS_GST_AUDIO_SRC (audio));
328
329   /* Okay let's go go go. */
330
331   priv->action_group = gtk_action_group_new ("EmpathyMicMenu");
332   gtk_ui_manager_insert_action_group (ui_manager, priv->action_group, -1);
333   /* the UI manager now owns this */
334   g_object_unref (priv->action_group);
335
336   priv->anchor_action = g_object_new (GTK_TYPE_RADIO_ACTION,
337       "name", "EmpathyMicMenuAnchorAction",
338       NULL);
339   gtk_action_group_add_action (priv->action_group, priv->anchor_action);
340   g_object_unref (priv->anchor_action);
341
342   tp_g_signal_connect_object (audio, "notify::microphone",
343       G_CALLBACK (empathy_mic_menu_notify_microphone_cb), self, 0);
344   tp_g_signal_connect_object (audio, "microphone-added",
345       G_CALLBACK (empathy_mic_menu_microphone_added_cb), self, 0);
346   tp_g_signal_connect_object (audio, "microphone-removed",
347       G_CALLBACK (empathy_mic_menu_microphone_removed_cb), self, 0);
348
349   priv->microphones = g_queue_new ();
350
351   empathy_audio_src_get_microphones_async (audio,
352       empathy_mic_menu_get_microphones_cb, self);
353 }
354
355 static void
356 empathy_mic_menu_dispose (GObject *obj)
357 {
358   EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj);
359   EmpathyMicMenuPrivate *priv = self->priv;
360
361   if (priv->microphones != NULL)
362     g_queue_free (priv->microphones);
363   priv->microphones = NULL;
364
365   G_OBJECT_CLASS (empathy_mic_menu_parent_class)->dispose (obj);
366 }
367
368 static void
369 empathy_mic_menu_class_init (EmpathyMicMenuClass *klass)
370 {
371   GObjectClass *object_class = G_OBJECT_CLASS (klass);
372
373   object_class->set_property = empathy_mic_menu_set_property;
374   object_class->get_property = empathy_mic_menu_get_property;
375   object_class->constructed = empathy_mic_menu_constructed;
376   object_class->dispose = empathy_mic_menu_dispose;
377
378   g_object_class_install_property (object_class, PROP_WINDOW,
379       g_param_spec_object ("window", "window", "window",
380           EMPATHY_TYPE_CALL_WINDOW,
381           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
382
383   g_type_class_add_private (object_class, sizeof (EmpathyMicMenuPrivate));
384 }
385
386 EmpathyMicMenu *
387 empathy_mic_menu_new (EmpathyCallWindow *window)
388 {
389   return g_object_new (EMPATHY_TYPE_MIC_MENU,
390       "window", window,
391       NULL);
392 }