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