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