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