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