]> git.0d.be Git - empathy.git/blob - src/empathy-mic-menu.c
Merge branch 'change-audio'
[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 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
29 #include <libempathy/empathy-debug.h>
30
31 struct _EmpathyMicMenuPrivate
32 {
33   /* Borrowed ref; the call window actually owns us. */
34   EmpathyCallWindow *window;
35
36   /* Given away ref; the call window's UI manager now owns this. */
37   GtkActionGroup *action_group;
38
39   /* An invisible radio action so new microphones are always in the
40    * same radio group. */
41   GtkAction *anchor_action;
42
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. */
46   guint ui_id;
47
48   /* TRUE if we're in _update and so calling _set_active. */
49   gboolean in_update;
50
51   /* Queue of GtkRadioActions. */
52   GQueue *microphones;
53 };
54
55 G_DEFINE_TYPE (EmpathyMicMenu, empathy_mic_menu, G_TYPE_OBJECT);
56
57 #define MONITOR_KEY "empathy-mic-menu-is-monitor"
58
59 enum
60 {
61   PROP_WINDOW = 1,
62 };
63
64 static void empathy_mic_menu_update (EmpathyMicMenu *self);
65
66 static void
67 empathy_mic_menu_init (EmpathyMicMenu *self)
68 {
69   EmpathyMicMenuPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
70     EMPATHY_TYPE_MIC_MENU, EmpathyMicMenuPrivate);
71
72   self->priv = priv;
73 }
74
75 static void
76 empathy_mic_menu_set_property (GObject *object,
77     guint property_id,
78     const GValue *value,
79     GParamSpec *pspec)
80 {
81   EmpathyMicMenu *self = EMPATHY_MIC_MENU (object);
82   EmpathyMicMenuPrivate *priv = self->priv;
83
84   switch (property_id)
85     {
86       case PROP_WINDOW:
87         priv->window = g_value_get_object (value);
88         break;
89       default:
90         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
91     }
92 }
93
94 static void
95 empathy_mic_menu_get_property (GObject *object,
96     guint property_id,
97     GValue *value,
98     GParamSpec *pspec)
99 {
100   EmpathyMicMenu *self = EMPATHY_MIC_MENU (object);
101   EmpathyMicMenuPrivate *priv = self->priv;
102
103   switch (property_id)
104     {
105       case PROP_WINDOW:
106         g_value_set_object (value, priv->window);
107         break;
108       default:
109         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
110     }
111 }
112
113 static void
114 empathy_mic_menu_clean (EmpathyMicMenu *self)
115 {
116   EmpathyMicMenuPrivate *priv = self->priv;
117   GtkUIManager *ui_manager;
118
119   if (priv->ui_id == 0)
120     return;
121
122   ui_manager = empathy_call_window_get_ui_manager (priv->window);
123
124   gtk_ui_manager_remove_ui (ui_manager, priv->ui_id);
125   gtk_ui_manager_ensure_update (ui_manager);
126   priv->ui_id = 0;
127 }
128
129 static void
130 empathy_mic_menu_change_mic_cb (GObject *source_object,
131     GAsyncResult *result,
132     gpointer user_data)
133 {
134   EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object);
135   EmpathyMicMenu *self = user_data;
136   GError *error = NULL;
137
138   if (!empathy_audio_src_change_microphone_finish (audio, result, &error))
139     {
140       DEBUG ("Failed to change microphone: %s", error->message);
141       g_clear_error (&error);
142
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);
150     }
151 }
152
153 static void
154 empathy_mic_menu_activate_cb (GtkToggleAction *action,
155     EmpathyMicMenu *self)
156 {
157   EmpathyMicMenuPrivate *priv = self->priv;
158   EmpathyGstAudioSrc *audio;
159   gint value;
160
161   if (priv->in_update)
162     return;
163
164   audio = empathy_call_window_get_audio_src (priv->window);
165
166   g_object_get (action, "value", &value, NULL);
167
168   empathy_audio_src_change_microphone_async (audio, value,
169       empathy_mic_menu_change_mic_cb, self);
170 }
171
172 static void
173 empathy_mic_menu_update (EmpathyMicMenu *self)
174 {
175   EmpathyMicMenuPrivate *priv = self->priv;
176   GList *l;
177   GtkUIManager *ui_manager;
178   EmpathyGstAudioSrc *audio;
179   guint current_mic;
180
181   ui_manager = empathy_call_window_get_ui_manager (priv->window);
182
183   audio = empathy_call_window_get_audio_src (priv->window);
184   current_mic = empathy_audio_src_get_microphone (audio);
185
186   empathy_mic_menu_clean (self);
187   priv->ui_id = gtk_ui_manager_new_merge_id (ui_manager);
188
189   for (l = priv->microphones->head; l != NULL; l = l->next)
190     {
191       GtkRadioAction *action = l->data;
192       const gchar *name = gtk_action_get_name (GTK_ACTION (action));
193       gint value;
194       gboolean active;
195
196       g_object_get (action, "value", &value, NULL);
197
198       active = (value == (gint) current_mic);
199
200       if (active)
201         {
202           priv->in_update = TRUE;
203           gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
204           priv->in_update = FALSE;
205         }
206
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
213           && !active)
214         continue;
215
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);
222     }
223 }
224
225 static void
226 empathy_mic_menu_add_microphone (EmpathyMicMenu *self,
227     const gchar *name,
228     const gchar *description,
229     guint source_idx,
230     gboolean is_monitor)
231 {
232   EmpathyMicMenuPrivate *priv = self->priv;
233   GtkRadioAction *action;
234   GSList *group;
235
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);
239
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. */
242   if (is_monitor)
243     {
244       g_object_set_data (G_OBJECT (action), MONITOR_KEY,
245           GUINT_TO_POINTER (TRUE));
246     }
247
248   group = gtk_radio_action_get_group (GTK_RADIO_ACTION (priv->anchor_action));
249   gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group);
250
251   g_queue_push_tail (priv->microphones, action);
252
253   g_signal_connect (action, "activate",
254       G_CALLBACK (empathy_mic_menu_activate_cb), self);
255 }
256
257 static void
258 empathy_mic_menu_notify_microphone_cb (EmpathyGstAudioSrc *audio,
259     GParamSpec *pspec,
260     EmpathyMicMenu *self)
261 {
262   empathy_mic_menu_update (self);
263 }
264
265 static void
266 empathy_mic_menu_microphone_added_cb (EmpathyGstAudioSrc *audio,
267     guint source_idx,
268     const gchar *name,
269     const gchar *description,
270     gboolean is_monitor,
271     EmpathyMicMenu *self)
272 {
273   empathy_mic_menu_add_microphone (self, name, description,
274       source_idx, is_monitor);
275
276   empathy_mic_menu_update (self);
277 }
278
279 static void
280 empathy_mic_menu_microphone_removed_cb (EmpathyGstAudioSrc *audio,
281     guint source_idx,
282     EmpathyMicMenu *self)
283 {
284   EmpathyMicMenuPrivate *priv = self->priv;
285   GList *l;
286
287   for (l = priv->microphones->head; l != NULL; l = l->next)
288     {
289       GtkRadioAction *action = l->data;
290       gint value;
291
292       g_object_get (action, "value", &value, NULL);
293
294       if (value != (gint) source_idx)
295         {
296           action = NULL;
297           continue;
298         }
299
300       g_signal_handlers_disconnect_by_func (action,
301           G_CALLBACK (empathy_mic_menu_activate_cb), self);
302
303       gtk_action_group_remove_action (priv->action_group, GTK_ACTION (action));
304       g_queue_remove (priv->microphones, action);
305       break;
306     }
307
308   empathy_mic_menu_update (self);
309 }
310
311 static void
312 empathy_mic_menu_get_microphones_cb (GObject *source_object,
313     GAsyncResult *result,
314     gpointer user_data)
315 {
316   EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object);
317   EmpathyMicMenu *self = user_data;
318   GError *error = NULL;
319   const GList *mics = NULL;
320
321   mics = empathy_audio_src_get_microphones_finish (audio, result, &error);
322
323   if (error != NULL)
324     {
325       DEBUG ("Failed to get microphone list: %s", error->message);
326       g_clear_error (&error);
327       return;
328     }
329
330   for (; mics != NULL; mics = mics->next)
331     {
332       EmpathyAudioSrcMicrophone *mic = mics->data;
333
334       empathy_mic_menu_add_microphone (self, mic->name,
335           mic->description, mic->index, mic->is_monitor);
336     }
337
338   empathy_mic_menu_update (self);
339 }
340
341 static void
342 empathy_mic_menu_constructed (GObject *obj)
343 {
344   EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj);
345   EmpathyMicMenuPrivate *priv = self->priv;
346   GtkUIManager *ui_manager;
347   EmpathyGstAudioSrc *audio;
348
349   g_assert (EMPATHY_IS_CALL_WINDOW (priv->window));
350
351   ui_manager = empathy_call_window_get_ui_manager (priv->window);
352   audio = empathy_call_window_get_audio_src (priv->window);
353
354   g_assert (GTK_IS_UI_MANAGER (ui_manager));
355   g_assert (EMPATHY_IS_GST_AUDIO_SRC (audio));
356
357   /* Okay let's go go go. */
358
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);
363
364   priv->anchor_action = g_object_new (GTK_TYPE_RADIO_ACTION,
365       "name", "EmpathyMicMenuAnchorAction",
366       NULL);
367   gtk_action_group_add_action (priv->action_group, priv->anchor_action);
368   g_object_unref (priv->anchor_action);
369
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);
376
377   priv->microphones = g_queue_new ();
378
379   empathy_audio_src_get_microphones_async (audio,
380       empathy_mic_menu_get_microphones_cb, self);
381 }
382
383 static void
384 empathy_mic_menu_dispose (GObject *obj)
385 {
386   EmpathyMicMenu *self = EMPATHY_MIC_MENU (obj);
387   EmpathyMicMenuPrivate *priv = self->priv;
388
389   if (priv->microphones != NULL)
390     g_queue_free (priv->microphones);
391   priv->microphones = NULL;
392
393   G_OBJECT_CLASS (empathy_mic_menu_parent_class)->dispose (obj);
394 }
395
396 static void
397 empathy_mic_menu_class_init (EmpathyMicMenuClass *klass)
398 {
399   GObjectClass *object_class = G_OBJECT_CLASS (klass);
400
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;
405
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));
410
411   g_type_class_add_private (object_class, sizeof (EmpathyMicMenuPrivate));
412 }
413
414 EmpathyMicMenu *
415 empathy_mic_menu_new (EmpathyCallWindow *window)
416 {
417   return g_object_new (EMPATHY_TYPE_MIC_MENU,
418       "window", window,
419       NULL);
420 }