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