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