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