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