]> git.0d.be Git - empathy.git/blob - src/empathy-camera-menu.c
Merge branch 'gnome-3-8'
[empathy.git] / src / empathy-camera-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-camera-menu.h"
24
25 #include "empathy-camera-monitor.h"
26 #include "empathy-gsettings.h"
27
28 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
29 #include "empathy-debug.h"
30
31 struct _EmpathyCameraMenuPrivate
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 cameras 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 *cameras;
53
54   EmpathyCameraMonitor *camera_monitor;
55
56   GSettings *settings;
57 };
58
59 G_DEFINE_TYPE (EmpathyCameraMenu, empathy_camera_menu, G_TYPE_OBJECT);
60
61 enum
62 {
63   PROP_WINDOW = 1,
64 };
65
66 static void empathy_camera_menu_update (EmpathyCameraMenu *self);
67
68 static void
69 empathy_camera_menu_init (EmpathyCameraMenu *self)
70 {
71   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
72     EMPATHY_TYPE_CAMERA_MENU, EmpathyCameraMenuPrivate);
73 }
74
75 static void
76 empathy_camera_menu_set_property (GObject *object,
77     guint property_id,
78     const GValue *value,
79     GParamSpec *pspec)
80 {
81   EmpathyCameraMenu *self = EMPATHY_CAMERA_MENU (object);
82
83   switch (property_id)
84     {
85       case PROP_WINDOW:
86         self->priv->window = g_value_get_object (value);
87         break;
88       default:
89         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
90     }
91 }
92
93 static void
94 empathy_camera_menu_get_property (GObject *object,
95     guint property_id,
96     GValue *value,
97     GParamSpec *pspec)
98 {
99   EmpathyCameraMenu *self = EMPATHY_CAMERA_MENU (object);
100
101   switch (property_id)
102     {
103       case PROP_WINDOW:
104         g_value_set_object (value, self->priv->window);
105         break;
106       default:
107         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
108     }
109 }
110
111 static void
112 empathy_camera_menu_clean (EmpathyCameraMenu *self)
113 {
114   GtkUIManager *ui_manager;
115
116   if (self->priv->ui_id == 0)
117     return;
118
119   ui_manager = empathy_call_window_get_ui_manager (self->priv->window);
120
121   gtk_ui_manager_remove_ui (ui_manager, self->priv->ui_id);
122   gtk_ui_manager_ensure_update (ui_manager);
123   self->priv->ui_id = 0;
124 }
125
126 static void
127 empathy_camera_menu_activate_cb (GtkAction *action,
128     EmpathyCameraMenu *self)
129 {
130   EmpathyGstVideoSrc *video;
131   const gchar *device;
132   gchar *current_device = NULL;
133
134   if (self->priv->in_update)
135     return;
136
137   video = empathy_call_window_get_video_src (self->priv->window);
138   if (video != NULL)
139     current_device = empathy_video_src_dup_device (video);
140
141   device = gtk_action_get_name (action);
142
143   /* Don't change the device if it's the currently used one */
144   if (!tp_strdiff (device, current_device))
145     goto out;
146
147   empathy_call_window_change_webcam (self->priv->window, device);
148
149  out:
150   g_free (current_device);
151 }
152
153 static void
154 empathy_camera_menu_update (EmpathyCameraMenu *self)
155 {
156   GList *l;
157   GtkAction *menu;
158   GtkUIManager *ui_manager;
159   EmpathyGstVideoSrc *video;
160   gboolean show_menu;
161   gchar *current_camera = NULL;
162   guint n_cameras;
163
164   ui_manager = empathy_call_window_get_ui_manager (self->priv->window);
165
166   menu = gtk_ui_manager_get_action (ui_manager, "/menubar1/edit/menucamera");
167   n_cameras = g_queue_get_length (self->priv->cameras);
168   show_menu = (n_cameras > 1);
169   gtk_action_set_visible (menu, show_menu);
170
171   video = empathy_call_window_get_video_src (self->priv->window);
172   if (video != NULL)
173     current_camera = empathy_video_src_dup_device (video);
174
175   empathy_camera_menu_clean (self);
176   self->priv->ui_id = gtk_ui_manager_new_merge_id (ui_manager);
177
178   for (l = self->priv->cameras->head; l != NULL; l = g_list_next (l))
179     {
180       GtkRadioAction *action = l->data;
181       const gchar *name = gtk_action_get_name (GTK_ACTION (action));
182
183       if (!tp_strdiff (current_camera, name))
184         {
185           self->priv->in_update = TRUE;
186           gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
187           self->priv->in_update = FALSE;
188         }
189
190       gtk_ui_manager_add_ui (ui_manager, self->priv->ui_id,
191           /* TODO: this should probably be passed from the call
192            * window, seeing that it's a reference to
193            * empathy-call-window.ui. */
194           "/menubar1/edit/menucamera",
195           name, name, GTK_UI_MANAGER_MENUITEM, FALSE);
196     }
197
198   g_free (current_camera);
199 }
200
201 static void
202 empathy_camera_menu_add_camera (EmpathyCameraMenu *self,
203     EmpathyCamera *camera)
204 {
205   GtkRadioAction *action;
206   GSList *group;
207
208   action = gtk_radio_action_new (camera->device, camera->name, NULL, NULL, 0);
209   gtk_action_group_add_action (self->priv->action_group, GTK_ACTION (action));
210
211   group = gtk_radio_action_get_group (
212       GTK_RADIO_ACTION (self->priv->anchor_action));
213   gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group);
214
215   g_queue_push_tail (self->priv->cameras, action);
216
217   g_signal_connect (action, "activate",
218       G_CALLBACK (empathy_camera_menu_activate_cb), self);
219 }
220
221 static void
222 empathy_camera_menu_camera_added_cb (EmpathyCameraMonitor *monitor,
223     EmpathyCamera *camera,
224     EmpathyCameraMenu *self)
225 {
226   empathy_camera_menu_add_camera (self, camera);
227   empathy_camera_menu_update (self);
228 }
229
230 static void
231 empathy_camera_menu_camera_removed_cb (EmpathyCameraMonitor *monitor,
232     EmpathyCamera *camera,
233     EmpathyCameraMenu *self)
234 {
235   GList *l;
236
237   for (l = self->priv->cameras->head; l != NULL; l = g_list_next (l))
238     {
239       GtkAction *action = l->data;
240       const gchar *device;
241
242       device = gtk_action_get_name (action);
243
244       if (tp_strdiff (device, camera->device))
245         continue;
246
247       g_signal_handlers_disconnect_by_func (action,
248           G_CALLBACK (empathy_camera_menu_activate_cb), self);
249
250       gtk_action_group_remove_action (self->priv->action_group,
251           action);
252       g_queue_remove (self->priv->cameras, action);
253       break;
254     }
255
256   empathy_camera_menu_update (self);
257 }
258
259 static void
260 empathy_camera_menu_prefs_camera_changed_cb (GSettings *settings,
261     gchar *key,
262     EmpathyCameraMenu *self)
263 {
264   gchar *device = g_settings_get_string (settings, key);
265   GtkRadioAction *action = NULL;
266   gboolean found = FALSE;
267   GList *l;
268
269   for (l = self->priv->cameras->head; l != NULL; l = g_list_next (l))
270     {
271       const gchar *name;
272
273       action = l->data;
274       name = gtk_action_get_name (GTK_ACTION (action));
275
276       if (!tp_strdiff (device, name))
277         {
278           found = TRUE;
279           break;
280         }
281     }
282
283   /* If the selected camera isn't found, we connect the first
284    * available one */
285   if (!found && self->priv->cameras->head != NULL)
286     action = self->priv->cameras->head->data;
287
288   if (action != NULL &&
289       !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
290     {
291       g_signal_handlers_block_by_func (settings,
292           empathy_camera_menu_prefs_camera_changed_cb, self);
293       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
294       g_signal_handlers_unblock_by_func (settings,
295           empathy_camera_menu_prefs_camera_changed_cb, self);
296     }
297
298   g_free (device);
299 }
300
301 static void
302 empathy_camera_menu_get_cameras (EmpathyCameraMenu *self)
303 {
304   const GList *cameras;
305
306   cameras = empathy_camera_monitor_get_cameras (self->priv->camera_monitor);
307
308   for (; cameras != NULL; cameras = g_list_next (cameras))
309     {
310       EmpathyCamera *camera = cameras->data;
311
312       empathy_camera_menu_add_camera (self, camera);
313     }
314
315   empathy_camera_menu_update (self);
316
317   /* Do as if the gsettings key had changed, so we select the key that
318    * was last set. */
319   empathy_camera_menu_prefs_camera_changed_cb (self->priv->settings,
320       EMPATHY_PREFS_CALL_CAMERA_DEVICE, self);
321 }
322
323 static void
324 empathy_camera_menu_constructed (GObject *obj)
325 {
326   EmpathyCameraMenu *self = EMPATHY_CAMERA_MENU (obj);
327   GtkUIManager *ui_manager;
328
329   g_assert (EMPATHY_IS_CALL_WINDOW (self->priv->window));
330
331   ui_manager = empathy_call_window_get_ui_manager (self->priv->window);
332
333   g_assert (GTK_IS_UI_MANAGER (ui_manager));
334
335   /* Okay let's go go go. */
336
337   self->priv->action_group = gtk_action_group_new ("EmpathyCameraMenu");
338   gtk_ui_manager_insert_action_group (ui_manager, self->priv->action_group, -1);
339   /* the UI manager now owns this */
340   g_object_unref (self->priv->action_group);
341
342   self->priv->anchor_action = g_object_new (GTK_TYPE_RADIO_ACTION,
343       "name", "EmpathyCameraMenuAnchorAction",
344       NULL);
345   gtk_action_group_add_action (self->priv->action_group,
346       self->priv->anchor_action);
347   g_object_unref (self->priv->anchor_action);
348
349   self->priv->camera_monitor = empathy_camera_monitor_new ();
350
351   tp_g_signal_connect_object (self->priv->camera_monitor, "added",
352       G_CALLBACK (empathy_camera_menu_camera_added_cb), self, 0);
353   tp_g_signal_connect_object (self->priv->camera_monitor, "removed",
354       G_CALLBACK (empathy_camera_menu_camera_removed_cb), self, 0);
355
356   self->priv->settings = g_settings_new (EMPATHY_PREFS_CALL_SCHEMA);
357   g_signal_connect (self->priv->settings,
358       "changed::"EMPATHY_PREFS_CALL_CAMERA_DEVICE,
359       G_CALLBACK (empathy_camera_menu_prefs_camera_changed_cb), self);
360
361   self->priv->cameras = g_queue_new ();
362
363   empathy_camera_menu_get_cameras (self);
364 }
365
366 static void
367 empathy_camera_menu_dispose (GObject *obj)
368 {
369   EmpathyCameraMenu *self = EMPATHY_CAMERA_MENU (obj);
370
371   tp_clear_pointer (&self->priv->cameras, g_queue_free);
372
373   tp_clear_object (&self->priv->camera_monitor);
374   tp_clear_object (&self->priv->settings);
375
376   G_OBJECT_CLASS (empathy_camera_menu_parent_class)->dispose (obj);
377 }
378
379 static void
380 empathy_camera_menu_class_init (EmpathyCameraMenuClass *klass)
381 {
382   GObjectClass *object_class = G_OBJECT_CLASS (klass);
383
384   object_class->set_property = empathy_camera_menu_set_property;
385   object_class->get_property = empathy_camera_menu_get_property;
386   object_class->constructed = empathy_camera_menu_constructed;
387   object_class->dispose = empathy_camera_menu_dispose;
388
389   g_object_class_install_property (object_class, PROP_WINDOW,
390       g_param_spec_object ("window", "window", "window",
391           EMPATHY_TYPE_CALL_WINDOW,
392           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
393
394   g_type_class_add_private (object_class, sizeof (EmpathyCameraMenuPrivate));
395 }
396
397 EmpathyCameraMenu *
398 empathy_camera_menu_new (EmpathyCallWindow *window)
399 {
400   return g_object_new (EMPATHY_TYPE_CAMERA_MENU,
401       "window", window,
402       NULL);
403 }
404
405 void
406 empathy_camera_menu_set_sensitive (EmpathyCameraMenu *self,
407     gboolean sensitive)
408 {
409   GtkUIManager *ui_manager;
410
411   gtk_action_group_set_sensitive (self->priv->action_group, sensitive);
412   if (sensitive) /* Mark the active camera as such. */
413     empathy_camera_menu_update (self);
414
415   ui_manager = empathy_call_window_get_ui_manager (self->priv->window);
416   gtk_ui_manager_ensure_update (ui_manager);
417 }