]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
media_stream_error_to_txt: display a link to the fdo bugzilla
[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     EmpathyTpCall *call,
1468     const gchar *img,
1469     const gchar *title,
1470     const gchar *desc,
1471     const gchar *details)
1472 {
1473   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1474   GtkWidget *info_bar;
1475   GtkWidget *content_area;
1476   GtkWidget *hbox;
1477   GtkWidget *vbox;
1478   GtkWidget *image;
1479   GtkWidget *label;
1480   gchar *txt;
1481
1482   /* Create info bar */
1483   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1484       NULL);
1485
1486   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1487
1488   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1489
1490   /* hbox containing the image and the messages vbox */
1491   hbox = gtk_hbox_new (FALSE, 3);
1492   gtk_container_add (GTK_CONTAINER (content_area), hbox);
1493
1494   /* Add image */
1495   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1496   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1497
1498   /* vbox containing the main message and the details expander */
1499   vbox = gtk_vbox_new (FALSE, 3);
1500   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1501
1502   /* Add text */
1503   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1504
1505   label = gtk_label_new (NULL);
1506   gtk_label_set_markup (GTK_LABEL (label), txt);
1507   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1508   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1509   g_free (txt);
1510
1511   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1512
1513   /* Add details */
1514   if (details != NULL)
1515     {
1516       GtkWidget *expander;
1517
1518       expander = gtk_expander_new (_("Technical Details"));
1519
1520       txt = g_strdup_printf ("<i>%s</i>", details);
1521
1522       label = gtk_label_new (NULL);
1523       gtk_label_set_markup (GTK_LABEL (label), txt);
1524       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1525       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1526       g_free (txt);
1527
1528       gtk_container_add (GTK_CONTAINER (expander), label);
1529       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1530     }
1531
1532   g_signal_connect (info_bar, "response",
1533       G_CALLBACK (gtk_widget_destroy), NULL);
1534
1535   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1536       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1537   gtk_widget_show_all (info_bar);
1538 }
1539
1540 static gchar *
1541 media_stream_error_to_txt (EmpathyCallWindow *self,
1542     EmpathyTpCall *call,
1543     gboolean audio,
1544     TpMediaStreamError error)
1545 {
1546   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1547   const gchar *cm;
1548   gchar *url;
1549   gchar *result;
1550
1551   switch (error)
1552     {
1553       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1554         if (audio)
1555           return g_strdup_printf (
1556               _("%s's software does not understand any of the audio formats "
1557                 "supported by your computer"),
1558             empathy_contact_get_name (priv->contact));
1559         else
1560           return g_strdup_printf (
1561               _("%s's software does not understand any of the video formats "
1562                 "supported by your computer"),
1563             empathy_contact_get_name (priv->contact));
1564
1565       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1566         return g_strdup_printf (
1567             _("Can't establish a connection to %s. "
1568               "One of you might be on a network that does not allow "
1569               "direct connections."),
1570           empathy_contact_get_name (priv->contact));
1571
1572       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1573           return g_strdup (_("There was a failure on the network"));
1574
1575       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1576         if (audio)
1577           return g_strdup (_("The audio formats necessary for this call "
1578                 "are not installed on your computer"));
1579         else
1580           return g_strdup (_("The video formats necessary for this call "
1581                 "are not installed on your computer"));
1582
1583       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1584         cm = empathy_tp_call_get_connection_manager (call);
1585
1586         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1587             "product=Telepathy&amp;component=%s", cm);
1588
1589         result = g_strdup_printf (
1590             _("Something not expected happened in a Telepathy component. "
1591               "Please <a href=\"%s\">report this bug</a> and attach "
1592               "logs gathered from the 'Debug' window in the Help menu."), url);
1593
1594         g_free (url);
1595         return result;
1596
1597       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1598         return g_strdup (_("There was a failure in the call engine"));
1599
1600       default:
1601         return NULL;
1602     }
1603 }
1604
1605 static void
1606 empathy_call_window_stream_error (EmpathyCallWindow *self,
1607     EmpathyTpCall *call,
1608     gboolean audio,
1609     guint code,
1610     const gchar *msg,
1611     const gchar *icon,
1612     const gchar *title)
1613 {
1614   gchar *desc;
1615
1616   desc = media_stream_error_to_txt (self, call, audio, code);
1617   if (desc == NULL)
1618     {
1619       /* No description, use the error message. That's not great as it's not
1620        * localized but it's better than nothing. */
1621       display_error (self, call, icon, title, msg, NULL);
1622     }
1623   else
1624     {
1625       display_error (self, call, icon, title, desc, msg);
1626       g_free (desc);
1627     }
1628 }
1629
1630 static void
1631 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1632     guint code,
1633     const gchar *msg,
1634     EmpathyCallWindow *self)
1635 {
1636   empathy_call_window_stream_error (self, call, TRUE, code, msg,
1637       "gnome-stock-mic", _("Can't establish audio stream"));
1638 }
1639
1640 static void
1641 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1642     guint code,
1643     const gchar *msg,
1644     EmpathyCallWindow *self)
1645 {
1646   empathy_call_window_stream_error (self, call, FALSE, code, msg,
1647       "camera-web", _("Can't establish video stream"));
1648 }
1649
1650 static gboolean
1651 empathy_call_window_connected (gpointer user_data)
1652 {
1653   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1654   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1655   EmpathyTpCall *call;
1656   gboolean can_send_video;
1657
1658   empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1659
1660   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1661     empathy_contact_can_voip_video (priv->contact);
1662
1663   g_object_get (priv->handler, "tp-call", &call, NULL);
1664
1665   g_signal_connect (call, "notify::video-stream",
1666     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1667
1668   if (empathy_tp_call_has_dtmf (call))
1669     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1670
1671   if (priv->video_input == NULL)
1672     empathy_call_window_set_send_video (self, FALSE);
1673
1674   priv->sending_video = can_send_video ?
1675     empathy_tp_call_is_sending_video (call) : FALSE;
1676
1677   gtk_action_set_sensitive (priv->show_preview, TRUE);
1678   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1679       priv->sending_video
1680       || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (
1681               priv->show_preview)));
1682   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1683       priv->sending_video && priv->video_input != NULL);
1684   gtk_toggle_tool_button_set_active (
1685       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1686       priv->sending_video && priv->video_input != NULL);
1687   gtk_widget_set_sensitive (priv->camera_button, can_send_video);
1688   gtk_action_set_sensitive (priv->send_video, can_send_video);
1689
1690   gtk_action_set_sensitive (priv->redial, FALSE);
1691   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1692
1693   gtk_widget_set_sensitive (priv->mic_button, TRUE);
1694
1695   empathy_call_window_update_avatars_visibility (call, self);
1696
1697   g_object_unref (call);
1698
1699   g_mutex_lock (priv->lock);
1700
1701   priv->timer_id = g_timeout_add_seconds (1,
1702     empathy_call_window_update_timer, self);
1703
1704   g_mutex_unlock (priv->lock);
1705
1706   empathy_call_window_update_timer (self);
1707
1708   return FALSE;
1709 }
1710
1711
1712 /* Called from the streaming thread */
1713 static void
1714 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1715   GstPad *src, guint media_type, gpointer user_data)
1716 {
1717   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1718   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1719
1720   GstPad *pad;
1721
1722   g_mutex_lock (priv->lock);
1723
1724   if (priv->call_state != CONNECTED)
1725     {
1726       g_timer_start (priv->timer);
1727       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1728       priv->call_state = CONNECTED;
1729     }
1730
1731   switch (media_type)
1732     {
1733       case TP_MEDIA_STREAM_TYPE_AUDIO:
1734         pad = empathy_call_window_get_audio_sink_pad (self);
1735         break;
1736       case TP_MEDIA_STREAM_TYPE_VIDEO:
1737         gtk_widget_hide (priv->remote_user_avatar_widget);
1738         gtk_widget_show (priv->video_output);
1739         pad = empathy_call_window_get_video_sink_pad (self);
1740         break;
1741       default:
1742         g_assert_not_reached ();
1743     }
1744
1745   gst_pad_link (src, pad);
1746   gst_object_unref (pad);
1747
1748   g_mutex_unlock (priv->lock);
1749 }
1750
1751 /* Called from the streaming thread */
1752 static void
1753 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1754   GstPad *sink, guint media_type, gpointer user_data)
1755 {
1756   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1757   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1758   GstPad *pad;
1759
1760   switch (media_type)
1761     {
1762       case TP_MEDIA_STREAM_TYPE_AUDIO:
1763         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1764
1765         pad = gst_element_get_static_pad (priv->audio_input, "src");
1766         gst_pad_link (pad, sink);
1767
1768         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1769         break;
1770       case TP_MEDIA_STREAM_TYPE_VIDEO:
1771         if (priv->video_input != NULL)
1772           {
1773             EmpathyTpCall *call;
1774             g_object_get (priv->handler, "tp-call", &call, NULL);
1775
1776             if (empathy_tp_call_is_sending_video (call))
1777               {
1778                 empathy_call_window_setup_video_preview (self);
1779
1780                 gtk_toggle_action_set_active (
1781                     GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1782
1783                 if (priv->video_preview != NULL)
1784                   gtk_widget_show (priv->video_preview);
1785                 gtk_widget_hide (priv->self_user_avatar_widget);
1786               }
1787
1788             g_object_unref (call);
1789
1790             if (priv->video_tee != NULL)
1791               {
1792                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1793                 gst_pad_link (pad, sink);
1794               }
1795           }
1796         break;
1797       default:
1798         g_assert_not_reached ();
1799     }
1800
1801 }
1802
1803 static void
1804 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1805 {
1806   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1807   GstElement *preview;
1808
1809   preview = empathy_video_widget_get_element (
1810     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1811
1812   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1813   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1814   gst_element_set_state (preview, GST_STATE_NULL);
1815
1816   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1817     priv->video_tee, preview, NULL);
1818
1819   g_object_unref (priv->video_input);
1820   priv->video_input = NULL;
1821   g_object_unref (priv->video_tee);
1822   priv->video_tee = NULL;
1823   gtk_widget_destroy (priv->video_preview);
1824   priv->video_preview = NULL;
1825
1826   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1827   gtk_toggle_tool_button_set_active (
1828       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1829   gtk_widget_set_sensitive (priv->camera_button, FALSE);
1830   gtk_action_set_sensitive (priv->send_video, FALSE);
1831
1832   gtk_widget_show (priv->self_user_avatar_widget);
1833 }
1834
1835
1836 static gboolean
1837 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1838   gpointer user_data)
1839 {
1840   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1841   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1842   GstState newstate;
1843
1844   empathy_call_handler_bus_message (priv->handler, bus, message);
1845
1846   switch (GST_MESSAGE_TYPE (message))
1847     {
1848       case GST_MESSAGE_STATE_CHANGED:
1849         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1850           {
1851             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1852             if (newstate == GST_STATE_PAUSED)
1853                 empathy_call_window_setup_video_input (self);
1854           }
1855         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1856             !priv->call_started)
1857           {
1858             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1859             if (newstate == GST_STATE_PAUSED)
1860               {
1861                 priv->call_started = TRUE;
1862                 empathy_call_handler_start_call (priv->handler);
1863                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1864               }
1865           }
1866         break;
1867       case GST_MESSAGE_ERROR:
1868         {
1869           GError *error = NULL;
1870           GstElement *gst_error;
1871           gchar *debug;
1872
1873           gst_message_parse_error (message, &error, &debug);
1874           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1875
1876           g_message ("Element error: %s -- %s\n", error->message, debug);
1877
1878           if (g_str_has_prefix (gst_element_get_name (gst_error),
1879                 VIDEO_INPUT_ERROR_PREFIX))
1880             {
1881               /* Remove the video input and continue */
1882               if (priv->video_input != NULL)
1883                 empathy_call_window_remove_video_input (self);
1884               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1885             }
1886           else
1887             {
1888               empathy_call_window_disconnected (self);
1889             }
1890           g_error_free (error);
1891           g_free (debug);
1892         }
1893       default:
1894         break;
1895     }
1896
1897   return TRUE;
1898 }
1899
1900 static void
1901 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1902 {
1903   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1904
1905   if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1906     {
1907       if (priv->video_preview != NULL)
1908         {
1909           gtk_widget_hide (priv->self_user_avatar_widget);
1910           gtk_widget_show (priv->video_preview);
1911         }
1912       else
1913         {
1914           if (priv->video_preview != NULL)
1915             gtk_widget_hide (priv->video_preview);
1916
1917           gtk_widget_show (priv->self_user_avatar_widget);
1918         }
1919     }
1920 }
1921
1922 static void
1923 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1924     EmpathyCallWindow *window)
1925 {
1926   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1927
1928   if (empathy_tp_call_is_receiving_video (call))
1929     {
1930       gtk_widget_hide (priv->remote_user_avatar_widget);
1931       gtk_widget_show (priv->video_output);
1932     }
1933   else
1934     {
1935       gtk_widget_hide (priv->video_output);
1936       gtk_widget_show (priv->remote_user_avatar_widget);
1937     }
1938
1939   empathy_call_window_update_self_avatar_visibility (window);
1940 }
1941
1942 static void
1943 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
1944     GParamSpec *spec,
1945     EmpathyCallWindow *self)
1946 {
1947   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1948   EmpathyTpCall *call;
1949
1950   g_object_get (priv->handler, "tp-call", &call, NULL);
1951   if (call == NULL)
1952     return;
1953
1954   empathy_signal_connect_weak (call, "audio-stream-error",
1955       G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
1956   empathy_signal_connect_weak (call, "video-stream-error",
1957       G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
1958
1959   g_object_unref (call);
1960 }
1961
1962 static void
1963 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1964 {
1965   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1966   EmpathyTpCall *call;
1967
1968   g_signal_connect (priv->handler, "conference-added",
1969     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1970   g_signal_connect (priv->handler, "request-resource",
1971     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1972   g_signal_connect (priv->handler, "closed",
1973     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1974   g_signal_connect (priv->handler, "src-pad-added",
1975     G_CALLBACK (empathy_call_window_src_added_cb), window);
1976   g_signal_connect (priv->handler, "sink-pad-added",
1977     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1978   g_signal_connect (priv->handler, "stream-closed",
1979     G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
1980
1981   g_object_get (priv->handler, "tp-call", &call, NULL);
1982   if (call != NULL)
1983     {
1984       empathy_signal_connect_weak (call, "audio-stream-error",
1985         G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
1986       empathy_signal_connect_weak (call, "video-stream-error",
1987         G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
1988
1989       g_object_unref (call);
1990     }
1991   else
1992     {
1993       /* tp-call doesn't exist yet, we'll connect signals once it has been
1994        * set */
1995       g_signal_connect (priv->handler, "notify::tp-call",
1996         G_CALLBACK (call_handler_notify_tp_call_cb), window);
1997     }
1998
1999   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2000 }
2001
2002 static gboolean
2003 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2004   EmpathyCallWindow *window)
2005 {
2006   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2007
2008   if (priv->pipeline != NULL)
2009     {
2010       if (priv->bus_message_source_id != 0)
2011         {
2012           g_source_remove (priv->bus_message_source_id);
2013           priv->bus_message_source_id = 0;
2014         }
2015
2016       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2017     }
2018
2019   if (priv->call_state == CONNECTING)
2020     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2021
2022   return FALSE;
2023 }
2024
2025 static void
2026 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2027 {
2028   GtkWidget *menu;
2029   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2030
2031   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2032             "/menubar1");
2033
2034   if (set_fullscreen)
2035     {
2036       gtk_widget_hide (priv->sidebar);
2037       gtk_widget_hide (menu);
2038       gtk_widget_hide (priv->vbox);
2039       gtk_widget_hide (priv->statusbar);
2040       gtk_widget_hide (priv->toolbar);
2041     }
2042   else
2043     {
2044       if (priv->sidebar_was_visible_before_fs)
2045         gtk_widget_show (priv->sidebar);
2046
2047       gtk_widget_show (menu);
2048       gtk_widget_show (priv->vbox);
2049       gtk_widget_show (priv->statusbar);
2050       gtk_widget_show (priv->toolbar);
2051
2052       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2053           priv->original_height_before_fs);
2054     }
2055 }
2056
2057 static void
2058 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2059 {
2060   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2061
2062   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2063       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2064   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2065       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2066   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2067       priv->video_output, TRUE, TRUE,
2068       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2069       GTK_PACK_START);
2070   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2071       priv->vbox, TRUE, TRUE,
2072       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2073       GTK_PACK_START);
2074 }
2075
2076 static gboolean
2077 empathy_call_window_state_event_cb (GtkWidget *widget,
2078   GdkEventWindowState *event, EmpathyCallWindow *window)
2079 {
2080   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2081     {
2082       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2083       gboolean set_fullscreen = event->new_window_state &
2084         GDK_WINDOW_STATE_FULLSCREEN;
2085
2086       if (set_fullscreen)
2087         {
2088           gboolean sidebar_was_visible;
2089           gint original_width = GTK_WIDGET (window)->allocation.width;
2090           gint original_height = GTK_WIDGET (window)->allocation.height;
2091
2092           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2093
2094           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2095           priv->original_width_before_fs = original_width;
2096           priv->original_height_before_fs = original_height;
2097
2098           if (priv->video_output_motion_handler_id == 0 &&
2099                 priv->video_output != NULL)
2100             {
2101               priv->video_output_motion_handler_id = g_signal_connect (
2102                   G_OBJECT (priv->video_output), "motion-notify-event",
2103                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2104                   window);
2105             }
2106         }
2107       else
2108         {
2109           if (priv->video_output_motion_handler_id != 0)
2110             {
2111               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2112                   priv->video_output_motion_handler_id);
2113               priv->video_output_motion_handler_id = 0;
2114             }
2115         }
2116
2117       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2118           set_fullscreen);
2119       show_controls (window, set_fullscreen);
2120       show_borders (window, set_fullscreen);
2121       gtk_action_set_stock_id (priv->menu_fullscreen,
2122           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2123       priv->is_fullscreen = set_fullscreen;
2124   }
2125
2126   return FALSE;
2127 }
2128
2129 static void
2130 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2131   EmpathyCallWindow *window)
2132 {
2133   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2134   GtkWidget *arrow;
2135   int w,h, handle_size;
2136
2137   w = GTK_WIDGET (window)->allocation.width;
2138   h = GTK_WIDGET (window)->allocation.height;
2139
2140   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2141
2142   if (gtk_toggle_button_get_active (toggle))
2143     {
2144       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2145       gtk_widget_show (priv->sidebar);
2146       w += priv->sidebar->allocation.width + handle_size;
2147     }
2148   else
2149     {
2150       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2151       w -= priv->sidebar->allocation.width + handle_size;
2152       gtk_widget_hide (priv->sidebar);
2153     }
2154
2155   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2156
2157   if (w > 0 && h > 0)
2158     gtk_window_resize (GTK_WINDOW (window), w, h);
2159 }
2160
2161 static void
2162 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2163   gboolean send)
2164 {
2165   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2166   EmpathyTpCall *call;
2167
2168   priv->sending_video = send;
2169
2170   /* When we start sending video, we want to show the video preview by
2171      default. */
2172   if (send)
2173     {
2174       empathy_call_window_setup_video_preview (window);
2175       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
2176           TRUE);
2177     }
2178
2179   g_object_get (priv->handler, "tp-call", &call, NULL);
2180   empathy_tp_call_request_video_stream_direction (call, send);
2181   g_object_unref (call);
2182 }
2183
2184 static void
2185 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
2186   EmpathyCallWindow *window)
2187 {
2188   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2189   gboolean active;
2190
2191   if (priv->call_state != CONNECTED)
2192     return;
2193
2194   active = (gtk_toggle_tool_button_get_active (toggle));
2195
2196   if (priv->sending_video == active)
2197     return;
2198
2199   empathy_call_window_set_send_video (window, active);
2200   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
2201 }
2202
2203 static void
2204 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
2205   EmpathyCallWindow *window)
2206 {
2207   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2208   gboolean active;
2209
2210   if (priv->call_state != CONNECTED)
2211     return;
2212
2213   active = (gtk_toggle_action_get_active (toggle));
2214
2215   if (priv->sending_video == active)
2216     return;
2217
2218   empathy_call_window_set_send_video (window, active);
2219   gtk_toggle_tool_button_set_active (
2220       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
2221 }
2222
2223 static void
2224 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
2225   EmpathyCallWindow *window)
2226 {
2227   gboolean show_preview_toggled;
2228   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2229
2230   show_preview_toggled = gtk_toggle_action_get_active (toggle);
2231
2232   if (show_preview_toggled)
2233     {
2234       empathy_call_window_setup_video_preview (window);
2235       gtk_widget_show (priv->self_user_output_frame);
2236       empathy_call_window_update_self_avatar_visibility (window);
2237     }
2238   else
2239     {
2240       gtk_widget_hide (priv->self_user_output_frame);
2241     }
2242 }
2243
2244 static void
2245 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2246   EmpathyCallWindow *window)
2247 {
2248   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2249   gboolean active;
2250
2251   if (priv->audio_input == NULL)
2252     return;
2253
2254   active = (gtk_toggle_tool_button_get_active (toggle));
2255
2256   if (active)
2257     {
2258       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2259         priv->volume);
2260       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2261     }
2262   else
2263     {
2264       /* TODO, Instead of setting the input volume to 0 we should probably
2265        * stop sending but this would cause the audio call to drop if both
2266        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2267        * in the future. GNOME #574574
2268        */
2269       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2270         0);
2271       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2272     }
2273 }
2274
2275 static void
2276 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2277   EmpathyCallWindow *window)
2278 {
2279   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2280
2281   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2282     FALSE);
2283 }
2284
2285 static void
2286 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2287   EmpathyCallWindow *window)
2288 {
2289   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2290
2291   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2292     TRUE);
2293 }
2294
2295 static void
2296 empathy_call_window_hangup_cb (gpointer object,
2297                                EmpathyCallWindow *window)
2298 {
2299   if (empathy_call_window_disconnected (window))
2300     gtk_widget_destroy (GTK_WIDGET (window));
2301 }
2302
2303 static void
2304 empathy_call_window_restart_call (EmpathyCallWindow *window)
2305 {
2306   GstBus *bus;
2307   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2308
2309   gtk_widget_destroy (priv->remote_user_output_hbox);
2310   gtk_widget_destroy (priv->self_user_output_hbox);
2311
2312   priv->pipeline = gst_pipeline_new (NULL);
2313   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2314   priv->bus_message_source_id = gst_bus_add_watch (bus,
2315       empathy_call_window_bus_message, window);
2316
2317   empathy_call_window_setup_remote_frame (bus, window);
2318   empathy_call_window_setup_self_frame (bus, window);
2319
2320   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2321       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2322
2323   /* While the call was disconnected, the input volume might have changed.
2324    * However, since the audio_input source was destroyed, its volume has not
2325    * been updated during that time. That's why we manually update it here */
2326   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2327
2328   g_object_unref (bus);
2329
2330   gtk_widget_show_all (priv->content_hbox);
2331
2332   if (!empathy_call_handler_has_initial_video (priv->handler))
2333     gtk_widget_hide (priv->self_user_output_frame);
2334
2335   priv->outgoing = TRUE;
2336   empathy_call_window_set_state_connecting (window);
2337
2338   priv->call_started = TRUE;
2339   empathy_call_handler_start_call (priv->handler);
2340   empathy_call_window_setup_avatars (window, priv->handler);
2341   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2342
2343   gtk_action_set_sensitive (priv->redial, FALSE);
2344   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2345 }
2346
2347 static void
2348 empathy_call_window_redial_cb (gpointer object,
2349     EmpathyCallWindow *window)
2350 {
2351   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2352
2353   if (priv->call_state == CONNECTED)
2354     priv->call_state = REDIALING;
2355
2356   empathy_call_handler_stop_call (priv->handler);
2357
2358   if (priv->call_state != CONNECTED)
2359     empathy_call_window_restart_call (window);
2360 }
2361
2362 static void
2363 empathy_call_window_fullscreen_cb (gpointer object,
2364                                    EmpathyCallWindow *window)
2365 {
2366   empathy_call_window_fullscreen_toggle (window);
2367 }
2368
2369 static void
2370 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2371 {
2372   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2373
2374   if (priv->is_fullscreen)
2375     gtk_window_unfullscreen (GTK_WINDOW (window));
2376   else
2377     gtk_window_fullscreen (GTK_WINDOW (window));
2378 }
2379
2380 static gboolean
2381 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2382   GdkEventButton *event, EmpathyCallWindow *window)
2383 {
2384   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2385     {
2386       empathy_call_window_video_menu_popup (window, event->button);
2387       return TRUE;
2388     }
2389
2390   return FALSE;
2391 }
2392
2393 static gboolean
2394 empathy_call_window_key_press_cb (GtkWidget *video_output,
2395   GdkEventKey *event, EmpathyCallWindow *window)
2396 {
2397   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2398
2399   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2400     {
2401       /* Since we are in fullscreen mode, toggling will bring us back to
2402          normal mode. */
2403       empathy_call_window_fullscreen_toggle (window);
2404       return TRUE;
2405     }
2406
2407   return FALSE;
2408 }
2409
2410 static gboolean
2411 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2412     GdkEventMotion *event, EmpathyCallWindow *window)
2413 {
2414   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2415
2416   if (priv->is_fullscreen)
2417     {
2418       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2419       return TRUE;
2420     }
2421   return FALSE;
2422 }
2423
2424 static void
2425 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2426   guint button)
2427 {
2428   GtkWidget *menu;
2429   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2430
2431   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2432             "/video-popup");
2433   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2434       button, gtk_get_current_event_time ());
2435   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2436 }
2437
2438 static void
2439 empathy_call_window_status_message (EmpathyCallWindow *window,
2440   gchar *message)
2441 {
2442   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2443
2444   if (priv->context_id == 0)
2445     {
2446       priv->context_id = gtk_statusbar_get_context_id (
2447         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2448     }
2449   else
2450     {
2451       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2452     }
2453
2454   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2455     message);
2456 }
2457
2458 static void
2459 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2460   gdouble value, EmpathyCallWindow *window)
2461 {
2462   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2463
2464   if (priv->audio_output == NULL)
2465     return;
2466
2467   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2468     value);
2469 }