]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
When no video is received or sent we show contacts' avatars instead of showing
[empathy.git] / src / empathy-call-window.c
1 /*
2  * empathy-call-window.c - Source for EmpathyCallWindow
3  * Copyright (C) 2008-2009 Collabora Ltd.
4  * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <math.h>
26
27 #include <gdk/gdkkeysyms.h>
28 #include <gst/gst.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31
32 #include <telepathy-farsight/channel.h>
33
34 #include <libempathy/empathy-tp-contact-factory.h>
35 #include <libempathy/empathy-utils.h>
36 #include <libempathy-gtk/empathy-avatar-image.h>
37 #include <libempathy-gtk/empathy-video-widget.h>
38 #include <libempathy-gtk/empathy-audio-src.h>
39 #include <libempathy-gtk/empathy-audio-sink.h>
40 #include <libempathy-gtk/empathy-video-src.h>
41 #include <libempathy-gtk/empathy-ui-utils.h>
42
43 #include "empathy-call-window.h"
44
45 #include "empathy-call-window-fullscreen.h"
46 #include "empathy-sidebar.h"
47
48 #define BUTTON_ID "empathy-call-dtmf-button-id"
49
50 #define CONTENT_HBOX_BORDER_WIDTH 6
51 #define CONTENT_HBOX_SPACING 3
52 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
53
54 #define SELF_VIDEO_SECTION_WIDTH 160
55 #define SELF_VIDEO_SECTION_HEIGTH 120
56
57 /* The avatar's default width and height are set to the same value because we
58    want a square icon. */
59 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
60 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
61
62 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
63
64 /* signal enum */
65 #if 0
66 enum
67 {
68     LAST_SIGNAL
69 };
70
71 static guint signals[LAST_SIGNAL] = {0};
72 #endif
73
74 enum {
75   PROP_CALL_HANDLER = 1,
76 };
77
78 /* private structure */
79 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
80
81 struct _EmpathyCallWindowPriv
82 {
83   gboolean dispose_has_run;
84   EmpathyCallHandler *handler;
85   EmpathyContact *contact;
86
87   gboolean connected;
88
89   GtkUIManager *ui_manager;
90   GtkWidget *video_output;
91   GtkWidget *video_preview;
92   GtkWidget *remote_user_avatar_widget;
93   GtkWidget *self_user_avatar_widget;
94   GtkWidget *sidebar;
95   GtkWidget *sidebar_button;
96   GtkWidget *statusbar;
97   GtkWidget *volume_button;
98   GtkWidget *mic_button;
99   GtkWidget *camera_button;
100   GtkWidget *toolbar;
101   GtkWidget *pane;
102   GtkAction *send_video;
103   GtkAction *menu_fullscreen;
104
105   /* We keep a reference on the hbox which contains the main content so we can
106      easilly repack everything when toggling fullscreen */
107   GtkWidget *content_hbox;
108
109   /* This vbox is contained in the content_hbox. When toggling fullscreen,
110      it needs to be repacked. We keep a reference on it for easier access. */
111   GtkWidget *vbox;
112
113   gulong video_output_motion_handler_id;
114
115   gdouble volume;
116   GtkAdjustment *audio_input_adj;
117
118   GtkWidget *dtmf_panel;
119
120   GstElement *video_input;
121   GstElement *audio_input;
122   GstElement *audio_output;
123   GstElement *pipeline;
124   GstElement *video_tee;
125
126   GstElement *funnel;
127   GstElement *liveadder;
128
129   guint context_id;
130
131   GTimer *timer;
132   guint timer_id;
133
134   GtkWidget *video_contrast;
135   GtkWidget *video_brightness;
136   GtkWidget *video_gamma;
137
138   GMutex *lock;
139   gboolean call_started;
140   gboolean sending_video;
141
142   EmpathyCallWindowFullscreen *fullscreen;
143   gboolean is_fullscreen;
144
145   /* Those fields represent the state of the window before it actually was in
146      fullscreen mode. */
147   gboolean sidebar_was_visible_before_fs;
148   gint original_width_before_fs;
149   gint original_height_before_fs;
150 };
151
152 #define GET_PRIV(o) \
153   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
154     EmpathyCallWindowPriv))
155
156 static void empathy_call_window_realized_cb (GtkWidget *widget,
157   EmpathyCallWindow *window);
158
159 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
160   GdkEvent *event, EmpathyCallWindow *window);
161
162 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
163   GdkEventWindowState *event, EmpathyCallWindow *window);
164
165 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
166   EmpathyCallWindow *window);
167
168 static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
169   EmpathyCallWindow *window);
170
171 static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
172   EmpathyCallWindow *window);
173
174 static void empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
175   EmpathyCallWindow *window);
176
177 static void empathy_call_window_mic_toggled_cb (
178   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
179
180 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
181   EmpathyCallWindow *window);
182
183 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
184   EmpathyCallWindow *window);
185
186 static void empathy_call_window_hangup_cb (gpointer object,
187   EmpathyCallWindow *window);
188
189 static void empathy_call_window_fullscreen_cb (gpointer object,
190   EmpathyCallWindow *window);
191
192 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
193
194 static gboolean empathy_call_window_video_button_press_cb (GtkWidget *video_output,
195   GdkEventButton *event, EmpathyCallWindow *window);
196
197 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
198   GdkEventKey *event, EmpathyCallWindow *window);
199
200 static gboolean empathy_call_window_video_output_motion_notify (GtkWidget *widget,
201   GdkEventMotion *event, EmpathyCallWindow *window);
202
203 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
204   guint button);
205
206 static void empathy_call_window_status_message (EmpathyCallWindow *window,
207   gchar *message);
208
209 static gboolean empathy_call_window_bus_message (GstBus *bus,
210   GstMessage *message, gpointer user_data);
211
212 static void
213 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
214   gdouble value, EmpathyCallWindow *window);
215
216 static void
217 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
218 {
219   EmpathyCallWindowPriv *priv = GET_PRIV (self);
220   GtkToolItem *tool_item;
221
222   /* Add an empty expanded GtkToolItem so the volume button is at the end of
223    * the toolbar. */
224   tool_item = gtk_tool_item_new ();
225   gtk_tool_item_set_expand (tool_item, TRUE);
226   gtk_widget_show (GTK_WIDGET (tool_item));
227   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
228
229   priv->volume_button = gtk_volume_button_new ();
230   /* FIXME listen to the audiosinks signals and update the button according to
231    * that, for now starting out at 1.0 and assuming only the app changes the
232    * volume will do */
233   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
234   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
235     G_CALLBACK (empathy_call_window_volume_changed_cb), self);
236
237   tool_item = gtk_tool_item_new ();
238   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
239   gtk_widget_show_all (GTK_WIDGET (tool_item));
240   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
241 }
242
243 static void
244 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
245 {
246   EmpathyCallWindowPriv *priv = GET_PRIV (window);
247   EmpathyTpCall *call;
248   GQuark button_quark;
249   TpDTMFEvent event;
250
251   g_object_get (priv->handler, "tp-call", &call, NULL);
252
253   button_quark = g_quark_from_static_string (BUTTON_ID);
254   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
255     button_quark));
256
257   empathy_tp_call_start_tone (call, event);
258
259   g_object_unref (call);
260 }
261
262 static void
263 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
264 {
265   EmpathyCallWindowPriv *priv = GET_PRIV (window);
266   EmpathyTpCall *call;
267
268   g_object_get (priv->handler, "tp-call", &call, NULL);
269
270   empathy_tp_call_stop_tone (call);
271
272   g_object_unref (call);
273 }
274
275 static GtkWidget *
276 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
277 {
278   GtkWidget *table;
279   int i;
280   GQuark button_quark;
281   struct {
282     gchar *label;
283     TpDTMFEvent event;
284   } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
285                       { "2", TP_DTMF_EVENT_DIGIT_2 },
286                       { "3", TP_DTMF_EVENT_DIGIT_3 },
287                       { "4", TP_DTMF_EVENT_DIGIT_4 },
288                       { "5", TP_DTMF_EVENT_DIGIT_5 },
289                       { "6", TP_DTMF_EVENT_DIGIT_6 },
290                       { "7", TP_DTMF_EVENT_DIGIT_7 },
291                       { "8", TP_DTMF_EVENT_DIGIT_8 },
292                       { "9", TP_DTMF_EVENT_DIGIT_9 },
293                       { "#", TP_DTMF_EVENT_HASH },
294                       { "0", TP_DTMF_EVENT_DIGIT_0 },
295                       { "*", TP_DTMF_EVENT_ASTERISK },
296                       { NULL, } };
297
298   button_quark = g_quark_from_static_string (BUTTON_ID);
299
300   table = gtk_table_new (4, 3, TRUE);
301
302   for (i = 0; dtmfbuttons[i].label != NULL; i++)
303     {
304       GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
305       gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
306         i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
307
308       g_object_set_qdata (G_OBJECT (button), button_quark,
309         GUINT_TO_POINTER (dtmfbuttons[i].event));
310
311       g_signal_connect (G_OBJECT (button), "pressed",
312         G_CALLBACK (dtmf_button_pressed_cb), self);
313       g_signal_connect (G_OBJECT (button), "released",
314         G_CALLBACK (dtmf_button_released_cb), self);
315     }
316
317   return table;
318 }
319
320 static GtkWidget *
321 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
322   gchar *label_text, GtkWidget *bin)
323 {
324    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
325    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
326    GtkWidget *label = gtk_label_new (label_text);
327
328    gtk_widget_set_sensitive (scale, FALSE);
329
330    gtk_container_add (GTK_CONTAINER (bin), vbox);
331
332    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
333    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
334    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
335
336    return scale;
337 }
338
339 static void
340 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
341   EmpathyCallWindow *self)
342
343 {
344   EmpathyCallWindowPriv *priv = GET_PRIV (self);
345
346   empathy_video_src_set_channel (priv->video_input,
347     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
348 }
349
350 static void
351 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
352   EmpathyCallWindow *self)
353
354 {
355   EmpathyCallWindowPriv *priv = GET_PRIV (self);
356
357   empathy_video_src_set_channel (priv->video_input,
358     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
359 }
360
361 static void
362 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
363   EmpathyCallWindow *self)
364
365 {
366   EmpathyCallWindowPriv *priv = GET_PRIV (self);
367
368   empathy_video_src_set_channel (priv->video_input,
369     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
370 }
371
372
373 static GtkWidget *
374 empathy_call_window_create_video_input (EmpathyCallWindow *self)
375 {
376   EmpathyCallWindowPriv *priv = GET_PRIV (self);
377   GtkWidget *hbox;
378
379   hbox = gtk_hbox_new (TRUE, 3);
380
381   priv->video_contrast = empathy_call_window_create_video_input_add_slider (
382     self,  _("Contrast"), hbox);
383
384   priv->video_brightness = empathy_call_window_create_video_input_add_slider (
385     self,  _("Brightness"), hbox);
386
387   priv->video_gamma = empathy_call_window_create_video_input_add_slider (
388     self,  _("Gamma"), hbox);
389
390   return hbox;
391 }
392
393 static void
394 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
395 {
396   EmpathyCallWindowPriv *priv = GET_PRIV (self);
397   guint supported;
398   GtkAdjustment *adj;
399
400   supported = empathy_video_src_get_supported_channels (priv->video_input);
401
402   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
403     {
404       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
405
406       gtk_adjustment_set_value (adj,
407         empathy_video_src_get_channel (priv->video_input,
408           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
409
410       g_signal_connect (G_OBJECT (adj), "value-changed",
411         G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
412
413       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
414     }
415
416   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
417     {
418       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
419
420       gtk_adjustment_set_value (adj,
421         empathy_video_src_get_channel (priv->video_input,
422           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
423
424       g_signal_connect (G_OBJECT (adj), "value-changed",
425         G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
426       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
427     }
428
429   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
430     {
431       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
432
433       gtk_adjustment_set_value (adj,
434         empathy_video_src_get_channel (priv->video_input,
435           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
436
437       g_signal_connect (G_OBJECT (adj), "value-changed",
438         G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
439       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
440     }
441 }
442
443 static void
444 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
445   EmpathyCallWindow *self)
446 {
447   EmpathyCallWindowPriv *priv = GET_PRIV (self);
448   gdouble volume;
449
450   volume =  gtk_adjustment_get_value (adj)/100.0;
451
452   /* Don't store the volume because of muting */
453   if (volume > 0 || gtk_toggle_tool_button_get_active (
454         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
455     priv->volume = volume;
456
457   /* Ensure that the toggle button is active if the volume is > 0 and inactive
458    * if it's smaller then 0 */
459   if ((volume > 0) != gtk_toggle_tool_button_get_active (
460         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
461     gtk_toggle_tool_button_set_active (
462       GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
463
464   empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
465     volume);
466 }
467
468 static void
469 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
470   gdouble level, GtkProgressBar *bar)
471 {
472   gdouble value;
473
474   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
475   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), value);
476 }
477
478 static GtkWidget *
479 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
480 {
481   EmpathyCallWindowPriv *priv = GET_PRIV (self);
482   GtkWidget *hbox, *vbox, *scale, *progress, *label;
483   GtkAdjustment *adj;
484
485   hbox = gtk_hbox_new (TRUE, 3);
486
487   vbox = gtk_vbox_new (FALSE, 3);
488   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
489
490   scale = gtk_vscale_new_with_range (0, 150, 100);
491   gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
492   label = gtk_label_new (_("Volume"));
493
494   priv->audio_input_adj = adj = gtk_range_get_adjustment (GTK_RANGE (scale));
495   priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
496     (priv->audio_input));
497   gtk_adjustment_set_value (adj, priv->volume * 100);
498
499   g_signal_connect (G_OBJECT (adj), "value-changed",
500     G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
501
502   gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 3);
503   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
504
505   progress = gtk_progress_bar_new ();
506   gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (progress),
507     GTK_PROGRESS_BOTTOM_TO_TOP);
508   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), 0);
509
510   g_signal_connect (priv->audio_input, "peak-level-changed",
511     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), progress);
512
513   gtk_box_pack_start (GTK_BOX (hbox), progress, FALSE, FALSE, 3);
514
515   return hbox;
516 }
517
518 static void
519 empathy_call_window_init (EmpathyCallWindow *self)
520 {
521   EmpathyCallWindowPriv *priv = GET_PRIV (self);
522   GtkBuilder *gui;
523   GtkWidget *top_vbox;
524   GtkWidget *h;
525   GtkWidget *arrow;
526   GtkWidget *page;
527   GtkWidget *remote_user_output_frame, *self_user_output_frame;
528   GtkWidget *remote_user_output_hbox, *self_user_output_hbox;
529   GstBus *bus;
530   gchar *filename;
531
532   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
533   gui = empathy_builder_get_file (filename,
534     "call_window_vbox", &top_vbox,
535     "pane", &priv->pane,
536     "statusbar", &priv->statusbar,
537     "microphone", &priv->mic_button,
538     "camera", &priv->camera_button,
539     "toolbar", &priv->toolbar,
540     "send_video", &priv->send_video,
541     "ui_manager", &priv->ui_manager,
542     "menufullscreen", &priv->menu_fullscreen,
543     NULL);
544
545   empathy_builder_connect (gui, self,
546     "menuhangup", "activate", empathy_call_window_hangup_cb,
547     "hangup", "clicked", empathy_call_window_hangup_cb,
548     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
549     "camera", "toggled", empathy_call_window_camera_toggled_cb,
550     "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
551     "show_preview", "toggled", empathy_call_window_show_preview_toggled_cb,
552     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
553     NULL);
554
555   priv->lock = g_mutex_new ();
556
557   gtk_container_add (GTK_CONTAINER (self), top_vbox);
558
559   empathy_call_window_setup_toolbar (self);
560
561   priv->pipeline = gst_pipeline_new (NULL);
562
563   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
564   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
565                                   CONTENT_HBOX_BORDER_WIDTH);
566   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
567
568   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
569
570   gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
571
572   remote_user_output_frame = gtk_frame_new (NULL);
573   gtk_widget_set_size_request (remote_user_output_frame,
574       EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
575   remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
576
577   priv->remote_user_avatar_widget = gtk_image_new ();
578   gtk_box_pack_start (GTK_BOX (remote_user_output_hbox),
579       priv->remote_user_avatar_widget, TRUE, TRUE, 0);
580
581   priv->video_output = empathy_video_widget_new (bus);
582   gtk_box_pack_start (GTK_BOX (remote_user_output_hbox),
583       priv->video_output, TRUE, TRUE, 0);
584
585   gtk_container_add (GTK_CONTAINER (remote_user_output_frame),
586       remote_user_output_hbox);
587
588   gtk_widget_add_events (priv->video_output,
589       GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
590   g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
591       G_CALLBACK (empathy_call_window_video_button_press_cb), self);
592   gtk_box_pack_start (GTK_BOX (priv->content_hbox), remote_user_output_frame,
593       TRUE, TRUE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
594
595   priv->video_tee = gst_element_factory_make ("tee", NULL);
596   gst_object_ref (priv->video_tee);
597   gst_object_sink (priv->video_tee);
598
599   priv->vbox = gtk_vbox_new (FALSE, 3);
600   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
601                       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
602
603   self_user_output_frame = gtk_frame_new (NULL);
604   gtk_widget_set_size_request (self_user_output_frame, SELF_VIDEO_SECTION_WIDTH,
605       SELF_VIDEO_SECTION_HEIGTH);
606   self_user_output_hbox = gtk_hbox_new (FALSE, 0);
607
608   priv->self_user_avatar_widget = gtk_image_new ();
609   gtk_box_pack_start (GTK_BOX (self_user_output_hbox),
610       priv->self_user_avatar_widget, TRUE, TRUE, 0);
611
612   priv->video_preview = empathy_video_widget_new_with_size (bus,
613       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
614   g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
615   gtk_box_pack_start (GTK_BOX (self_user_output_hbox), priv->video_preview,
616       TRUE, TRUE, 0);
617
618   gtk_container_add (GTK_CONTAINER (self_user_output_frame),
619       self_user_output_hbox);
620
621   gtk_box_pack_start (GTK_BOX (priv->vbox), self_user_output_frame, FALSE,
622       FALSE, 0);
623
624   priv->video_input = empathy_video_src_new ();
625   gst_object_ref (priv->video_input);
626   gst_object_sink (priv->video_input);
627
628   priv->audio_input = empathy_audio_src_new ();
629   gst_object_ref (priv->audio_input);
630   gst_object_sink (priv->audio_input);
631
632   priv->audio_output = empathy_audio_sink_new ();
633   gst_object_ref (priv->audio_output);
634   gst_object_sink (priv->audio_output);
635
636   g_object_unref (bus);
637
638   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
639   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
640   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
641     G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
642
643   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
644
645   h = gtk_hbox_new (FALSE, 3);
646   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
647   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
648
649   priv->sidebar = empathy_sidebar_new ();
650   g_signal_connect (G_OBJECT (priv->sidebar),
651     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
652   g_signal_connect (G_OBJECT (priv->sidebar),
653     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
654   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
655
656   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
657   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
658     priv->dtmf_panel);
659
660   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
661
662   page = empathy_call_window_create_audio_input (self);
663   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
664     page);
665
666   page = empathy_call_window_create_video_input (self);
667   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
668     page);
669
670   gtk_widget_show_all (top_vbox);
671
672   gtk_widget_hide (priv->sidebar);
673
674   priv->fullscreen = empathy_call_window_fullscreen_new (self);
675   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen, priv->video_output);
676   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
677       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
678
679   g_signal_connect (G_OBJECT (self), "realize",
680     G_CALLBACK (empathy_call_window_realized_cb), self);
681
682   g_signal_connect (G_OBJECT (self), "delete-event",
683     G_CALLBACK (empathy_call_window_delete_cb), self);
684
685   g_signal_connect (G_OBJECT (self), "window-state-event",
686     G_CALLBACK (empathy_call_window_state_event_cb), self);
687
688   g_signal_connect (G_OBJECT (self), "key-press-event",
689       G_CALLBACK (empathy_call_window_key_press_cb), self);
690
691   empathy_call_window_status_message (self, _("Connecting..."));
692
693   priv->timer = g_timer_new ();
694
695   g_object_ref (priv->ui_manager);
696   g_object_unref (gui);
697   g_free (filename);
698 }
699
700 /* Instead of specifying a width and a height, we specify only one size. That's
701    because we want a square avatar icon.  */
702 static void
703 init_contact_avatar_with_size (EmpathyContact *contact, GtkWidget *image_widget,
704     gint size)
705 {
706
707   GdkPixbuf *pixbuf_avatar;
708
709   if (contact != NULL)
710     {
711       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
712         size, size);
713     }
714
715   if (pixbuf_avatar == NULL)
716     {
717       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
718           size);
719     }
720
721   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
722 }
723
724 static void
725 set_window_title (EmpathyCallWindow *self)
726 {
727   EmpathyCallWindowPriv *priv = GET_PRIV (self);
728   gchar *tmp;
729
730   tmp = g_strdup_printf (_("Call with %s"),
731       empathy_contact_get_name (priv->contact));
732   gtk_window_set_title (GTK_WINDOW (self), tmp);
733   g_free (tmp);
734 }
735
736 static void
737 contact_name_changed_cb (EmpathyContact *contact,
738     GParamSpec *pspec, EmpathyCallWindow *self)
739 {
740   set_window_title (self);
741 }
742
743 static void
744 contact_avatar_changed_cb (EmpathyContact *contact,
745     GParamSpec *pspec, GtkWidget *avatar_widget)
746 {
747   init_contact_avatar_with_size (contact, avatar_widget,
748       avatar_widget->allocation.height);
749 }
750
751 static void
752 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
753     EmpathyContact *contact, const GError *error, gpointer user_data,
754     GObject *weak_object)
755 {
756   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
757   EmpathyCallWindowPriv *priv = GET_PRIV (self);
758
759   init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
760       MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
761
762   g_signal_connect (contact, "notify::avatar",
763       G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
764 }
765
766 static void
767 empathy_call_window_constructed (GObject *object)
768 {
769   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
770   EmpathyCallWindowPriv *priv = GET_PRIV (self);
771
772   g_assert (priv->handler != NULL);
773
774   g_object_get (priv->handler, "contact", &(priv->contact), NULL);
775
776   if (priv->contact != NULL)
777     {
778       TpConnection *connection;
779       EmpathyTpContactFactory *factory;
780
781       set_window_title (self);
782
783       g_signal_connect (priv->contact, "notify::name",
784           G_CALLBACK (contact_name_changed_cb), self);
785       g_signal_connect (priv->contact, "notify::avatar",
786           G_CALLBACK (contact_avatar_changed_cb),
787           priv->remote_user_avatar_widget);
788
789       /* Retreiving the self avatar */
790       connection = empathy_contact_get_connection (priv->contact);
791       factory = empathy_tp_contact_factory_dup_singleton(connection);
792       empathy_tp_contact_factory_get_from_handle (factory,
793           tp_connection_get_self_handle (connection),
794           empathy_call_window_got_self_contact_cb, self, NULL, NULL);
795
796       g_object_unref (factory);
797     }
798   else
799     {
800       g_warning ("call handler doesn't have a contact");
801       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
802
803       /* Since we can't access the remote contact, we can't get a connection
804          to it and can't get the self contact (and its avatar). This means
805          that we have to manually set the self avatar. */
806       init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
807           MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
808     }
809
810   init_contact_avatar_with_size (priv->contact,
811       priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
812       REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
813
814   /* We hide the self avatar. It will be shown if a problem is
815      encountered when we try to send video. As for the remote avatar, it
816      is shown by default and will be hidden when we receive video from
817      the remote side. */
818   gtk_widget_hide (priv->self_user_avatar_widget);
819   gtk_widget_hide (priv->video_output);
820   gtk_widget_show (priv->remote_user_avatar_widget);
821 }
822
823 static void empathy_call_window_dispose (GObject *object);
824 static void empathy_call_window_finalize (GObject *object);
825
826 static void
827 empathy_call_window_set_property (GObject *object,
828   guint property_id, const GValue *value, GParamSpec *pspec)
829 {
830   EmpathyCallWindowPriv *priv = GET_PRIV (object);
831
832   switch (property_id)
833     {
834       case PROP_CALL_HANDLER:
835         priv->handler = g_value_dup_object (value);
836         break;
837       default:
838         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
839     }
840 }
841
842 static void
843 empathy_call_window_get_property (GObject *object,
844   guint property_id, GValue *value, GParamSpec *pspec)
845 {
846   EmpathyCallWindowPriv *priv = GET_PRIV (object);
847
848   switch (property_id)
849     {
850       case PROP_CALL_HANDLER:
851         g_value_set_object (value, priv->handler);
852         break;
853       default:
854         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
855     }
856 }
857
858 static void
859 empathy_call_window_class_init (
860   EmpathyCallWindowClass *empathy_call_window_class)
861 {
862   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
863   GParamSpec *param_spec;
864
865   g_type_class_add_private (empathy_call_window_class,
866     sizeof (EmpathyCallWindowPriv));
867
868   object_class->constructed = empathy_call_window_constructed;
869   object_class->set_property = empathy_call_window_set_property;
870   object_class->get_property = empathy_call_window_get_property;
871
872   object_class->dispose = empathy_call_window_dispose;
873   object_class->finalize = empathy_call_window_finalize;
874
875   param_spec = g_param_spec_object ("handler",
876     "handler", "The call handler",
877     EMPATHY_TYPE_CALL_HANDLER,
878     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
879   g_object_class_install_property (object_class,
880     PROP_CALL_HANDLER, param_spec);
881
882 }
883
884 void
885 empathy_call_window_dispose (GObject *object)
886 {
887   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
888   EmpathyCallWindowPriv *priv = GET_PRIV (self);
889
890   if (priv->dispose_has_run)
891     return;
892
893   priv->dispose_has_run = TRUE;
894
895   if (priv->handler != NULL)
896     g_object_unref (priv->handler);
897
898   priv->handler = NULL;
899
900   if (priv->pipeline != NULL)
901     g_object_unref (priv->pipeline);
902   priv->pipeline = NULL;
903
904   if (priv->video_input != NULL)
905     g_object_unref (priv->video_input);
906   priv->video_input = NULL;
907
908   if (priv->audio_input != NULL)
909     g_object_unref (priv->audio_input);
910   priv->audio_input = NULL;
911
912   if (priv->audio_output != NULL)
913     g_object_unref (priv->audio_output);
914   priv->audio_output = NULL;
915
916   if (priv->video_tee != NULL)
917     g_object_unref (priv->video_tee);
918   priv->video_tee = NULL;
919
920   if (priv->timer_id != 0)
921     g_source_remove (priv->timer_id);
922   priv->timer_id = 0;
923
924   if (priv->ui_manager != NULL)
925     g_object_unref (priv->ui_manager);
926   priv->ui_manager = NULL;
927
928   if (priv->contact != NULL)
929     {
930       g_signal_handlers_disconnect_by_func (priv->contact,
931           contact_name_changed_cb, self);
932       g_object_unref (priv->contact);
933       priv->contact = NULL;
934     }
935
936   /* release any references held by the object here */
937   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
938     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
939 }
940
941 void
942 empathy_call_window_finalize (GObject *object)
943 {
944   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
945   EmpathyCallWindowPriv *priv = GET_PRIV (self);
946
947   if (priv->video_output_motion_handler_id != 0)
948     {
949       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
950           priv->video_output_motion_handler_id);
951       priv->video_output_motion_handler_id = 0;
952     }
953
954   /* free any data held directly by the object here */
955   g_mutex_free (priv->lock);
956
957   g_timer_destroy (priv->timer);
958
959   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
960 }
961
962
963 EmpathyCallWindow *
964 empathy_call_window_new (EmpathyCallHandler *handler)
965 {
966   return EMPATHY_CALL_WINDOW (
967     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
968 }
969
970 static void
971 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
972   GstElement *conference, gpointer user_data)
973 {
974   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
975   EmpathyCallWindowPriv *priv = GET_PRIV (self);
976
977   gst_bin_add (GST_BIN (priv->pipeline), conference);
978
979   gst_element_set_state (conference, GST_STATE_PLAYING);
980 }
981
982 static gboolean
983 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
984   FsMediaType type, FsStreamDirection direction, gpointer user_data)
985 {
986   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
987   EmpathyCallWindowPriv *priv = GET_PRIV (self);
988
989   if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
990     return TRUE;
991
992   if (direction == FS_DIRECTION_RECV)
993     return TRUE;
994
995   /* video and direction is send */
996   return priv->video_input != NULL;
997 }
998
999 static void
1000 empathy_call_window_disconnected (EmpathyCallWindow *self)
1001 {
1002   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1003
1004   g_mutex_lock (priv->lock);
1005
1006   g_timer_stop (priv->timer);
1007
1008   if (priv->timer_id != 0)
1009     g_source_remove (priv->timer_id);
1010   priv->timer_id = 0;
1011
1012   g_mutex_unlock (priv->lock);
1013
1014   empathy_call_window_status_message (self, _("Disconnected"));
1015
1016   gtk_widget_set_sensitive (priv->camera_button, FALSE);
1017   gtk_action_set_sensitive (priv->send_video, FALSE);
1018   priv->sending_video = FALSE;
1019 }
1020
1021
1022 static void
1023 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1024 {
1025   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1026
1027   empathy_call_window_disconnected (self);
1028 }
1029
1030 /* Called with global lock held */
1031 static GstPad *
1032 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1033 {
1034   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1035   GstPad *pad;
1036
1037   if (priv->funnel == NULL)
1038     {
1039       GstElement *output;
1040
1041       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1042         (priv->video_output));
1043
1044       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1045
1046       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1047       gst_bin_add (GST_BIN (priv->pipeline), output);
1048
1049       gst_element_link (priv->funnel, output);
1050
1051       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1052       gst_element_set_state (output, GST_STATE_PLAYING);
1053     }
1054
1055   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1056
1057   return pad;
1058 }
1059
1060 /* Called with global lock held */
1061 static GstPad *
1062 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1063 {
1064   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1065   GstPad *pad;
1066
1067   if (priv->liveadder == NULL)
1068     {
1069       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1070
1071       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1072       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1073
1074       gst_element_link (priv->liveadder, priv->audio_output);
1075
1076       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1077       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1078     }
1079
1080   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1081
1082   return pad;
1083 }
1084
1085 static gboolean
1086 empathy_call_window_update_timer (gpointer user_data)
1087 {
1088   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1089   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1090   gchar *str;
1091   gdouble time;
1092
1093   time = g_timer_elapsed (priv->timer, NULL);
1094
1095   /* Translators: number of minutes:seconds the caller has been connected */
1096   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time / 60,
1097     (int) time % 60);
1098   empathy_call_window_status_message (self, str);
1099   g_free (str);
1100
1101   return TRUE;
1102 }
1103
1104 static gboolean
1105 empathy_call_window_connected (gpointer user_data)
1106 {
1107   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1108   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1109   EmpathyTpCall *call;
1110
1111   g_object_get (priv->handler, "tp-call", &call, NULL);
1112
1113   if (empathy_tp_call_has_dtmf (call))
1114     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1115
1116   if (priv->video_input != NULL)
1117     {
1118       gtk_widget_set_sensitive (priv->camera_button, TRUE);
1119       gtk_action_set_sensitive (priv->send_video, TRUE);
1120     }
1121
1122   g_object_unref (call);
1123
1124   g_mutex_lock (priv->lock);
1125
1126   priv->timer_id = g_timeout_add_seconds (1,
1127     empathy_call_window_update_timer, self);
1128
1129   g_mutex_unlock (priv->lock);
1130
1131   empathy_call_window_update_timer (self);
1132
1133   return FALSE;
1134 }
1135
1136
1137 /* Called from the streaming thread */
1138 static void
1139 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1140   GstPad *src, guint media_type, gpointer user_data)
1141 {
1142   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1143   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1144
1145   GstPad *pad;
1146
1147   g_mutex_lock (priv->lock);
1148
1149   if (priv->connected == FALSE)
1150     {
1151       g_timer_start (priv->timer);
1152       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1153       priv->connected = TRUE;
1154     }
1155
1156   switch (media_type)
1157     {
1158       case TP_MEDIA_STREAM_TYPE_AUDIO:
1159         pad = empathy_call_window_get_audio_sink_pad (self);
1160         break;
1161       case TP_MEDIA_STREAM_TYPE_VIDEO:
1162         gtk_widget_hide (priv->remote_user_avatar_widget);
1163         gtk_widget_show (priv->video_output);
1164         pad = empathy_call_window_get_video_sink_pad (self);
1165         break;
1166       default:
1167         g_assert_not_reached ();
1168     }
1169
1170   gst_pad_link (src, pad);
1171   gst_object_unref (pad);
1172
1173   g_mutex_unlock (priv->lock);
1174 }
1175
1176 /* Called from the streaming thread */
1177 static void
1178 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1179   GstPad *sink, guint media_type, gpointer user_data)
1180 {
1181   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1182   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1183   GstPad *pad;
1184
1185   switch (media_type)
1186     {
1187       case TP_MEDIA_STREAM_TYPE_AUDIO:
1188         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1189
1190         pad = gst_element_get_static_pad (priv->audio_input, "src");
1191         gst_pad_link (pad, sink);
1192
1193         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1194         break;
1195       case TP_MEDIA_STREAM_TYPE_VIDEO:
1196         if (priv->video_input != NULL)
1197           {
1198             pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1199             gst_pad_link (pad, sink);
1200           }
1201         break;
1202       default:
1203         g_assert_not_reached ();
1204     }
1205
1206 }
1207
1208 static gboolean
1209 empathy_gst_bin_has_child (GstBin *bin, GstElement *element)
1210 {
1211   GstIterator *it;
1212   gboolean ret = FALSE;
1213   GstElement *item;
1214
1215   it = gst_bin_iterate_recurse (bin);
1216
1217   for (;;)
1218     {
1219       switch (gst_iterator_next (it, (gpointer *)&item))
1220        {
1221          case GST_ITERATOR_OK:
1222            if (item == element)
1223             {
1224               gst_object_unref (GST_OBJECT (item));
1225               ret = TRUE;
1226               goto out;
1227             }
1228            gst_object_unref (GST_OBJECT (item));
1229            break;
1230          case GST_ITERATOR_RESYNC:
1231            gst_iterator_resync (it);
1232            break;
1233         case GST_ITERATOR_ERROR:
1234            g_assert_not_reached ();
1235            /* fallthrough */
1236         case GST_ITERATOR_DONE:
1237            goto out;
1238            break;
1239       }
1240     }
1241     gst_iterator_free (it);
1242
1243 out:
1244   return ret;
1245 }
1246
1247 static void
1248 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1249 {
1250   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1251   GstElement *preview;
1252
1253   preview = empathy_video_widget_get_element (
1254     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1255
1256   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1257   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1258   gst_element_set_state (preview, GST_STATE_NULL);
1259
1260   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1261     priv->video_tee, preview, NULL);
1262
1263   g_object_unref (priv->video_input);
1264   priv->video_input = NULL;
1265   g_object_unref (priv->video_tee);
1266   priv->video_tee = NULL;
1267
1268   gtk_widget_hide (priv->video_preview);
1269   gtk_widget_show (priv->self_user_avatar_widget);
1270 }
1271
1272
1273 static gboolean
1274 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1275   gpointer user_data)
1276 {
1277   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1278   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1279   GstState newstate;
1280
1281   empathy_call_handler_bus_message (priv->handler, bus, message);
1282
1283   switch (GST_MESSAGE_TYPE (message))
1284     {
1285       case GST_MESSAGE_STATE_CHANGED:
1286         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1287           {
1288             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1289             if (newstate == GST_STATE_PAUSED)
1290                 empathy_call_window_setup_video_input (self);
1291           }
1292         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1293             !priv->call_started)
1294           {
1295             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1296             if (newstate == GST_STATE_PAUSED)
1297               {
1298                 priv->call_started = TRUE;
1299                 empathy_call_handler_start_call (priv->handler);
1300                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1301               }
1302           }
1303         break;
1304       case GST_MESSAGE_ERROR:
1305         {
1306           GError *error = NULL;
1307           gchar *debug;
1308
1309           gst_message_parse_error (message, &error, &debug);
1310
1311           g_message ("Element error: %s -- %s\n", error->message, debug);
1312
1313           if (priv->video_input != NULL &&
1314               empathy_gst_bin_has_child (GST_BIN (priv->video_input),
1315                 GST_ELEMENT (GST_MESSAGE_SRC (message))))
1316             {
1317               /* Remove the video input and continue */
1318               empathy_call_window_remove_video_input (self);
1319               gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1320             }
1321           else
1322             {
1323               gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1324               empathy_call_window_disconnected (self);
1325             }
1326           g_error_free (error);
1327           g_free (debug);
1328         }
1329       default:
1330         break;
1331     }
1332
1333   return TRUE;
1334 }
1335
1336 static void
1337 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1338 {
1339   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1340   GstElement *preview;
1341
1342   g_signal_connect (priv->handler, "conference-added",
1343     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1344   g_signal_connect (priv->handler, "request-resource",
1345     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1346   g_signal_connect (priv->handler, "closed",
1347     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1348   g_signal_connect (priv->handler, "src-pad-added",
1349     G_CALLBACK (empathy_call_window_src_added_cb), window);
1350   g_signal_connect (priv->handler, "sink-pad-added",
1351     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1352
1353
1354   preview = empathy_video_widget_get_element (
1355     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1356
1357   gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
1358     priv->video_tee, preview, NULL);
1359   gst_element_link_many (priv->video_input, priv->video_tee,
1360     preview, NULL);
1361
1362   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1363 }
1364
1365 static gboolean
1366 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1367   EmpathyCallWindow *window)
1368 {
1369   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1370
1371   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1372
1373   return FALSE;
1374 }
1375
1376 static void
1377 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1378 {
1379   GtkWidget *menu;
1380   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1381
1382   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1383             "/menubar1");
1384
1385   if (set_fullscreen)
1386     {
1387       gtk_widget_hide (priv->sidebar);
1388       gtk_widget_hide (menu);
1389       gtk_widget_hide (priv->vbox);
1390       gtk_widget_hide (priv->statusbar);
1391       gtk_widget_hide (priv->toolbar);
1392     }
1393   else
1394     {
1395       if (priv->sidebar_was_visible_before_fs)
1396         gtk_widget_show (priv->sidebar);
1397
1398       gtk_widget_show (menu);
1399       gtk_widget_show (priv->vbox);
1400       gtk_widget_show (priv->statusbar);
1401       gtk_widget_show (priv->toolbar);
1402
1403       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1404           priv->original_height_before_fs);
1405     }
1406 }
1407
1408 static void
1409 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1410 {
1411   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1412
1413   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1414       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1415   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1416       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1417   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1418       priv->video_output, TRUE, TRUE,
1419       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1420       GTK_PACK_START);
1421   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1422       priv->vbox, TRUE, TRUE,
1423       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1424       GTK_PACK_START);
1425 }
1426
1427 static gboolean
1428 empathy_call_window_state_event_cb (GtkWidget *widget,
1429   GdkEventWindowState *event, EmpathyCallWindow *window)
1430 {
1431   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1432     {
1433       EmpathyCallWindowPriv *priv = GET_PRIV (window);
1434       gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1435
1436       if (set_fullscreen)
1437         {
1438           gboolean sidebar_was_visible;
1439           gint original_width = GTK_WIDGET (window)->allocation.width;
1440           gint original_height = GTK_WIDGET (window)->allocation.height;
1441
1442           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1443
1444           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1445           priv->original_width_before_fs = original_width;
1446           priv->original_height_before_fs = original_height;
1447
1448           if (priv->video_output_motion_handler_id == 0 &&
1449                 priv->video_output != NULL)
1450             {
1451               priv->video_output_motion_handler_id = g_signal_connect (
1452                   G_OBJECT (priv->video_output), "motion-notify-event",
1453                   G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1454             }
1455         }
1456       else
1457         {
1458           if (priv->video_output_motion_handler_id != 0)
1459             {
1460               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1461                   priv->video_output_motion_handler_id);
1462               priv->video_output_motion_handler_id = 0;
1463             }
1464         }
1465
1466       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1467           set_fullscreen);
1468       show_controls (window, set_fullscreen);
1469       show_borders (window, set_fullscreen);
1470       gtk_action_set_stock_id (priv->menu_fullscreen,
1471           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1472       priv->is_fullscreen = set_fullscreen;
1473   }
1474
1475   return FALSE;
1476 }
1477
1478 static void
1479 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1480   EmpathyCallWindow *window)
1481 {
1482   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1483   GtkWidget *arrow;
1484   int w,h, handle_size;
1485
1486   w = GTK_WIDGET (window)->allocation.width;
1487   h = GTK_WIDGET (window)->allocation.height;
1488
1489   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1490
1491   if (gtk_toggle_button_get_active (toggle))
1492     {
1493       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1494       gtk_widget_show (priv->sidebar);
1495       w += priv->sidebar->allocation.width + handle_size;
1496     }
1497   else
1498     {
1499       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1500       w -= priv->sidebar->allocation.width + handle_size;
1501       gtk_widget_hide (priv->sidebar);
1502     }
1503
1504   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1505
1506   if (w > 0 && h > 0)
1507     gtk_window_resize (GTK_WINDOW (window), w, h);
1508 }
1509
1510 static void
1511 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1512   gboolean send)
1513 {
1514   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1515   EmpathyTpCall *call;
1516
1517   g_object_get (priv->handler, "tp-call", &call, NULL);
1518   empathy_tp_call_request_video_stream_direction (call, send);
1519   g_object_unref (call);
1520 }
1521
1522 static void
1523 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1524   EmpathyCallWindow *window)
1525 {
1526   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1527   gboolean active;
1528
1529   active = (gtk_toggle_tool_button_get_active (toggle));
1530
1531   if (priv->sending_video == active)
1532     return;
1533   priv->sending_video = active;
1534
1535   empathy_call_window_set_send_video (window, active);
1536   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1537 }
1538
1539 static void
1540 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1541   EmpathyCallWindow *window)
1542 {
1543   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1544   gboolean active;
1545
1546   active = (gtk_toggle_action_get_active (toggle));
1547
1548   if (priv->sending_video == active)
1549     return;
1550   priv->sending_video = active;
1551
1552   empathy_call_window_set_send_video (window, active);
1553   gtk_toggle_tool_button_set_active (
1554       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1555 }
1556
1557 static void
1558 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1559   EmpathyCallWindow *window)
1560 {
1561   /* FIXME: Not implemented */
1562 }
1563
1564 static void
1565 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1566   EmpathyCallWindow *window)
1567 {
1568   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1569   gboolean active;
1570
1571   active = (gtk_toggle_tool_button_get_active (toggle));
1572
1573   if (active)
1574     {
1575       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1576         priv->volume);
1577       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1578     }
1579   else
1580     {
1581       /* TODO, Instead of setting the input volume to 0 we should probably
1582        * stop sending but this would cause the audio call to drop if both
1583        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1584        * in the future. GNOME #574574
1585        */
1586       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1587         0);
1588       gtk_adjustment_set_value (priv->audio_input_adj, 0);
1589     }
1590 }
1591
1592 static void
1593 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1594   EmpathyCallWindow *window)
1595 {
1596   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1597
1598   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1599     FALSE);
1600 }
1601
1602 static void
1603 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1604   EmpathyCallWindow *window)
1605 {
1606   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1607
1608   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1609     TRUE);
1610 }
1611
1612 static void
1613 empathy_call_window_hangup_cb (gpointer object,
1614                                EmpathyCallWindow *window)
1615 {
1616   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1617
1618   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1619   gtk_widget_destroy (GTK_WIDGET (window));
1620 }
1621
1622 static void
1623 empathy_call_window_fullscreen_cb (gpointer object,
1624                                    EmpathyCallWindow *window)
1625 {
1626   empathy_call_window_fullscreen_toggle (window);
1627 }
1628
1629 static void
1630 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1631 {
1632   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1633
1634   if (priv->is_fullscreen)
1635     gtk_window_unfullscreen (GTK_WINDOW (window));
1636   else
1637     gtk_window_fullscreen (GTK_WINDOW (window));
1638 }
1639
1640 static gboolean
1641 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
1642   GdkEventButton *event, EmpathyCallWindow *window)
1643 {
1644   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1645     {
1646       empathy_call_window_video_menu_popup (window, event->button);
1647       return TRUE;
1648     }
1649
1650   return FALSE;
1651 }
1652
1653 static gboolean
1654 empathy_call_window_key_press_cb (GtkWidget *video_output,
1655   GdkEventKey *event, EmpathyCallWindow *window)
1656 {
1657   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1658
1659   if (priv->is_fullscreen && event->keyval == GDK_Escape)
1660     {
1661       /* Since we are in fullscreen mode, toggling will bring us back to
1662          normal mode. */
1663       empathy_call_window_fullscreen_toggle (window);
1664       return TRUE;
1665     }
1666
1667   return FALSE;
1668 }
1669
1670 static gboolean
1671 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
1672     GdkEventMotion *event, EmpathyCallWindow *window)
1673 {
1674   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1675
1676   if (priv->is_fullscreen)
1677     {
1678       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
1679       return TRUE;
1680     }
1681
1682   return FALSE;
1683 }
1684
1685 static void
1686 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
1687   guint button)
1688 {
1689   GtkWidget *menu;
1690   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1691
1692   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1693             "/video-popup");
1694   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1695       button, gtk_get_current_event_time ());
1696   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
1697 }
1698
1699 static void
1700 empathy_call_window_status_message (EmpathyCallWindow *window,
1701   gchar *message)
1702 {
1703   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1704
1705   if (priv->context_id == 0)
1706     {
1707       priv->context_id = gtk_statusbar_get_context_id (
1708         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
1709     }
1710   else
1711     {
1712       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
1713     }
1714
1715   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
1716     message);
1717 }
1718
1719 static void
1720 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
1721   gdouble value, EmpathyCallWindow *window)
1722 {
1723   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1724
1725   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
1726     value);
1727 }