]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
Merge branch 'timestamp-615976'
[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-glib/util.h>
33 #include <telepathy-farsight/channel.h>
34 #include <telepathy-glib/util.h>
35
36 #include <gst/farsight/fs-element-added-notifier.h>
37
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-call-factory.h>
40 #include <libempathy/empathy-utils.h>
41 #include <libempathy-gtk/empathy-avatar-image.h>
42 #include <libempathy-gtk/empathy-video-widget.h>
43 #include <libempathy-gtk/empathy-audio-src.h>
44 #include <libempathy-gtk/empathy-audio-sink.h>
45 #include <libempathy-gtk/empathy-video-src.h>
46 #include <libempathy-gtk/empathy-ui-utils.h>
47 #include <libempathy-gtk/empathy-sound.h>
48 #include <libempathy-gtk/empathy-geometry.h>
49
50 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
51 #include <libempathy/empathy-debug.h>
52
53 #include "empathy-call-window.h"
54 #include "empathy-call-window-fullscreen.h"
55 #include "empathy-sidebar.h"
56
57 #define BUTTON_ID "empathy-call-dtmf-button-id"
58
59 #define CONTENT_HBOX_BORDER_WIDTH 6
60 #define CONTENT_HBOX_SPACING 3
61 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
62
63 #define SELF_VIDEO_SECTION_WIDTH 160
64 #define SELF_VIDEO_SECTION_HEIGTH 120
65
66 /* The avatar's default width and height are set to the same value because we
67    want a square icon. */
68 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
69 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
70   EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
71
72 /* If an video input error occurs, the error message will start with "v4l" */
73 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
74
75 /* The time interval in milliseconds between 2 outgoing rings */
76 #define MS_BETWEEN_RING 500
77
78 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
79
80 /* signal enum */
81 #if 0
82 enum
83 {
84     LAST_SIGNAL
85 };
86
87 static guint signals[LAST_SIGNAL] = {0};
88 #endif
89
90 enum {
91   PROP_CALL_HANDLER = 1,
92 };
93
94 typedef enum {
95   CONNECTING,
96   CONNECTED,
97   DISCONNECTED,
98   REDIALING
99 } CallState;
100
101 typedef enum {
102   CAMERA_STATE_OFF = 0,
103   CAMERA_STATE_PREVIEW,
104   CAMERA_STATE_ON,
105 } CameraState;
106
107 /* private structure */
108 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
109
110 struct _EmpathyCallWindowPriv
111 {
112   gboolean dispose_has_run;
113   EmpathyCallHandler *handler;
114   EmpathyContact *contact;
115
116   guint call_state;
117   gboolean outgoing;
118
119   GtkUIManager *ui_manager;
120   GtkWidget *errors_vbox;
121   /* widget displays the video received from the remote user. This widget is
122    * alive only during call. */
123   GtkWidget *video_output;
124   GtkWidget *video_preview;
125   GtkWidget *remote_user_avatar_widget;
126   GtkWidget *self_user_avatar_widget;
127   GtkWidget *sidebar;
128   GtkWidget *sidebar_button;
129   GtkWidget *statusbar;
130   GtkWidget *volume_button;
131   GtkWidget *redial_button;
132   GtkWidget *mic_button;
133   GtkWidget *toolbar;
134   GtkWidget *pane;
135   GtkAction *redial;
136   GtkAction *menu_fullscreen;
137   GtkAction *action_camera;
138   GtkAction *action_camera_preview;
139   GtkWidget *tool_button_camera_off;
140   GtkWidget *tool_button_camera_preview;
141   GtkWidget *tool_button_camera_on;
142
143   /* The frames and boxes that contain self and remote avatar and video
144      input/output. When we redial, we destroy and re-create the boxes */
145   GtkWidget *remote_user_output_frame;
146   GtkWidget *self_user_output_frame;
147   GtkWidget *remote_user_output_hbox;
148   GtkWidget *self_user_output_hbox;
149
150   /* We keep a reference on the hbox which contains the main content so we can
151      easilly repack everything when toggling fullscreen */
152   GtkWidget *content_hbox;
153
154   /* This vbox is contained in the content_hbox and it contains the
155      self_user_output_frame and the sidebar button. When toggling fullscreen,
156      it needs to be repacked. We keep a reference on it for easier access. */
157   GtkWidget *vbox;
158
159   gulong video_output_motion_handler_id;
160   guint bus_message_source_id;
161
162   gdouble volume;
163   GtkWidget *volume_scale;
164   GtkWidget *volume_progress_bar;
165   GtkAdjustment *audio_input_adj;
166
167   GtkWidget *dtmf_panel;
168
169   GstElement *video_input;
170   GstElement *audio_input;
171   GstElement *audio_output;
172   GstElement *pipeline;
173   GstElement *video_tee;
174
175   GstElement *funnel;
176   GstElement *liveadder;
177
178   FsElementAddedNotifier *fsnotifier;
179
180   guint context_id;
181
182   GTimer *timer;
183   guint timer_id;
184
185   GtkWidget *video_contrast;
186   GtkWidget *video_brightness;
187   GtkWidget *video_gamma;
188
189   GMutex *lock;
190   gboolean call_started;
191   gboolean sending_video;
192   CameraState camera_state;
193
194   EmpathyCallWindowFullscreen *fullscreen;
195   gboolean is_fullscreen;
196
197   /* Those fields represent the state of the window before it actually was in
198      fullscreen mode. */
199   gboolean sidebar_was_visible_before_fs;
200   gint original_width_before_fs;
201   gint original_height_before_fs;
202
203   /* TRUE if the call should be started when the pipeline is playing */
204   gboolean start_call_when_playing;
205   /* TRUE if we requested to set the pipeline in the playing state */
206   gboolean pipeline_playing;
207 };
208
209 #define GET_PRIV(o) \
210   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
211     EmpathyCallWindowPriv))
212
213 static void empathy_call_window_realized_cb (GtkWidget *widget,
214   EmpathyCallWindow *window);
215
216 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
217   GdkEvent *event, EmpathyCallWindow *window);
218
219 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
220   GdkEventWindowState *event, EmpathyCallWindow *window);
221
222 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
223   EmpathyCallWindow *window);
224
225 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
226   gboolean send);
227
228 static void empathy_call_window_mic_toggled_cb (
229   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
230
231 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
232   EmpathyCallWindow *window);
233
234 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
235   EmpathyCallWindow *window);
236
237 static void empathy_call_window_hangup_cb (gpointer object,
238   EmpathyCallWindow *window);
239
240 static void empathy_call_window_fullscreen_cb (gpointer object,
241   EmpathyCallWindow *window);
242
243 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
244
245 static gboolean empathy_call_window_video_button_press_cb (
246   GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
247
248 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
249   GdkEventKey *event, EmpathyCallWindow *window);
250
251 static gboolean empathy_call_window_video_output_motion_notify (
252   GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
253
254 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
255   guint button);
256
257 static void empathy_call_window_redial_cb (gpointer object,
258   EmpathyCallWindow *window);
259
260 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
261
262 static void empathy_call_window_status_message (EmpathyCallWindow *window,
263   gchar *message);
264
265 static void empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
266   EmpathyCallWindow *window);
267
268 static gboolean empathy_call_window_bus_message (GstBus *bus,
269   GstMessage *message, gpointer user_data);
270
271 static void
272 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
273   gdouble value, EmpathyCallWindow *window);
274
275 static void block_camera_control_signals (EmpathyCallWindow *self);
276 static void unblock_camera_control_signals (EmpathyCallWindow *self);
277
278 static void
279 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
280 {
281   EmpathyCallWindowPriv *priv = GET_PRIV (self);
282   GtkToolItem *tool_item;
283   GtkWidget *camera_off_icon;
284   GdkPixbuf *pixbuf, *modded_pixbuf;
285
286   /* set the icon of the 'camera off' button by greying off the webcam icon */
287   pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
288       GTK_ICON_SIZE_SMALL_TOOLBAR);
289
290   modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
291       gdk_pixbuf_get_width (pixbuf),
292       gdk_pixbuf_get_height (pixbuf));
293
294   gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
295   g_object_unref (pixbuf);
296
297   camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
298   g_object_unref (modded_pixbuf);
299   gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
300         priv->tool_button_camera_off), camera_off_icon);
301
302   /* Add an empty expanded GtkToolItem so the volume button is at the end of
303    * the toolbar. */
304   tool_item = gtk_tool_item_new ();
305   gtk_tool_item_set_expand (tool_item, TRUE);
306   gtk_widget_show (GTK_WIDGET (tool_item));
307   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
308
309   priv->volume_button = gtk_volume_button_new ();
310   /* FIXME listen to the audiosinks signals and update the button according to
311    * that, for now starting out at 1.0 and assuming only the app changes the
312    * volume will do */
313   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
314   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
315     G_CALLBACK (empathy_call_window_volume_changed_cb), self);
316
317   tool_item = gtk_tool_item_new ();
318   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
319   gtk_widget_show_all (GTK_WIDGET (tool_item));
320   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
321 }
322
323 static void
324 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
325 {
326   EmpathyCallWindowPriv *priv = GET_PRIV (window);
327   EmpathyTpCall *call;
328   GQuark button_quark;
329   TpDTMFEvent event;
330
331   g_object_get (priv->handler, "tp-call", &call, NULL);
332
333   button_quark = g_quark_from_static_string (BUTTON_ID);
334   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
335     button_quark));
336
337   empathy_tp_call_start_tone (call, event);
338
339   g_object_unref (call);
340 }
341
342 static void
343 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
344 {
345   EmpathyCallWindowPriv *priv = GET_PRIV (window);
346   EmpathyTpCall *call;
347
348   g_object_get (priv->handler, "tp-call", &call, NULL);
349
350   empathy_tp_call_stop_tone (call);
351
352   g_object_unref (call);
353 }
354
355 static GtkWidget *
356 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
357 {
358   GtkWidget *table;
359   int i;
360   GQuark button_quark;
361   struct {
362     gchar *label;
363     TpDTMFEvent event;
364   } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
365                       { "2", TP_DTMF_EVENT_DIGIT_2 },
366                       { "3", TP_DTMF_EVENT_DIGIT_3 },
367                       { "4", TP_DTMF_EVENT_DIGIT_4 },
368                       { "5", TP_DTMF_EVENT_DIGIT_5 },
369                       { "6", TP_DTMF_EVENT_DIGIT_6 },
370                       { "7", TP_DTMF_EVENT_DIGIT_7 },
371                       { "8", TP_DTMF_EVENT_DIGIT_8 },
372                       { "9", TP_DTMF_EVENT_DIGIT_9 },
373                       { "#", TP_DTMF_EVENT_HASH },
374                       { "0", TP_DTMF_EVENT_DIGIT_0 },
375                       { "*", TP_DTMF_EVENT_ASTERISK },
376                       { NULL, } };
377
378   button_quark = g_quark_from_static_string (BUTTON_ID);
379
380   table = gtk_table_new (4, 3, TRUE);
381
382   for (i = 0; dtmfbuttons[i].label != NULL; i++)
383     {
384       GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
385       gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
386         i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
387
388       g_object_set_qdata (G_OBJECT (button), button_quark,
389         GUINT_TO_POINTER (dtmfbuttons[i].event));
390
391       g_signal_connect (G_OBJECT (button), "pressed",
392         G_CALLBACK (dtmf_button_pressed_cb), self);
393       g_signal_connect (G_OBJECT (button), "released",
394         G_CALLBACK (dtmf_button_released_cb), self);
395     }
396
397   return table;
398 }
399
400 static GtkWidget *
401 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
402   gchar *label_text, GtkWidget *bin)
403 {
404    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
405    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
406    GtkWidget *label = gtk_label_new (label_text);
407
408    gtk_widget_set_sensitive (scale, FALSE);
409
410    gtk_container_add (GTK_CONTAINER (bin), vbox);
411
412    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
413    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
414    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
415
416    return scale;
417 }
418
419 static void
420 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
421   EmpathyCallWindow *self)
422
423 {
424   EmpathyCallWindowPriv *priv = GET_PRIV (self);
425
426   empathy_video_src_set_channel (priv->video_input,
427     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
428 }
429
430 static void
431 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
432   EmpathyCallWindow *self)
433
434 {
435   EmpathyCallWindowPriv *priv = GET_PRIV (self);
436
437   empathy_video_src_set_channel (priv->video_input,
438     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
439 }
440
441 static void
442 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
443   EmpathyCallWindow *self)
444
445 {
446   EmpathyCallWindowPriv *priv = GET_PRIV (self);
447
448   empathy_video_src_set_channel (priv->video_input,
449     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
450 }
451
452
453 static GtkWidget *
454 empathy_call_window_create_video_input (EmpathyCallWindow *self)
455 {
456   EmpathyCallWindowPriv *priv = GET_PRIV (self);
457   GtkWidget *hbox;
458
459   hbox = gtk_hbox_new (TRUE, 3);
460
461   priv->video_contrast = empathy_call_window_create_video_input_add_slider (
462     self,  _("Contrast"), hbox);
463
464   priv->video_brightness = empathy_call_window_create_video_input_add_slider (
465     self,  _("Brightness"), hbox);
466
467   priv->video_gamma = empathy_call_window_create_video_input_add_slider (
468     self,  _("Gamma"), hbox);
469
470   return hbox;
471 }
472
473 static void
474 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
475 {
476   EmpathyCallWindowPriv *priv = GET_PRIV (self);
477   guint supported;
478   GtkAdjustment *adj;
479
480   supported = empathy_video_src_get_supported_channels (priv->video_input);
481
482   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
483     {
484       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
485
486       gtk_adjustment_set_value (adj,
487         empathy_video_src_get_channel (priv->video_input,
488           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
489
490       g_signal_connect (G_OBJECT (adj), "value-changed",
491         G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
492
493       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
494     }
495
496   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
497     {
498       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
499
500       gtk_adjustment_set_value (adj,
501         empathy_video_src_get_channel (priv->video_input,
502           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
503
504       g_signal_connect (G_OBJECT (adj), "value-changed",
505         G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
506       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
507     }
508
509   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
510     {
511       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
512
513       gtk_adjustment_set_value (adj,
514         empathy_video_src_get_channel (priv->video_input,
515           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
516
517       g_signal_connect (G_OBJECT (adj), "value-changed",
518         G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
519       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
520     }
521 }
522
523 static void
524 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
525   EmpathyCallWindow *self)
526 {
527   EmpathyCallWindowPriv *priv = GET_PRIV (self);
528   gdouble volume;
529
530   volume = gtk_adjustment_get_value (adj)/100.0;
531
532   /* Don't store the volume because of muting */
533   if (volume > 0 || gtk_toggle_tool_button_get_active (
534         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
535     priv->volume = volume;
536
537   /* Ensure that the toggle button is active if the volume is > 0 and inactive
538    * if it's smaller than 0 */
539   if ((volume > 0) != gtk_toggle_tool_button_get_active (
540         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
541     gtk_toggle_tool_button_set_active (
542       GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
543
544   empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
545     volume);
546 }
547
548 static void
549 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
550   gdouble level, EmpathyCallWindow *window)
551 {
552   gdouble value;
553   EmpathyCallWindowPriv *priv = GET_PRIV (window);
554
555   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
556   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
557       value);
558 }
559
560 static GtkWidget *
561 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
562 {
563   EmpathyCallWindowPriv *priv = GET_PRIV (self);
564   GtkWidget *hbox, *vbox, *label;
565
566   hbox = gtk_hbox_new (TRUE, 3);
567
568   vbox = gtk_vbox_new (FALSE, 3);
569   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
570
571   priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
572   gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
573   label = gtk_label_new (_("Volume"));
574
575   priv->audio_input_adj = gtk_range_get_adjustment (
576     GTK_RANGE (priv->volume_scale));
577   priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
578     (priv->audio_input));
579   gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
580
581   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
582     G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
583
584   gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
585   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
586
587   priv->volume_progress_bar = gtk_progress_bar_new ();
588   gtk_progress_bar_set_orientation (
589       GTK_PROGRESS_BAR (priv->volume_progress_bar),
590       GTK_PROGRESS_BOTTOM_TO_TOP);
591   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
592       0);
593
594   gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
595       3);
596
597   return hbox;
598 }
599
600 static void
601 create_video_output_widget (EmpathyCallWindow *self)
602 {
603   EmpathyCallWindowPriv *priv = GET_PRIV (self);
604   GstBus *bus;
605
606   g_assert (priv->video_output == NULL);
607   g_assert (priv->pipeline != NULL);
608
609   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
610   priv->video_output = empathy_video_widget_new (bus);
611
612   gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
613       priv->video_output, TRUE, TRUE, 0);
614
615   gtk_widget_add_events (priv->video_output,
616       GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
617   g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
618       G_CALLBACK (empathy_call_window_video_button_press_cb), self);
619
620   g_object_unref (bus);
621 }
622
623 static void
624 create_audio_output (EmpathyCallWindow *self)
625 {
626   EmpathyCallWindowPriv *priv = GET_PRIV (self);
627
628   g_assert (priv->audio_output == NULL);
629   priv->audio_output = empathy_audio_sink_new ();
630   gst_object_ref (priv->audio_output);
631   gst_object_sink (priv->audio_output);
632 }
633
634 static void
635 create_video_input (EmpathyCallWindow *self)
636 {
637   EmpathyCallWindowPriv *priv = GET_PRIV (self);
638
639   g_assert (priv->video_input == NULL);
640   priv->video_input = empathy_video_src_new ();
641   gst_object_ref (priv->video_input);
642   gst_object_sink (priv->video_input);
643 }
644
645 static void
646 create_audio_input (EmpathyCallWindow *self)
647 {
648   EmpathyCallWindowPriv *priv = GET_PRIV (self);
649
650   g_assert (priv->audio_input == NULL);
651   priv->audio_input = empathy_audio_src_new ();
652   gst_object_ref (priv->audio_input);
653   gst_object_sink (priv->audio_input);
654
655   tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
656     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
657     self, 0);
658 }
659
660 static void
661 add_video_preview_to_pipeline (EmpathyCallWindow *self)
662 {
663   EmpathyCallWindowPriv *priv = GET_PRIV (self);
664   GstElement *preview;
665
666   g_assert (priv->video_preview != NULL);
667   g_assert (priv->pipeline != NULL);
668   g_assert (priv->video_input != NULL);
669   g_assert (priv->video_tee != NULL);
670
671   preview = empathy_video_widget_get_element (
672       EMPATHY_VIDEO_WIDGET (priv->video_preview));
673
674   if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
675     {
676       g_warning ("Could not add video input to pipeline");
677       return;
678     }
679
680   if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee))
681     {
682       g_warning ("Could not add video tee to pipeline");
683       return;
684     }
685
686   if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
687     {
688       g_warning ("Could not add video preview to pipeline");
689       return;
690     }
691
692   if (!gst_element_link (priv->video_input, priv->video_tee))
693     {
694       g_warning ("Could not link video input to video tee");
695       return;
696     }
697
698   if (!gst_element_link (priv->video_tee, preview))
699     {
700       g_warning ("Could not link video tee to video preview");
701       return;
702     }
703 }
704
705 static void
706 create_video_preview (EmpathyCallWindow *self)
707 {
708   EmpathyCallWindowPriv *priv = GET_PRIV (self);
709   GstBus *bus;
710
711   g_assert (priv->video_preview == NULL);
712   g_assert (priv->video_tee == NULL);
713
714   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
715
716   priv->video_preview = empathy_video_widget_new_with_size (bus,
717       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
718   g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
719
720   gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
721       priv->video_preview, TRUE, TRUE, 0);
722
723   priv->video_tee = gst_element_factory_make ("tee", NULL);
724   gst_object_ref (priv->video_tee);
725   gst_object_sink (priv->video_tee);
726
727   g_object_unref (bus);
728 }
729
730 static void
731 play_camera (EmpathyCallWindow *window,
732     gboolean play)
733 {
734   EmpathyCallWindowPriv *priv = GET_PRIV (window);
735   GstElement *preview;
736   GstState state;
737
738   if (priv->video_preview == NULL)
739     {
740       create_video_preview (window);
741       add_video_preview_to_pipeline (window);
742     }
743
744   if (play)
745     state = GST_STATE_PLAYING;
746   else
747     state = GST_STATE_NULL;
748
749   preview = empathy_video_widget_get_element (
750       EMPATHY_VIDEO_WIDGET (priv->video_preview));
751
752   gst_element_set_state (preview, state);
753   gst_element_set_state (priv->video_input, state);
754   gst_element_set_state (priv->video_tee, state);
755 }
756
757 static void
758 display_video_preview (EmpathyCallWindow *self,
759     gboolean display)
760 {
761   EmpathyCallWindowPriv *priv = GET_PRIV (self);
762
763   if (display)
764     {
765       /* Display the preview and hide the self avatar */
766       DEBUG ("Show video preview");
767
768       play_camera (self, TRUE);
769       gtk_widget_show (priv->video_preview);
770       gtk_widget_hide (priv->self_user_avatar_widget);
771     }
772   else
773     {
774       /* Display the self avatar and hide the preview */
775       DEBUG ("Show self avatar");
776
777       if (priv->video_preview != NULL)
778         gtk_widget_hide (priv->video_preview);
779       play_camera (self, FALSE);
780       gtk_widget_show (priv->self_user_avatar_widget);
781     }
782 }
783
784 static void
785 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
786 {
787   EmpathyCallWindowPriv *priv = GET_PRIV (window);
788
789   empathy_call_window_status_message (window, _("Connecting…"));
790   priv->call_state = CONNECTING;
791
792   if (priv->outgoing)
793     empathy_sound_start_playing (GTK_WIDGET (window),
794         EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
795 }
796
797 static void
798 disable_camera (EmpathyCallWindow *self)
799 {
800   EmpathyCallWindowPriv *priv = GET_PRIV (self);
801
802   if (priv->camera_state == CAMERA_STATE_OFF)
803     return;
804
805   DEBUG ("Disable camera");
806
807   display_video_preview (self, FALSE);
808
809   if (priv->camera_state == CAMERA_STATE_ON)
810     empathy_call_window_set_send_video (self, FALSE);
811
812   block_camera_control_signals (self);
813   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
814         priv->tool_button_camera_on), FALSE);
815   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
816       priv->tool_button_camera_preview), FALSE);
817
818   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
819       priv->tool_button_camera_off), TRUE);
820   gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
821       CAMERA_STATE_OFF);
822   unblock_camera_control_signals (self);
823
824   priv->camera_state = CAMERA_STATE_OFF;
825 }
826
827 static void
828 tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
829   EmpathyCallWindow *self)
830 {
831   EmpathyCallWindowPriv *priv = GET_PRIV (self);
832
833   if (!gtk_toggle_tool_button_get_active (toggle))
834     {
835       if (priv->camera_state == CAMERA_STATE_OFF)
836         {
837           /* We can't change the state by disabling the button */
838           block_camera_control_signals (self);
839           gtk_toggle_tool_button_set_active (toggle, TRUE);
840           unblock_camera_control_signals (self);
841         }
842
843       return;
844     }
845
846   disable_camera (self);
847 }
848
849 static void
850 enable_preview (EmpathyCallWindow *self)
851 {
852   EmpathyCallWindowPriv *priv = GET_PRIV (self);
853
854   if (priv->camera_state == CAMERA_STATE_PREVIEW)
855     return;
856
857   DEBUG ("Enable preview");
858
859   if (priv->camera_state == CAMERA_STATE_ON)
860     /* preview is already displayed so we just have to stop sending */
861     empathy_call_window_set_send_video (self, FALSE);
862
863   display_video_preview (self, TRUE);
864
865   block_camera_control_signals (self);
866   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
867       priv->tool_button_camera_off), FALSE);
868   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
869         priv->tool_button_camera_on), FALSE);
870
871   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
872         priv->tool_button_camera_preview), TRUE);
873   gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
874       CAMERA_STATE_PREVIEW);
875   unblock_camera_control_signals (self);
876
877   priv->camera_state = CAMERA_STATE_PREVIEW;
878 }
879
880 static void
881 tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
882   EmpathyCallWindow *self)
883 {
884   EmpathyCallWindowPriv *priv = GET_PRIV (self);
885
886   if (!gtk_toggle_tool_button_get_active (toggle))
887     {
888       if (priv->camera_state == CAMERA_STATE_PREVIEW)
889         {
890           /* We can't change the state by disabling the button */
891           block_camera_control_signals (self);
892           gtk_toggle_tool_button_set_active (toggle, TRUE);
893           unblock_camera_control_signals (self);
894         }
895
896       return;
897     }
898
899   enable_preview (self);
900 }
901
902 static void
903 enable_camera (EmpathyCallWindow *self)
904 {
905   EmpathyCallWindowPriv *priv = GET_PRIV (self);
906
907   if (priv->camera_state == CAMERA_STATE_ON)
908     return;
909
910   DEBUG ("Enable camera");
911
912   empathy_call_window_set_send_video (self, TRUE);
913
914   block_camera_control_signals (self);
915   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
916       priv->tool_button_camera_off), FALSE);
917   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
918         priv->tool_button_camera_preview), FALSE);
919
920   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
921       priv->tool_button_camera_on), TRUE);
922   gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
923       CAMERA_STATE_ON);
924   unblock_camera_control_signals (self);
925
926   priv->camera_state = CAMERA_STATE_ON;
927 }
928
929 static void
930 tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
931   EmpathyCallWindow *self)
932 {
933   EmpathyCallWindowPriv *priv = GET_PRIV (self);
934
935   if (!gtk_toggle_tool_button_get_active (toggle))
936     {
937       if (priv->camera_state == CAMERA_STATE_ON)
938         {
939           /* We can't change the state by disabling the button */
940           block_camera_control_signals (self);
941           gtk_toggle_tool_button_set_active (toggle, TRUE);
942           unblock_camera_control_signals (self);
943         }
944
945       return;
946     }
947
948   enable_camera (self);
949 }
950
951 static void
952 action_camera_change_cb (GtkRadioAction *action,
953     GtkRadioAction *current,
954     EmpathyCallWindow *self)
955 {
956   CameraState state;
957
958   state = gtk_radio_action_get_current_value (current);
959
960   switch (state)
961     {
962       case CAMERA_STATE_OFF:
963         disable_camera (self);
964         break;
965
966       case CAMERA_STATE_PREVIEW:
967         enable_preview (self);
968         break;
969
970       case CAMERA_STATE_ON:
971         enable_camera (self);
972         break;
973
974       default:
975         g_assert_not_reached ();
976     }
977 }
978
979 static void
980 create_pipeline (EmpathyCallWindow *self)
981 {
982   EmpathyCallWindowPriv *priv = GET_PRIV (self);
983   GstBus *bus;
984
985   g_assert (priv->pipeline == NULL);
986
987   priv->pipeline = gst_pipeline_new (NULL);
988   priv->pipeline_playing = FALSE;
989
990   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
991   priv->bus_message_source_id = gst_bus_add_watch (bus,
992       empathy_call_window_bus_message, self);
993
994   g_object_unref (bus);
995 }
996
997
998 static void
999 empathy_call_window_init (EmpathyCallWindow *self)
1000 {
1001   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1002   GtkBuilder *gui;
1003   GtkWidget *top_vbox;
1004   GtkWidget *h;
1005   GtkWidget *arrow;
1006   GtkWidget *page;
1007   gchar *filename;
1008   GKeyFile *keyfile;
1009   GError *error = NULL;
1010
1011   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
1012   gui = empathy_builder_get_file (filename,
1013     "call_window_vbox", &top_vbox,
1014     "errors_vbox", &priv->errors_vbox,
1015     "pane", &priv->pane,
1016     "statusbar", &priv->statusbar,
1017     "redial", &priv->redial_button,
1018     "microphone", &priv->mic_button,
1019     "toolbar", &priv->toolbar,
1020     "menuredial", &priv->redial,
1021     "ui_manager", &priv->ui_manager,
1022     "menufullscreen", &priv->menu_fullscreen,
1023     "camera_off", &priv->tool_button_camera_off,
1024     "camera_preview", &priv->tool_button_camera_preview,
1025     "camera_on", &priv->tool_button_camera_on,
1026     "action_camera_off",  &priv->action_camera,
1027     "action_camera_preview",  &priv->action_camera_preview,
1028     NULL);
1029   g_free (filename);
1030
1031   empathy_builder_connect (gui, self,
1032     "menuhangup", "activate", empathy_call_window_hangup_cb,
1033     "hangup", "clicked", empathy_call_window_hangup_cb,
1034     "menuredial", "activate", empathy_call_window_redial_cb,
1035     "redial", "clicked", empathy_call_window_redial_cb,
1036     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
1037     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
1038     "camera_off", "toggled", tool_button_camera_off_toggled_cb,
1039     "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
1040     "camera_on", "toggled", tool_button_camera_on_toggled_cb,
1041     "action_camera_off", "changed", action_camera_change_cb,
1042     NULL);
1043
1044   priv->lock = g_mutex_new ();
1045
1046   gtk_container_add (GTK_CONTAINER (self), top_vbox);
1047
1048   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
1049   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1050                                   CONTENT_HBOX_BORDER_WIDTH);
1051   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
1052
1053   /* remote user output frame */
1054   priv->remote_user_output_frame = gtk_frame_new (NULL);
1055   gtk_widget_set_size_request (priv->remote_user_output_frame,
1056       EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
1057   gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1058       priv->remote_user_output_frame, TRUE, TRUE,
1059       CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1060
1061   priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
1062
1063   priv->remote_user_avatar_widget = gtk_image_new ();
1064
1065   gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
1066       priv->remote_user_avatar_widget, TRUE, TRUE, 0);
1067
1068   gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
1069       priv->remote_user_output_hbox);
1070
1071   /* self user output frame */
1072   priv->self_user_output_frame = gtk_frame_new (NULL);
1073   gtk_widget_set_size_request (priv->self_user_output_frame,
1074       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
1075
1076   priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
1077
1078   priv->self_user_avatar_widget = gtk_image_new ();
1079   gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
1080       priv->self_user_avatar_widget, TRUE, TRUE, 0);
1081
1082   gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
1083       priv->self_user_output_hbox);
1084
1085   create_pipeline (self);
1086   create_video_output_widget (self);
1087   create_audio_input (self);
1088   create_audio_output (self);
1089   create_video_input (self);
1090
1091   priv->fsnotifier = fs_element_added_notifier_new ();
1092   fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
1093
1094   /* The call will be started as soon the pipeline is playing */
1095   priv->start_call_when_playing = TRUE;
1096
1097   keyfile = g_key_file_new ();
1098   filename = empathy_file_lookup ("element-properties", "data");
1099   if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
1100     {
1101       fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
1102           keyfile);
1103     }
1104   else
1105     {
1106       g_warning ("Could not load element-properties file: %s", error->message);
1107       g_key_file_free (keyfile);
1108       g_clear_error (&error);
1109     }
1110   g_free (filename);
1111
1112   priv->vbox = gtk_vbox_new (FALSE, 3);
1113   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
1114       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1115   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
1116       FALSE, FALSE, 0);
1117
1118   empathy_call_window_setup_toolbar (self);
1119
1120   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
1121   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1122   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
1123     G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
1124
1125   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1126
1127   h = gtk_hbox_new (FALSE, 3);
1128   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
1129   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
1130
1131   priv->sidebar = empathy_sidebar_new ();
1132   g_signal_connect (G_OBJECT (priv->sidebar),
1133     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
1134   g_signal_connect (G_OBJECT (priv->sidebar),
1135     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
1136   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1137
1138   page = empathy_call_window_create_audio_input (self);
1139   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
1140     page);
1141
1142   page = empathy_call_window_create_video_input (self);
1143   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
1144     page);
1145
1146   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
1147   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
1148     priv->dtmf_panel);
1149
1150   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1151
1152
1153   gtk_widget_show_all (top_vbox);
1154
1155   gtk_widget_hide (priv->sidebar);
1156
1157   priv->fullscreen = empathy_call_window_fullscreen_new (self);
1158   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1159       priv->video_output);
1160   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1161       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1162
1163   g_signal_connect (G_OBJECT (self), "realize",
1164     G_CALLBACK (empathy_call_window_realized_cb), self);
1165
1166   g_signal_connect (G_OBJECT (self), "delete-event",
1167     G_CALLBACK (empathy_call_window_delete_cb), self);
1168
1169   g_signal_connect (G_OBJECT (self), "window-state-event",
1170     G_CALLBACK (empathy_call_window_state_event_cb), self);
1171
1172   g_signal_connect (G_OBJECT (self), "key-press-event",
1173       G_CALLBACK (empathy_call_window_key_press_cb), self);
1174
1175   priv->timer = g_timer_new ();
1176
1177   g_object_ref (priv->ui_manager);
1178   g_object_unref (gui);
1179
1180   empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1181 }
1182
1183 /* Instead of specifying a width and a height, we specify only one size. That's
1184    because we want a square avatar icon.  */
1185 static void
1186 init_contact_avatar_with_size (EmpathyContact *contact,
1187     GtkWidget *image_widget,
1188     gint size)
1189 {
1190   GdkPixbuf *pixbuf_avatar = NULL;
1191
1192   if (contact != NULL)
1193     {
1194       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1195         size, size);
1196     }
1197
1198   if (pixbuf_avatar == NULL)
1199     {
1200       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1201           size);
1202     }
1203
1204   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1205
1206   if (pixbuf_avatar != NULL)
1207     g_object_unref (pixbuf_avatar);
1208 }
1209
1210 static void
1211 set_window_title (EmpathyCallWindow *self)
1212 {
1213   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1214   gchar *tmp;
1215
1216   /* translators: Call is a noun and %s is the contact name. This string
1217    * is used in the window title */
1218   tmp = g_strdup_printf (_("Call with %s"),
1219       empathy_contact_get_name (priv->contact));
1220   gtk_window_set_title (GTK_WINDOW (self), tmp);
1221   g_free (tmp);
1222 }
1223
1224 static void
1225 contact_name_changed_cb (EmpathyContact *contact,
1226     GParamSpec *pspec, EmpathyCallWindow *self)
1227 {
1228   set_window_title (self);
1229 }
1230
1231 static void
1232 contact_avatar_changed_cb (EmpathyContact *contact,
1233     GParamSpec *pspec, GtkWidget *avatar_widget)
1234 {
1235   int size;
1236
1237   size = avatar_widget->allocation.height;
1238
1239   if (size == 0)
1240     {
1241       /* the widget is not allocated yet, set a default size */
1242       size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1243           REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1244     }
1245
1246   init_contact_avatar_with_size (contact, avatar_widget, size);
1247 }
1248
1249 static void
1250 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1251     EmpathyContact *contact, const GError *error, gpointer user_data,
1252     GObject *weak_object)
1253 {
1254   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1255   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1256
1257   init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1258       MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1259
1260   g_signal_connect (contact, "notify::avatar",
1261       G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1262 }
1263
1264 static void
1265 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1266     EmpathyCallHandler *handler)
1267 {
1268   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1269
1270   g_object_get (handler, "contact", &(priv->contact), NULL);
1271
1272   if (priv->contact != NULL)
1273     {
1274       TpConnection *connection;
1275       EmpathyTpContactFactory *factory;
1276
1277       set_window_title (self);
1278
1279       g_signal_connect (priv->contact, "notify::name",
1280           G_CALLBACK (contact_name_changed_cb), self);
1281       g_signal_connect (priv->contact, "notify::avatar",
1282           G_CALLBACK (contact_avatar_changed_cb),
1283           priv->remote_user_avatar_widget);
1284
1285       /* Retreiving the self avatar */
1286       connection = empathy_contact_get_connection (priv->contact);
1287       factory = empathy_tp_contact_factory_dup_singleton (connection);
1288       empathy_tp_contact_factory_get_from_handle (factory,
1289           tp_connection_get_self_handle (connection),
1290           empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1291
1292       g_object_unref (factory);
1293     }
1294   else
1295     {
1296       g_warning ("call handler doesn't have a contact");
1297       /* translators: Call is a noun. This string is used in the window
1298        * title */
1299       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1300
1301       /* Since we can't access the remote contact, we can't get a connection
1302          to it and can't get the self contact (and its avatar). This means
1303          that we have to manually set the self avatar. */
1304       init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1305           MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1306     }
1307
1308   init_contact_avatar_with_size (priv->contact,
1309       priv->remote_user_avatar_widget,
1310       MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1311           REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1312
1313   /* The remote avatar is shown by default and will be hidden when we receive
1314      video from the remote side. */
1315   gtk_widget_hide (priv->video_output);
1316   gtk_widget_show (priv->remote_user_avatar_widget);
1317 }
1318
1319 static void
1320 empathy_call_window_constructed (GObject *object)
1321 {
1322   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1323   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1324   EmpathyTpCall *call;
1325
1326   g_assert (priv->handler != NULL);
1327
1328   g_object_get (priv->handler, "tp-call", &call, NULL);
1329   priv->outgoing = (call == NULL);
1330   if (call != NULL)
1331     g_object_unref (call);
1332
1333   empathy_call_window_setup_avatars (self, priv->handler);
1334   empathy_call_window_set_state_connecting (self);
1335
1336   if (!empathy_call_handler_has_initial_video (priv->handler))
1337     {
1338       gtk_toggle_tool_button_set_active (
1339           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1340     }
1341   /* If call has InitialVideo, the preview will be started once the call has
1342    * been started (start_call()). */
1343 }
1344
1345 static void empathy_call_window_dispose (GObject *object);
1346 static void empathy_call_window_finalize (GObject *object);
1347
1348 static void
1349 empathy_call_window_set_property (GObject *object,
1350   guint property_id, const GValue *value, GParamSpec *pspec)
1351 {
1352   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1353
1354   switch (property_id)
1355     {
1356       case PROP_CALL_HANDLER:
1357         priv->handler = g_value_dup_object (value);
1358         break;
1359       default:
1360         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1361     }
1362 }
1363
1364 static void
1365 empathy_call_window_get_property (GObject *object,
1366   guint property_id, GValue *value, GParamSpec *pspec)
1367 {
1368   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1369
1370   switch (property_id)
1371     {
1372       case PROP_CALL_HANDLER:
1373         g_value_set_object (value, priv->handler);
1374         break;
1375       default:
1376         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1377     }
1378 }
1379
1380 static void
1381 empathy_call_window_class_init (
1382   EmpathyCallWindowClass *empathy_call_window_class)
1383 {
1384   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1385   GParamSpec *param_spec;
1386
1387   g_type_class_add_private (empathy_call_window_class,
1388     sizeof (EmpathyCallWindowPriv));
1389
1390   object_class->constructed = empathy_call_window_constructed;
1391   object_class->set_property = empathy_call_window_set_property;
1392   object_class->get_property = empathy_call_window_get_property;
1393
1394   object_class->dispose = empathy_call_window_dispose;
1395   object_class->finalize = empathy_call_window_finalize;
1396
1397   param_spec = g_param_spec_object ("handler",
1398     "handler", "The call handler",
1399     EMPATHY_TYPE_CALL_HANDLER,
1400     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1401   g_object_class_install_property (object_class,
1402     PROP_CALL_HANDLER, param_spec);
1403 }
1404
1405 static void
1406 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1407     GParamSpec *property, EmpathyCallWindow *self)
1408 {
1409   DEBUG ("video stream changed");
1410   empathy_call_window_update_avatars_visibility (call, self);
1411 }
1412
1413 void
1414 empathy_call_window_dispose (GObject *object)
1415 {
1416   EmpathyTpCall *call;
1417   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1418   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1419
1420   if (priv->dispose_has_run)
1421     return;
1422
1423   priv->dispose_has_run = TRUE;
1424
1425   g_object_get (priv->handler, "tp-call", &call, NULL);
1426
1427   if (call != NULL)
1428     {
1429       g_object_unref (call);
1430     }
1431
1432   if (priv->handler != NULL)
1433     {
1434       empathy_call_handler_stop_call (priv->handler);
1435       g_object_unref (priv->handler);
1436     }
1437   priv->handler = NULL;
1438
1439   if (priv->bus_message_source_id != 0)
1440     {
1441       g_source_remove (priv->bus_message_source_id);
1442       priv->bus_message_source_id = 0;
1443     }
1444
1445   if (priv->pipeline != NULL)
1446     g_object_unref (priv->pipeline);
1447   priv->pipeline = NULL;
1448
1449   if (priv->video_input != NULL)
1450     g_object_unref (priv->video_input);
1451   priv->video_input = NULL;
1452
1453   if (priv->audio_input != NULL)
1454     g_object_unref (priv->audio_input);
1455   priv->audio_input = NULL;
1456
1457   if (priv->audio_output != NULL)
1458     g_object_unref (priv->audio_output);
1459   priv->audio_output = NULL;
1460
1461   if (priv->video_tee != NULL)
1462     g_object_unref (priv->video_tee);
1463   priv->video_tee = NULL;
1464
1465   if (priv->liveadder != NULL)
1466     gst_object_unref (priv->liveadder);
1467   priv->liveadder = NULL;
1468
1469   if (priv->fsnotifier != NULL)
1470     g_object_unref (priv->fsnotifier);
1471   priv->fsnotifier = NULL;
1472
1473   if (priv->timer_id != 0)
1474     g_source_remove (priv->timer_id);
1475   priv->timer_id = 0;
1476
1477   if (priv->ui_manager != NULL)
1478     g_object_unref (priv->ui_manager);
1479   priv->ui_manager = NULL;
1480
1481   if (priv->fullscreen != NULL)
1482     g_object_unref (priv->fullscreen);
1483   priv->fullscreen = NULL;
1484
1485   if (priv->contact != NULL)
1486     {
1487       g_signal_handlers_disconnect_by_func (priv->contact,
1488           contact_name_changed_cb, self);
1489       g_object_unref (priv->contact);
1490       priv->contact = NULL;
1491     }
1492
1493   /* release any references held by the object here */
1494   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1495     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1496 }
1497
1498 void
1499 empathy_call_window_finalize (GObject *object)
1500 {
1501   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1502   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1503
1504   if (priv->video_output_motion_handler_id != 0)
1505     {
1506       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1507           priv->video_output_motion_handler_id);
1508       priv->video_output_motion_handler_id = 0;
1509     }
1510
1511   /* free any data held directly by the object here */
1512   g_mutex_free (priv->lock);
1513
1514   g_timer_destroy (priv->timer);
1515
1516   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1517 }
1518
1519
1520 EmpathyCallWindow *
1521 empathy_call_window_new (EmpathyCallHandler *handler)
1522 {
1523   return EMPATHY_CALL_WINDOW (
1524     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1525 }
1526
1527 static void
1528 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1529   GstElement *conference, gpointer user_data)
1530 {
1531   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1532   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1533
1534   gst_bin_add (GST_BIN (priv->pipeline), conference);
1535
1536   gst_element_set_state (conference, GST_STATE_PLAYING);
1537 }
1538
1539 static gboolean
1540 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1541   FsMediaType type, FsStreamDirection direction, gpointer user_data)
1542 {
1543   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1544   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1545
1546   if (type != FS_MEDIA_TYPE_VIDEO)
1547     return TRUE;
1548
1549   if (direction == FS_DIRECTION_RECV)
1550     return TRUE;
1551
1552   /* video and direction is send */
1553   return priv->video_input != NULL;
1554 }
1555
1556 static gboolean
1557 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1558 {
1559   GstStateChangeReturn state_change_return;
1560   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1561
1562   if (priv->pipeline == NULL)
1563     return TRUE;
1564
1565   if (priv->bus_message_source_id != 0)
1566     {
1567       g_source_remove (priv->bus_message_source_id);
1568       priv->bus_message_source_id = 0;
1569     }
1570
1571   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1572
1573   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1574         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1575     {
1576       if (priv->pipeline != NULL)
1577         g_object_unref (priv->pipeline);
1578       priv->pipeline = NULL;
1579
1580       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1581           empathy_call_window_mic_volume_changed_cb, self);
1582
1583       if (priv->video_tee != NULL)
1584         g_object_unref (priv->video_tee);
1585       priv->video_tee = NULL;
1586
1587       if (priv->video_preview != NULL)
1588         gtk_widget_destroy (priv->video_preview);
1589       priv->video_preview = NULL;
1590
1591       priv->liveadder = NULL;
1592       priv->funnel = NULL;
1593
1594       create_pipeline (self);
1595       /* Call will be started when user will hit the 'redial' button */
1596       priv->start_call_when_playing = FALSE;
1597       gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1598
1599       return TRUE;
1600     }
1601   else
1602     {
1603       g_message ("Error: could not destroy pipeline. Closing call window");
1604       gtk_widget_destroy (GTK_WIDGET (self));
1605
1606       return FALSE;
1607     }
1608 }
1609
1610 static gboolean
1611 empathy_call_window_disconnected (EmpathyCallWindow *self,
1612     gboolean restart)
1613 {
1614   gboolean could_disconnect = FALSE;
1615   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1616   gboolean could_reset_pipeline;
1617
1618   could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1619
1620   if (priv->call_state == CONNECTING)
1621       empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1622
1623   if (priv->call_state != REDIALING)
1624     priv->call_state = DISCONNECTED;
1625
1626   if (could_reset_pipeline)
1627     {
1628       g_mutex_lock (priv->lock);
1629
1630       g_timer_stop (priv->timer);
1631
1632       if (priv->timer_id != 0)
1633         g_source_remove (priv->timer_id);
1634       priv->timer_id = 0;
1635
1636       g_mutex_unlock (priv->lock);
1637
1638       if (!restart)
1639         /* We are about to destroy the window, no need to update it or create
1640          * a video preview */
1641         return TRUE;
1642
1643       empathy_call_window_status_message (self, _("Disconnected"));
1644
1645       gtk_action_set_sensitive (priv->redial, TRUE);
1646       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1647
1648       /* Unsensitive the camera and mic button */
1649       gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1650       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1651
1652       /* Be sure that the mic button is enabled */
1653       gtk_toggle_tool_button_set_active (
1654           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1655
1656       if (priv->camera_state == CAMERA_STATE_ON)
1657         {
1658           /* Enable the 'preview' button as we are not sending atm. */
1659           gtk_toggle_tool_button_set_active (
1660               GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_preview), TRUE);
1661         }
1662       else if (priv->camera_state == CAMERA_STATE_PREVIEW)
1663         {
1664           /* Restart the preview with the new pipeline. */
1665           display_video_preview (self, TRUE);
1666         }
1667
1668       gtk_progress_bar_set_fraction (
1669           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1670
1671       /* destroy the video output; it will be recreated when we'll redial */
1672       gtk_widget_destroy (priv->video_output);
1673       priv->video_output = NULL;
1674
1675       gtk_widget_show (priv->remote_user_avatar_widget);
1676
1677       priv->sending_video = FALSE;
1678       priv->call_started = FALSE;
1679
1680       could_disconnect = TRUE;
1681
1682       /* TODO: display the self avatar of the preview (depends if the "Always
1683        * Show Video Preview" is enabled or not) */
1684     }
1685
1686   return could_disconnect;
1687 }
1688
1689
1690 static void
1691 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1692     gpointer user_data)
1693 {
1694   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1695   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1696
1697   if (empathy_call_window_disconnected (self, TRUE) &&
1698       priv->call_state == REDIALING)
1699       empathy_call_window_restart_call (self);
1700 }
1701
1702
1703 static void
1704 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1705     TfStream *stream, gpointer user_data)
1706 {
1707   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1708   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1709   guint media_type;
1710
1711   g_object_get (stream, "media-type", &media_type, NULL);
1712
1713   /*
1714    * This assumes that there is only one video stream per channel...
1715    */
1716
1717   if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1718     {
1719       if (priv->funnel != NULL)
1720         {
1721           GstElement *output;
1722
1723           output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1724               (priv->video_output));
1725
1726           gst_element_set_state (output, GST_STATE_NULL);
1727           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1728
1729           gst_bin_remove (GST_BIN (priv->pipeline), output);
1730           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1731           priv->funnel = NULL;
1732         }
1733     }
1734   else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1735     {
1736       if (priv->liveadder != NULL)
1737         {
1738           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1739           gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1740
1741           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1742           gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1743           priv->liveadder = NULL;
1744         }
1745     }
1746 }
1747
1748 /* Called with global lock held */
1749 static GstPad *
1750 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1751 {
1752   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1753   GstPad *pad;
1754   GstElement *output;
1755
1756   if (priv->funnel == NULL)
1757     {
1758       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1759         (priv->video_output));
1760
1761       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1762
1763       if (!priv->funnel)
1764         {
1765           g_warning ("Could not create fsfunnel");
1766           return NULL;
1767         }
1768
1769       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
1770         {
1771           gst_object_unref (priv->funnel);
1772           priv->funnel = NULL;
1773           g_warning ("Could  not add funnel to pipeline");
1774           return NULL;
1775         }
1776
1777       if (!gst_bin_add (GST_BIN (priv->pipeline), output))
1778         {
1779           g_warning ("Could not add the video output widget to the pipeline");
1780           goto error;
1781         }
1782
1783       if (!gst_element_link (priv->funnel, output))
1784         {
1785           g_warning ("Could not link output sink to funnel");
1786           goto error_output_added;
1787         }
1788
1789       if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1790         {
1791           g_warning ("Could not start video sink");
1792           goto error_output_added;
1793         }
1794
1795       if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1796         {
1797           g_warning ("Could not start funnel");
1798           goto error_output_added;
1799         }
1800     }
1801
1802   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1803
1804   if (!pad)
1805     g_warning ("Could not get request pad from funnel");
1806
1807   return pad;
1808
1809
1810  error_output_added:
1811
1812   gst_element_set_locked_state (priv->funnel, TRUE);
1813   gst_element_set_locked_state (output, TRUE);
1814
1815   gst_element_set_state (priv->funnel, GST_STATE_NULL);
1816   gst_element_set_state (output, GST_STATE_NULL);
1817
1818   gst_bin_remove (GST_BIN (priv->pipeline), output);
1819   gst_element_set_locked_state (output, FALSE);
1820
1821  error:
1822
1823   gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1824   priv->funnel = NULL;
1825
1826   return NULL;
1827 }
1828
1829 /* Called with global lock held */
1830 static GstPad *
1831 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1832 {
1833   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1834   GstPad *pad;
1835   GstElement *filter;
1836   GError *gerror = NULL;
1837
1838   if (priv->liveadder == NULL)
1839     {
1840       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1841
1842       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
1843         {
1844           g_warning ("Could not add liveadder to the pipeline");
1845           goto error_add_liveadder;
1846         }
1847       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
1848         {
1849           g_warning ("Could not add audio sink to pipeline");
1850           goto error_add_output;
1851         }
1852
1853       if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1854         {
1855           g_warning ("Could not start liveadder");
1856           goto error;
1857         }
1858
1859       if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1860         {
1861           g_warning ("Could not start audio sink");
1862           goto error;
1863         }
1864
1865       if (GST_PAD_LINK_FAILED (
1866               gst_element_link (priv->liveadder, priv->audio_output)))
1867         {
1868           g_warning ("Could not link liveadder to audio output");
1869           goto error;
1870         }
1871     }
1872
1873   filter = gst_parse_bin_from_description (
1874       "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
1875   if (filter == NULL)
1876     {
1877       g_warning ("Could not make audio conversion filter: %s", gerror->message);
1878       g_clear_error (&gerror);
1879       goto error;
1880     }
1881
1882   if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
1883     {
1884       g_warning ("Could not add audio conversion filter to pipeline");
1885       gst_object_unref (filter);
1886       goto error;
1887     }
1888
1889   if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1890     {
1891       g_warning ("Could not start audio conversion filter");
1892       goto error_filter;
1893     }
1894
1895   if (!gst_element_link (filter, priv->liveadder))
1896     {
1897       g_warning ("Could not link audio conversion filter to liveadder");
1898       goto error_filter;
1899     }
1900
1901   pad = gst_element_get_static_pad (filter, "sink");
1902
1903   if (pad == NULL)
1904     {
1905       g_warning ("Could not get sink pad from filter");
1906       goto error_filter;
1907     }
1908
1909   return pad;
1910
1911  error_filter:
1912
1913   gst_element_set_locked_state (filter, TRUE);
1914   gst_element_set_state (filter, GST_STATE_NULL);
1915   gst_bin_remove (GST_BIN (priv->pipeline), filter);
1916
1917  error:
1918
1919   gst_element_set_locked_state (priv->liveadder, TRUE);
1920   gst_element_set_locked_state (priv->audio_output, TRUE);
1921
1922   gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1923   gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1924
1925   gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1926
1927  error_add_output:
1928
1929   gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1930
1931   gst_element_set_locked_state (priv->liveadder, FALSE);
1932   gst_element_set_locked_state (priv->audio_output, FALSE);
1933
1934  error_add_liveadder:
1935
1936   if (priv->liveadder != NULL)
1937     {
1938       gst_object_unref (priv->liveadder);
1939       priv->liveadder = NULL;
1940     }
1941
1942   return NULL;
1943 }
1944
1945 static gboolean
1946 empathy_call_window_update_timer (gpointer user_data)
1947 {
1948   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1949   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1950   gchar *str;
1951   gdouble time_;
1952
1953   time_ = g_timer_elapsed (priv->timer, NULL);
1954
1955   /* Translators: number of minutes:seconds the caller has been connected */
1956   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time_ / 60,
1957     (int) time_ % 60);
1958   empathy_call_window_status_message (self, str);
1959   g_free (str);
1960
1961   return TRUE;
1962 }
1963
1964 static void
1965 display_error (EmpathyCallWindow *self,
1966     EmpathyTpCall *call,
1967     const gchar *img,
1968     const gchar *title,
1969     const gchar *desc,
1970     const gchar *details)
1971 {
1972   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1973   GtkWidget *info_bar;
1974   GtkWidget *content_area;
1975   GtkWidget *hbox;
1976   GtkWidget *vbox;
1977   GtkWidget *image;
1978   GtkWidget *label;
1979   gchar *txt;
1980
1981   /* Create info bar */
1982   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1983       NULL);
1984
1985   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1986
1987   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1988
1989   /* hbox containing the image and the messages vbox */
1990   hbox = gtk_hbox_new (FALSE, 3);
1991   gtk_container_add (GTK_CONTAINER (content_area), hbox);
1992
1993   /* Add image */
1994   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1995   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1996
1997   /* vbox containing the main message and the details expander */
1998   vbox = gtk_vbox_new (FALSE, 3);
1999   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2000
2001   /* Add text */
2002   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2003
2004   label = gtk_label_new (NULL);
2005   gtk_label_set_markup (GTK_LABEL (label), txt);
2006   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2007   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2008   g_free (txt);
2009
2010   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2011
2012   /* Add details */
2013   if (details != NULL)
2014     {
2015       GtkWidget *expander;
2016
2017       expander = gtk_expander_new (_("Technical Details"));
2018
2019       txt = g_strdup_printf ("<i>%s</i>", details);
2020
2021       label = gtk_label_new (NULL);
2022       gtk_label_set_markup (GTK_LABEL (label), txt);
2023       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2024       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2025       g_free (txt);
2026
2027       gtk_container_add (GTK_CONTAINER (expander), label);
2028       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2029     }
2030
2031   g_signal_connect (info_bar, "response",
2032       G_CALLBACK (gtk_widget_destroy), NULL);
2033
2034   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2035       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2036   gtk_widget_show_all (info_bar);
2037 }
2038
2039 static gchar *
2040 media_stream_error_to_txt (EmpathyCallWindow *self,
2041     EmpathyTpCall *call,
2042     gboolean audio,
2043     TpMediaStreamError error)
2044 {
2045   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2046   const gchar *cm;
2047   gchar *url;
2048   gchar *result;
2049
2050   switch (error)
2051     {
2052       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2053         if (audio)
2054           return g_strdup_printf (
2055               _("%s's software does not understand any of the audio formats "
2056                 "supported by your computer"),
2057             empathy_contact_get_name (priv->contact));
2058         else
2059           return g_strdup_printf (
2060               _("%s's software does not understand any of the video formats "
2061                 "supported by your computer"),
2062             empathy_contact_get_name (priv->contact));
2063
2064       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2065         return g_strdup_printf (
2066             _("Can't establish a connection to %s. "
2067               "One of you might be on a network that does not allow "
2068               "direct connections."),
2069           empathy_contact_get_name (priv->contact));
2070
2071       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2072           return g_strdup (_("There was a failure on the network"));
2073
2074       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2075         if (audio)
2076           return g_strdup (_("The audio formats necessary for this call "
2077                 "are not installed on your computer"));
2078         else
2079           return g_strdup (_("The video formats necessary for this call "
2080                 "are not installed on your computer"));
2081
2082       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2083         cm = empathy_tp_call_get_connection_manager (call);
2084
2085         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2086             "product=Telepathy&amp;component=%s", cm);
2087
2088         result = g_strdup_printf (
2089             _("Something unexpected happened in a Telepathy component. "
2090               "Please <a href=\"%s\">report this bug</a> and attach "
2091               "logs gathered from the 'Debug' window in the Help menu."), url);
2092
2093         g_free (url);
2094         return result;
2095
2096       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2097         return g_strdup (_("There was a failure in the call engine"));
2098
2099       default:
2100         return NULL;
2101     }
2102 }
2103
2104 static void
2105 empathy_call_window_stream_error (EmpathyCallWindow *self,
2106     EmpathyTpCall *call,
2107     gboolean audio,
2108     guint code,
2109     const gchar *msg,
2110     const gchar *icon,
2111     const gchar *title)
2112 {
2113   gchar *desc;
2114
2115   desc = media_stream_error_to_txt (self, call, audio, code);
2116   if (desc == NULL)
2117     {
2118       /* No description, use the error message. That's not great as it's not
2119        * localized but it's better than nothing. */
2120       display_error (self, call, icon, title, msg, NULL);
2121     }
2122   else
2123     {
2124       display_error (self, call, icon, title, desc, msg);
2125       g_free (desc);
2126     }
2127 }
2128
2129 static void
2130 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
2131     guint code,
2132     const gchar *msg,
2133     EmpathyCallWindow *self)
2134 {
2135   empathy_call_window_stream_error (self, call, TRUE, code, msg,
2136       "gnome-stock-mic", _("Can't establish audio stream"));
2137 }
2138
2139 static void
2140 empathy_call_window_video_stream_error (EmpathyTpCall *call,
2141     guint code,
2142     const gchar *msg,
2143     EmpathyCallWindow *self)
2144 {
2145   empathy_call_window_stream_error (self, call, FALSE, code, msg,
2146       "camera-web", _("Can't establish video stream"));
2147 }
2148
2149 static gboolean
2150 empathy_call_window_connected (gpointer user_data)
2151 {
2152   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2153   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2154   EmpathyTpCall *call;
2155   gboolean can_send_video;
2156
2157   empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2158
2159   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
2160     empathy_contact_can_voip_video (priv->contact);
2161
2162   g_object_get (priv->handler, "tp-call", &call, NULL);
2163
2164   tp_g_signal_connect_object (call, "notify::video-stream",
2165     G_CALLBACK (empathy_call_window_video_stream_changed_cb),
2166     self, 0);
2167
2168   if (empathy_tp_call_has_dtmf (call))
2169     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2170
2171   if (priv->video_input == NULL)
2172     empathy_call_window_set_send_video (self, FALSE);
2173
2174   priv->sending_video = can_send_video ?
2175     empathy_tp_call_is_sending_video (call) : FALSE;
2176
2177   gtk_toggle_tool_button_set_active (
2178       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
2179       priv->sending_video && priv->video_input != NULL);
2180   gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
2181
2182   gtk_action_set_sensitive (priv->redial, FALSE);
2183   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2184
2185   gtk_widget_set_sensitive (priv->mic_button, TRUE);
2186
2187   empathy_call_window_update_avatars_visibility (call, self);
2188
2189   g_object_unref (call);
2190
2191   g_mutex_lock (priv->lock);
2192
2193   priv->timer_id = g_timeout_add_seconds (1,
2194     empathy_call_window_update_timer, self);
2195
2196   g_mutex_unlock (priv->lock);
2197
2198   empathy_call_window_update_timer (self);
2199
2200   return FALSE;
2201 }
2202
2203
2204 /* Called from the streaming thread */
2205 static gboolean
2206 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2207   GstPad *src, guint media_type, gpointer user_data)
2208 {
2209   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2210   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2211   gboolean retval = FALSE;
2212
2213   GstPad *pad;
2214
2215   g_mutex_lock (priv->lock);
2216
2217   if (priv->call_state != CONNECTED)
2218     {
2219       g_timer_start (priv->timer);
2220       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
2221       priv->call_state = CONNECTED;
2222     }
2223
2224   switch (media_type)
2225     {
2226       case TP_MEDIA_STREAM_TYPE_AUDIO:
2227         pad = empathy_call_window_get_audio_sink_pad (self);
2228         break;
2229       case TP_MEDIA_STREAM_TYPE_VIDEO:
2230         gtk_widget_hide (priv->remote_user_avatar_widget);
2231         gtk_widget_show (priv->video_output);
2232         pad = empathy_call_window_get_video_sink_pad (self);
2233         break;
2234       default:
2235         g_assert_not_reached ();
2236     }
2237
2238   if (pad == NULL)
2239     goto out;
2240
2241   if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2242       g_warning ("Could not link %s sink pad",
2243           media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2244   else
2245       retval = TRUE;
2246
2247   gst_object_unref (pad);
2248
2249  out:
2250
2251   /* If no sink could be linked, try to add fakesink to prevent the whole call
2252    * aborting */
2253
2254   if (!retval)
2255     {
2256       GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2257
2258       if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2259         {
2260           GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2261           if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2262               GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2263             {
2264               gst_element_set_locked_state (fakesink, TRUE);
2265               gst_element_set_state (fakesink, GST_STATE_NULL);
2266               gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2267             }
2268           else
2269             {
2270               g_debug ("Could not link real sink, linked fakesink instead");
2271             }
2272           gst_object_unref (sinkpad);
2273         }
2274       else
2275         {
2276           gst_object_unref (fakesink);
2277         }
2278     }
2279
2280
2281   g_mutex_unlock (priv->lock);
2282
2283   return TRUE;
2284 }
2285
2286 static gboolean
2287 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2288   GstPad *sink, guint media_type, gpointer user_data)
2289 {
2290   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2291   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2292   GstPad *pad;
2293   gboolean retval = FALSE;
2294
2295   switch (media_type)
2296     {
2297       case TP_MEDIA_STREAM_TYPE_AUDIO:
2298         if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2299           {
2300             g_warning ("Could not add audio source to pipeline");
2301             break;
2302           }
2303
2304         pad = gst_element_get_static_pad (priv->audio_input, "src");
2305         if (!pad)
2306           {
2307             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2308             g_warning ("Could not get source pad from audio source");
2309             break;
2310           }
2311
2312         if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2313           {
2314             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2315             g_warning ("Could not link audio source to farsight");
2316             break;
2317           }
2318
2319         if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2320           {
2321             g_warning ("Could not start audio source");
2322             gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2323             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2324             break;
2325           }
2326
2327         retval = TRUE;
2328         break;
2329       case TP_MEDIA_STREAM_TYPE_VIDEO:
2330         if (priv->video_input != NULL)
2331           {
2332             if (priv->video_tee != NULL)
2333               {
2334                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2335                 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2336                   {
2337                     g_warning ("Could not link videp soure input pipeline");
2338                     break;
2339                   }
2340                 gst_object_unref (pad);
2341               }
2342
2343             retval = TRUE;
2344           }
2345         break;
2346       default:
2347         g_assert_not_reached ();
2348     }
2349
2350   return retval;
2351 }
2352
2353 static void
2354 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2355 {
2356   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2357   GstElement *preview;
2358
2359   DEBUG ("remove video input");
2360   preview = empathy_video_widget_get_element (
2361     EMPATHY_VIDEO_WIDGET (priv->video_preview));
2362
2363   gst_element_set_state (priv->video_input, GST_STATE_NULL);
2364   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2365   gst_element_set_state (preview, GST_STATE_NULL);
2366
2367   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2368     priv->video_tee, preview, NULL);
2369
2370   g_object_unref (priv->video_input);
2371   priv->video_input = NULL;
2372   g_object_unref (priv->video_tee);
2373   priv->video_tee = NULL;
2374   gtk_widget_destroy (priv->video_preview);
2375   priv->video_preview = NULL;
2376
2377   gtk_toggle_tool_button_set_active (
2378       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
2379   gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2380
2381   gtk_widget_show (priv->self_user_avatar_widget);
2382 }
2383
2384 static void
2385 start_call (EmpathyCallWindow *self)
2386 {
2387   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2388
2389   priv->call_started = TRUE;
2390   empathy_call_handler_start_call (priv->handler,
2391       gtk_get_current_event_time ());
2392
2393   if (empathy_call_handler_has_initial_video (priv->handler))
2394     {
2395       /* Enable 'send video' buttons and display the preview */
2396       gtk_toggle_tool_button_set_active (
2397           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
2398     }
2399 }
2400
2401 static gboolean
2402 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2403   gpointer user_data)
2404 {
2405   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2406   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2407   GstState newstate;
2408
2409   empathy_call_handler_bus_message (priv->handler, bus, message);
2410
2411   switch (GST_MESSAGE_TYPE (message))
2412     {
2413       case GST_MESSAGE_STATE_CHANGED:
2414         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2415           {
2416             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2417             if (newstate == GST_STATE_PAUSED)
2418                 empathy_call_window_setup_video_input (self);
2419           }
2420         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2421             !priv->call_started)
2422           {
2423             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2424             if (newstate == GST_STATE_PAUSED)
2425               {
2426                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2427                 priv->pipeline_playing = TRUE;
2428
2429                 if (priv->start_call_when_playing)
2430                   start_call (self);
2431               }
2432           }
2433         break;
2434       case GST_MESSAGE_ERROR:
2435         {
2436           GError *error = NULL;
2437           GstElement *gst_error;
2438           gchar *debug;
2439
2440           gst_message_parse_error (message, &error, &debug);
2441           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2442
2443           g_message ("Element error: %s -- %s\n", error->message, debug);
2444
2445           if (g_str_has_prefix (gst_element_get_name (gst_error),
2446                 VIDEO_INPUT_ERROR_PREFIX))
2447             {
2448               /* Remove the video input and continue */
2449               if (priv->video_input != NULL)
2450                 empathy_call_window_remove_video_input (self);
2451               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2452             }
2453           else
2454             {
2455               empathy_call_window_disconnected (self, TRUE);
2456             }
2457           g_error_free (error);
2458           g_free (debug);
2459         }
2460       default:
2461         break;
2462     }
2463
2464   return TRUE;
2465 }
2466
2467 static void
2468 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2469     EmpathyCallWindow *window)
2470 {
2471   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2472
2473   if (empathy_tp_call_is_receiving_video (call))
2474     {
2475       gtk_widget_hide (priv->remote_user_avatar_widget);
2476       gtk_widget_show (priv->video_output);
2477     }
2478   else
2479     {
2480       gtk_widget_hide (priv->video_output);
2481       gtk_widget_show (priv->remote_user_avatar_widget);
2482     }
2483 }
2484
2485 static void
2486 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2487     GParamSpec *spec,
2488     EmpathyCallWindow *self)
2489 {
2490   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2491   EmpathyTpCall *call;
2492
2493   g_object_get (priv->handler, "tp-call", &call, NULL);
2494   if (call == NULL)
2495     return;
2496
2497   tp_g_signal_connect_object (call, "audio-stream-error",
2498       G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
2499   tp_g_signal_connect_object (call, "video-stream-error",
2500       G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
2501
2502   g_object_unref (call);
2503 }
2504
2505 static void
2506 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2507 {
2508   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2509   EmpathyTpCall *call;
2510
2511   g_signal_connect (priv->handler, "conference-added",
2512     G_CALLBACK (empathy_call_window_conference_added_cb), window);
2513   g_signal_connect (priv->handler, "request-resource",
2514     G_CALLBACK (empathy_call_window_request_resource_cb), window);
2515   g_signal_connect (priv->handler, "closed",
2516     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2517   g_signal_connect (priv->handler, "src-pad-added",
2518     G_CALLBACK (empathy_call_window_src_added_cb), window);
2519   g_signal_connect (priv->handler, "sink-pad-added",
2520     G_CALLBACK (empathy_call_window_sink_added_cb), window);
2521   g_signal_connect (priv->handler, "stream-closed",
2522     G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2523
2524   g_object_get (priv->handler, "tp-call", &call, NULL);
2525   if (call != NULL)
2526     {
2527       tp_g_signal_connect_object (call, "audio-stream-error",
2528         G_CALLBACK (empathy_call_window_audio_stream_error), window,
2529         0);
2530       tp_g_signal_connect_object (call, "video-stream-error",
2531         G_CALLBACK (empathy_call_window_video_stream_error), window,
2532         0);
2533
2534       g_object_unref (call);
2535     }
2536   else
2537     {
2538       /* tp-call doesn't exist yet, we'll connect signals once it has been
2539        * set */
2540       g_signal_connect (priv->handler, "notify::tp-call",
2541         G_CALLBACK (call_handler_notify_tp_call_cb), window);
2542     }
2543
2544   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2545 }
2546
2547 static gboolean
2548 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2549   EmpathyCallWindow *window)
2550 {
2551   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2552
2553   if (priv->pipeline != NULL)
2554     {
2555       if (priv->bus_message_source_id != 0)
2556         {
2557           g_source_remove (priv->bus_message_source_id);
2558           priv->bus_message_source_id = 0;
2559         }
2560
2561       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2562     }
2563
2564   if (priv->call_state == CONNECTING)
2565     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2566
2567   return FALSE;
2568 }
2569
2570 static void
2571 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2572 {
2573   GtkWidget *menu;
2574   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2575
2576   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2577             "/menubar1");
2578
2579   if (set_fullscreen)
2580     {
2581       gtk_widget_hide (priv->sidebar);
2582       gtk_widget_hide (menu);
2583       gtk_widget_hide (priv->vbox);
2584       gtk_widget_hide (priv->statusbar);
2585       gtk_widget_hide (priv->toolbar);
2586     }
2587   else
2588     {
2589       if (priv->sidebar_was_visible_before_fs)
2590         gtk_widget_show (priv->sidebar);
2591
2592       gtk_widget_show (menu);
2593       gtk_widget_show (priv->vbox);
2594       gtk_widget_show (priv->statusbar);
2595       gtk_widget_show (priv->toolbar);
2596
2597       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2598           priv->original_height_before_fs);
2599     }
2600 }
2601
2602 static void
2603 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2604 {
2605   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2606
2607   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2608       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2609   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2610       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2611   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2612       priv->video_output, TRUE, TRUE,
2613       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2614       GTK_PACK_START);
2615   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2616       priv->vbox, TRUE, TRUE,
2617       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2618       GTK_PACK_START);
2619 }
2620
2621 static gboolean
2622 empathy_call_window_state_event_cb (GtkWidget *widget,
2623   GdkEventWindowState *event, EmpathyCallWindow *window)
2624 {
2625   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2626     {
2627       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2628       gboolean set_fullscreen = event->new_window_state &
2629         GDK_WINDOW_STATE_FULLSCREEN;
2630
2631       if (set_fullscreen)
2632         {
2633           gboolean sidebar_was_visible;
2634           GtkAllocation allocation;
2635           gint original_width, original_height;
2636
2637           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2638           original_width = allocation.width;
2639           original_height = allocation.height;
2640
2641           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2642
2643           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2644           priv->original_width_before_fs = original_width;
2645           priv->original_height_before_fs = original_height;
2646
2647           if (priv->video_output_motion_handler_id == 0 &&
2648                 priv->video_output != NULL)
2649             {
2650               priv->video_output_motion_handler_id = g_signal_connect (
2651                   G_OBJECT (priv->video_output), "motion-notify-event",
2652                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2653                   window);
2654             }
2655         }
2656       else
2657         {
2658           if (priv->video_output_motion_handler_id != 0)
2659             {
2660               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2661                   priv->video_output_motion_handler_id);
2662               priv->video_output_motion_handler_id = 0;
2663             }
2664         }
2665
2666       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2667           set_fullscreen);
2668       show_controls (window, set_fullscreen);
2669       show_borders (window, set_fullscreen);
2670       gtk_action_set_stock_id (priv->menu_fullscreen,
2671           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2672       priv->is_fullscreen = set_fullscreen;
2673   }
2674
2675   return FALSE;
2676 }
2677
2678 static void
2679 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2680   EmpathyCallWindow *window)
2681 {
2682   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2683   GtkWidget *arrow;
2684   int w, h, handle_size;
2685   GtkAllocation allocation, sidebar_allocation;
2686
2687   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2688   w = allocation.width;
2689   h = allocation.height;
2690
2691   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2692
2693   gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2694   if (gtk_toggle_button_get_active (toggle))
2695     {
2696       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2697       gtk_widget_show (priv->sidebar);
2698       w += sidebar_allocation.width + handle_size;
2699     }
2700   else
2701     {
2702       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2703       w -= sidebar_allocation.width + handle_size;
2704       gtk_widget_hide (priv->sidebar);
2705     }
2706
2707   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2708
2709   if (w > 0 && h > 0)
2710     gtk_window_resize (GTK_WINDOW (window), w, h);
2711 }
2712
2713 static void
2714 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2715   gboolean send)
2716 {
2717   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2718   EmpathyTpCall *call;
2719
2720   priv->sending_video = send;
2721
2722   /* When we start sending video, we want to show the video preview by
2723      default. */
2724   display_video_preview (window, send);
2725
2726   if (priv->call_state != CONNECTED)
2727     return;
2728
2729   g_object_get (priv->handler, "tp-call", &call, NULL);
2730   DEBUG ("%s sending video", send ? "start": "stop");
2731   empathy_tp_call_request_video_stream_direction (call, send);
2732   g_object_unref (call);
2733 }
2734
2735 static void
2736 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2737   EmpathyCallWindow *window)
2738 {
2739   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2740   gboolean active;
2741
2742   active = (gtk_toggle_tool_button_get_active (toggle));
2743
2744   if (active)
2745     {
2746       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2747         priv->volume);
2748       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2749     }
2750   else
2751     {
2752       /* TODO, Instead of setting the input volume to 0 we should probably
2753        * stop sending but this would cause the audio call to drop if both
2754        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2755        * in the future. GNOME #574574
2756        */
2757       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2758         0);
2759       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2760     }
2761 }
2762
2763 static void
2764 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2765   EmpathyCallWindow *window)
2766 {
2767   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2768
2769   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2770     FALSE);
2771 }
2772
2773 static void
2774 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2775   EmpathyCallWindow *window)
2776 {
2777   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2778
2779   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2780     TRUE);
2781 }
2782
2783 static void
2784 empathy_call_window_hangup_cb (gpointer object,
2785                                EmpathyCallWindow *window)
2786 {
2787   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2788
2789   empathy_call_handler_stop_call (priv->handler);
2790
2791   if (empathy_call_window_disconnected (window, FALSE))
2792     gtk_widget_destroy (GTK_WIDGET (window));
2793 }
2794
2795 static void
2796 empathy_call_window_restart_call (EmpathyCallWindow *window)
2797 {
2798   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2799
2800   create_video_output_widget (window);
2801
2802   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2803       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2804
2805   /* While the call was disconnected, the input volume might have changed.
2806    * However, since the audio_input source was destroyed, its volume has not
2807    * been updated during that time. That's why we manually update it here */
2808   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2809
2810   priv->outgoing = TRUE;
2811   empathy_call_window_set_state_connecting (window);
2812
2813   if (priv->pipeline_playing)
2814     start_call (window);
2815   else
2816     /* call will be started when the pipeline is ready */
2817     priv->start_call_when_playing = TRUE;
2818
2819
2820   empathy_call_window_setup_avatars (window, priv->handler);
2821
2822   gtk_action_set_sensitive (priv->redial, FALSE);
2823   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2824 }
2825
2826 static void
2827 empathy_call_window_redial_cb (gpointer object,
2828     EmpathyCallWindow *window)
2829 {
2830   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2831
2832   if (priv->call_state == CONNECTED)
2833     priv->call_state = REDIALING;
2834
2835   empathy_call_handler_stop_call (priv->handler);
2836
2837   if (priv->call_state != CONNECTED)
2838     empathy_call_window_restart_call (window);
2839 }
2840
2841 static void
2842 empathy_call_window_fullscreen_cb (gpointer object,
2843                                    EmpathyCallWindow *window)
2844 {
2845   empathy_call_window_fullscreen_toggle (window);
2846 }
2847
2848 static void
2849 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2850 {
2851   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2852
2853   if (priv->is_fullscreen)
2854     gtk_window_unfullscreen (GTK_WINDOW (window));
2855   else
2856     gtk_window_fullscreen (GTK_WINDOW (window));
2857 }
2858
2859 static gboolean
2860 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2861   GdkEventButton *event, EmpathyCallWindow *window)
2862 {
2863   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2864     {
2865       empathy_call_window_video_menu_popup (window, event->button);
2866       return TRUE;
2867     }
2868
2869   return FALSE;
2870 }
2871
2872 static gboolean
2873 empathy_call_window_key_press_cb (GtkWidget *video_output,
2874   GdkEventKey *event, EmpathyCallWindow *window)
2875 {
2876   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2877
2878   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2879     {
2880       /* Since we are in fullscreen mode, toggling will bring us back to
2881          normal mode. */
2882       empathy_call_window_fullscreen_toggle (window);
2883       return TRUE;
2884     }
2885
2886   return FALSE;
2887 }
2888
2889 static gboolean
2890 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2891     GdkEventMotion *event, EmpathyCallWindow *window)
2892 {
2893   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2894
2895   if (priv->is_fullscreen)
2896     {
2897       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2898       return TRUE;
2899     }
2900   return FALSE;
2901 }
2902
2903 static void
2904 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2905   guint button)
2906 {
2907   GtkWidget *menu;
2908   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2909
2910   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2911             "/video-popup");
2912   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2913       button, gtk_get_current_event_time ());
2914   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2915 }
2916
2917 static void
2918 empathy_call_window_status_message (EmpathyCallWindow *window,
2919   gchar *message)
2920 {
2921   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2922
2923   if (priv->context_id == 0)
2924     {
2925       priv->context_id = gtk_statusbar_get_context_id (
2926         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2927     }
2928   else
2929     {
2930       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2931     }
2932
2933   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2934     message);
2935 }
2936
2937 static void
2938 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2939   gdouble value, EmpathyCallWindow *window)
2940 {
2941   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2942
2943   if (priv->audio_output == NULL)
2944     return;
2945
2946   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2947     value);
2948 }
2949
2950 /* block all the signals related to camera control widgets. This is useful
2951  * when we are manually updating the UI and so don't want to fire the
2952  * callbacks */
2953 static void
2954 block_camera_control_signals (EmpathyCallWindow *self)
2955 {
2956   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2957
2958   g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2959       tool_button_camera_off_toggled_cb, self);
2960   g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2961       tool_button_camera_preview_toggled_cb, self);
2962   g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2963       tool_button_camera_on_toggled_cb, self);
2964   g_signal_handlers_block_by_func (priv->action_camera,
2965       action_camera_change_cb, self);
2966 }
2967
2968 static void
2969 unblock_camera_control_signals (EmpathyCallWindow *self)
2970 {
2971   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2972
2973   g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2974       tool_button_camera_off_toggled_cb, self);
2975   g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2976       tool_button_camera_preview_toggled_cb, self);
2977   g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2978       tool_button_camera_on_toggled_cb, self);
2979   g_signal_handlers_unblock_by_func (priv->action_camera,
2980       action_camera_change_cb, self);
2981 }