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