]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
Verify that the video source could be linked into the 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 empathy_call_window_init (EmpathyCallWindow *self)
921 {
922   EmpathyCallWindowPriv *priv = GET_PRIV (self);
923   GtkBuilder *gui;
924   GtkWidget *top_vbox;
925   GtkWidget *h;
926   GtkWidget *arrow;
927   GtkWidget *page;
928   GstBus *bus;
929   gchar *filename;
930   GKeyFile *keyfile;
931   GError *error = NULL;
932
933   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
934   gui = empathy_builder_get_file (filename,
935     "call_window_vbox", &top_vbox,
936     "errors_vbox", &priv->errors_vbox,
937     "pane", &priv->pane,
938     "statusbar", &priv->statusbar,
939     "redial", &priv->redial_button,
940     "microphone", &priv->mic_button,
941     "toolbar", &priv->toolbar,
942     "menuredial", &priv->redial,
943     "ui_manager", &priv->ui_manager,
944     "menufullscreen", &priv->menu_fullscreen,
945     "camera_off", &priv->tool_button_camera_off,
946     "camera_preview", &priv->tool_button_camera_preview,
947     "camera_on", &priv->tool_button_camera_on,
948     "action_camera_off",  &priv->action_camera,
949     "action_camera_preview",  &priv->action_camera_preview,
950     NULL);
951   g_free (filename);
952
953   empathy_builder_connect (gui, self,
954     "menuhangup", "activate", empathy_call_window_hangup_cb,
955     "hangup", "clicked", empathy_call_window_hangup_cb,
956     "menuredial", "activate", empathy_call_window_redial_cb,
957     "redial", "clicked", empathy_call_window_redial_cb,
958     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
959     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
960     "camera_off", "toggled", tool_button_camera_off_toggled_cb,
961     "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
962     "camera_on", "toggled", tool_button_camera_on_toggled_cb,
963     "action_camera_off", "changed", action_camera_change_cb,
964     NULL);
965
966   priv->lock = g_mutex_new ();
967
968   gtk_container_add (GTK_CONTAINER (self), top_vbox);
969
970   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
971   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
972                                   CONTENT_HBOX_BORDER_WIDTH);
973   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
974
975   priv->pipeline = gst_pipeline_new (NULL);
976   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
977   priv->bus_message_source_id = gst_bus_add_watch (bus,
978       empathy_call_window_bus_message, self);
979
980   priv->fsnotifier = fs_element_added_notifier_new ();
981   fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
982
983   keyfile = g_key_file_new ();
984   filename = empathy_file_lookup ("element-properties", "data");
985   if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
986     {
987       fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
988           keyfile);
989     }
990   else
991     {
992       g_warning ("Could not load element-properties file: %s", error->message);
993       g_key_file_free (keyfile);
994       g_clear_error (&error);
995     }
996   g_free (filename);
997
998
999   priv->remote_user_output_frame = gtk_frame_new (NULL);
1000   gtk_widget_set_size_request (priv->remote_user_output_frame,
1001       EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
1002   gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1003       priv->remote_user_output_frame, TRUE, TRUE,
1004       CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1005   empathy_call_window_setup_remote_frame (bus, self);
1006
1007   priv->self_user_output_frame = gtk_frame_new (NULL);
1008   gtk_widget_set_size_request (priv->self_user_output_frame,
1009       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
1010
1011   priv->vbox = gtk_vbox_new (FALSE, 3);
1012   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
1013       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1014   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
1015       FALSE, FALSE, 0);
1016   empathy_call_window_setup_self_frame (bus, self);
1017
1018   empathy_call_window_setup_toolbar (self);
1019
1020   g_object_unref (bus);
1021
1022   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
1023   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1024   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
1025     G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
1026
1027   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1028
1029   h = gtk_hbox_new (FALSE, 3);
1030   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
1031   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
1032
1033   priv->sidebar = empathy_sidebar_new ();
1034   g_signal_connect (G_OBJECT (priv->sidebar),
1035     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
1036   g_signal_connect (G_OBJECT (priv->sidebar),
1037     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
1038   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1039
1040   page = empathy_call_window_create_audio_input (self);
1041   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
1042     page);
1043
1044   page = empathy_call_window_create_video_input (self);
1045   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
1046     page);
1047
1048   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
1049   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
1050     priv->dtmf_panel);
1051
1052   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1053
1054
1055   gtk_widget_show_all (top_vbox);
1056
1057   gtk_widget_hide (priv->sidebar);
1058
1059   priv->fullscreen = empathy_call_window_fullscreen_new (self);
1060   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1061       priv->video_output);
1062   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1063       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1064
1065   g_signal_connect (G_OBJECT (self), "realize",
1066     G_CALLBACK (empathy_call_window_realized_cb), self);
1067
1068   g_signal_connect (G_OBJECT (self), "delete-event",
1069     G_CALLBACK (empathy_call_window_delete_cb), self);
1070
1071   g_signal_connect (G_OBJECT (self), "window-state-event",
1072     G_CALLBACK (empathy_call_window_state_event_cb), self);
1073
1074   g_signal_connect (G_OBJECT (self), "key-press-event",
1075       G_CALLBACK (empathy_call_window_key_press_cb), self);
1076
1077   priv->timer = g_timer_new ();
1078
1079   g_object_ref (priv->ui_manager);
1080   g_object_unref (gui);
1081
1082   empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1083 }
1084
1085 /* Instead of specifying a width and a height, we specify only one size. That's
1086    because we want a square avatar icon.  */
1087 static void
1088 init_contact_avatar_with_size (EmpathyContact *contact,
1089     GtkWidget *image_widget,
1090     gint size)
1091 {
1092   GdkPixbuf *pixbuf_avatar = NULL;
1093
1094   if (contact != NULL)
1095     {
1096       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1097         size, size);
1098     }
1099
1100   if (pixbuf_avatar == NULL)
1101     {
1102       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1103           size);
1104     }
1105
1106   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1107 }
1108
1109 static void
1110 set_window_title (EmpathyCallWindow *self)
1111 {
1112   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1113   gchar *tmp;
1114
1115   /* translators: Call is a noun and %s is the contact name. This string
1116    * is used in the window title */
1117   tmp = g_strdup_printf (_("Call with %s"),
1118       empathy_contact_get_name (priv->contact));
1119   gtk_window_set_title (GTK_WINDOW (self), tmp);
1120   g_free (tmp);
1121 }
1122
1123 static void
1124 contact_name_changed_cb (EmpathyContact *contact,
1125     GParamSpec *pspec, EmpathyCallWindow *self)
1126 {
1127   set_window_title (self);
1128 }
1129
1130 static void
1131 contact_avatar_changed_cb (EmpathyContact *contact,
1132     GParamSpec *pspec, GtkWidget *avatar_widget)
1133 {
1134   int size;
1135
1136   size = avatar_widget->allocation.height;
1137
1138   if (size == 0)
1139     {
1140       /* the widget is not allocated yet, set a default size */
1141       size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1142           REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1143     }
1144
1145   init_contact_avatar_with_size (contact, avatar_widget, size);
1146 }
1147
1148 static void
1149 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1150     EmpathyContact *contact, const GError *error, gpointer user_data,
1151     GObject *weak_object)
1152 {
1153   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1154   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1155
1156   init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1157       MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1158
1159   g_signal_connect (contact, "notify::avatar",
1160       G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1161 }
1162
1163 static void
1164 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1165     EmpathyCallHandler *handler)
1166 {
1167   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1168
1169   g_object_get (handler, "contact", &(priv->contact), NULL);
1170
1171   if (priv->contact != NULL)
1172     {
1173       TpConnection *connection;
1174       EmpathyTpContactFactory *factory;
1175
1176       set_window_title (self);
1177
1178       g_signal_connect (priv->contact, "notify::name",
1179           G_CALLBACK (contact_name_changed_cb), self);
1180       g_signal_connect (priv->contact, "notify::avatar",
1181           G_CALLBACK (contact_avatar_changed_cb),
1182           priv->remote_user_avatar_widget);
1183
1184       /* Retreiving the self avatar */
1185       connection = empathy_contact_get_connection (priv->contact);
1186       factory = empathy_tp_contact_factory_dup_singleton (connection);
1187       empathy_tp_contact_factory_get_from_handle (factory,
1188           tp_connection_get_self_handle (connection),
1189           empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1190
1191       g_object_unref (factory);
1192     }
1193   else
1194     {
1195       g_warning ("call handler doesn't have a contact");
1196       /* translators: Call is a noun. This string is used in the window
1197        * title */
1198       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1199
1200       /* Since we can't access the remote contact, we can't get a connection
1201          to it and can't get the self contact (and its avatar). This means
1202          that we have to manually set the self avatar. */
1203       init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1204           MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1205     }
1206
1207   init_contact_avatar_with_size (priv->contact,
1208       priv->remote_user_avatar_widget,
1209       MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1210           REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1211
1212   /* The remote avatar is shown by default and will be hidden when we receive
1213      video from the remote side. */
1214   gtk_widget_hide (priv->video_output);
1215   gtk_widget_show (priv->remote_user_avatar_widget);
1216 }
1217
1218 static void
1219 empathy_call_window_constructed (GObject *object)
1220 {
1221   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1222   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1223   EmpathyTpCall *call;
1224
1225   g_assert (priv->handler != NULL);
1226
1227   g_object_get (priv->handler, "tp-call", &call, NULL);
1228   priv->outgoing = (call == NULL);
1229   if (call != NULL)
1230     g_object_unref (call);
1231
1232   empathy_call_window_setup_avatars (self, priv->handler);
1233   empathy_call_window_set_state_connecting (self);
1234
1235   if (!empathy_call_handler_has_initial_video (priv->handler))
1236     {
1237       gtk_toggle_tool_button_set_active (
1238           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1239     }
1240   /* If call has InitialVideo, the preview will be started once the call has
1241    * been started (start_call()). */
1242 }
1243
1244 static void empathy_call_window_dispose (GObject *object);
1245 static void empathy_call_window_finalize (GObject *object);
1246
1247 static void
1248 empathy_call_window_set_property (GObject *object,
1249   guint property_id, const GValue *value, GParamSpec *pspec)
1250 {
1251   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1252
1253   switch (property_id)
1254     {
1255       case PROP_CALL_HANDLER:
1256         priv->handler = g_value_dup_object (value);
1257         break;
1258       default:
1259         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1260     }
1261 }
1262
1263 static void
1264 empathy_call_window_get_property (GObject *object,
1265   guint property_id, GValue *value, GParamSpec *pspec)
1266 {
1267   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1268
1269   switch (property_id)
1270     {
1271       case PROP_CALL_HANDLER:
1272         g_value_set_object (value, priv->handler);
1273         break;
1274       default:
1275         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1276     }
1277 }
1278
1279 static void
1280 empathy_call_window_class_init (
1281   EmpathyCallWindowClass *empathy_call_window_class)
1282 {
1283   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1284   GParamSpec *param_spec;
1285
1286   g_type_class_add_private (empathy_call_window_class,
1287     sizeof (EmpathyCallWindowPriv));
1288
1289   object_class->constructed = empathy_call_window_constructed;
1290   object_class->set_property = empathy_call_window_set_property;
1291   object_class->get_property = empathy_call_window_get_property;
1292
1293   object_class->dispose = empathy_call_window_dispose;
1294   object_class->finalize = empathy_call_window_finalize;
1295
1296   param_spec = g_param_spec_object ("handler",
1297     "handler", "The call handler",
1298     EMPATHY_TYPE_CALL_HANDLER,
1299     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1300   g_object_class_install_property (object_class,
1301     PROP_CALL_HANDLER, param_spec);
1302 }
1303
1304 static void
1305 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1306     GParamSpec *property, EmpathyCallWindow *self)
1307 {
1308   DEBUG ("video stream changed");
1309   empathy_call_window_update_avatars_visibility (call, self);
1310 }
1311
1312 void
1313 empathy_call_window_dispose (GObject *object)
1314 {
1315   EmpathyTpCall *call;
1316   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1317   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1318
1319   if (priv->dispose_has_run)
1320     return;
1321
1322   priv->dispose_has_run = TRUE;
1323
1324   g_object_get (priv->handler, "tp-call", &call, NULL);
1325
1326   if (call != NULL)
1327     {
1328       g_signal_handlers_disconnect_by_func (call,
1329         empathy_call_window_video_stream_changed_cb, object);
1330       g_object_unref (call);
1331     }
1332
1333   if (priv->handler != NULL)
1334     g_object_unref (priv->handler);
1335   priv->handler = NULL;
1336
1337   if (priv->pipeline != NULL)
1338     g_object_unref (priv->pipeline);
1339   priv->pipeline = NULL;
1340
1341   if (priv->video_input != NULL)
1342     g_object_unref (priv->video_input);
1343   priv->video_input = NULL;
1344
1345   if (priv->audio_input != NULL)
1346     g_object_unref (priv->audio_input);
1347   priv->audio_input = NULL;
1348
1349   if (priv->audio_output != NULL)
1350     g_object_unref (priv->audio_output);
1351   priv->audio_output = NULL;
1352
1353   if (priv->video_tee != NULL)
1354     g_object_unref (priv->video_tee);
1355   priv->video_tee = NULL;
1356
1357   if (priv->fsnotifier != NULL)
1358     g_object_unref (priv->fsnotifier);
1359   priv->fsnotifier = NULL;
1360
1361   if (priv->timer_id != 0)
1362     g_source_remove (priv->timer_id);
1363   priv->timer_id = 0;
1364
1365   if (priv->ui_manager != NULL)
1366     g_object_unref (priv->ui_manager);
1367   priv->ui_manager = NULL;
1368
1369   if (priv->contact != NULL)
1370     {
1371       g_signal_handlers_disconnect_by_func (priv->contact,
1372           contact_name_changed_cb, self);
1373       g_object_unref (priv->contact);
1374       priv->contact = NULL;
1375     }
1376
1377   /* release any references held by the object here */
1378   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1379     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1380 }
1381
1382 void
1383 empathy_call_window_finalize (GObject *object)
1384 {
1385   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1386   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1387
1388   if (priv->video_output_motion_handler_id != 0)
1389     {
1390       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1391           priv->video_output_motion_handler_id);
1392       priv->video_output_motion_handler_id = 0;
1393     }
1394
1395   if (priv->bus_message_source_id != 0)
1396     {
1397       g_source_remove (priv->bus_message_source_id);
1398       priv->bus_message_source_id = 0;
1399     }
1400
1401   /* free any data held directly by the object here */
1402   g_mutex_free (priv->lock);
1403
1404   g_timer_destroy (priv->timer);
1405
1406   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1407 }
1408
1409
1410 EmpathyCallWindow *
1411 empathy_call_window_new (EmpathyCallHandler *handler)
1412 {
1413   return EMPATHY_CALL_WINDOW (
1414     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1415 }
1416
1417 static void
1418 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1419   GstElement *conference, gpointer user_data)
1420 {
1421   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1422   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1423
1424   gst_bin_add (GST_BIN (priv->pipeline), conference);
1425
1426   gst_element_set_state (conference, GST_STATE_PLAYING);
1427 }
1428
1429 static gboolean
1430 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1431   FsMediaType type, FsStreamDirection direction, gpointer user_data)
1432 {
1433   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1434   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1435
1436   if (type != FS_MEDIA_TYPE_VIDEO)
1437     return TRUE;
1438
1439   if (direction == FS_DIRECTION_RECV)
1440     return TRUE;
1441
1442   /* video and direction is send */
1443   return priv->video_input != NULL;
1444 }
1445
1446 static gboolean
1447 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1448 {
1449   GstStateChangeReturn state_change_return;
1450   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1451
1452   if (priv->pipeline == NULL)
1453     return TRUE;
1454
1455   if (priv->bus_message_source_id != 0)
1456     {
1457       g_source_remove (priv->bus_message_source_id);
1458       priv->bus_message_source_id = 0;
1459     }
1460
1461   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1462
1463   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1464         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1465     {
1466       if (priv->pipeline != NULL)
1467         g_object_unref (priv->pipeline);
1468       priv->pipeline = NULL;
1469
1470       if (priv->video_input != NULL)
1471         g_object_unref (priv->video_input);
1472       priv->video_input = NULL;
1473
1474       if (priv->audio_input != NULL)
1475         g_object_unref (priv->audio_input);
1476       priv->audio_input = NULL;
1477
1478       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1479           empathy_call_window_mic_volume_changed_cb, self);
1480
1481       if (priv->audio_output != NULL)
1482         g_object_unref (priv->audio_output);
1483       priv->audio_output = NULL;
1484
1485       if (priv->video_tee != NULL)
1486         g_object_unref (priv->video_tee);
1487       priv->video_tee = NULL;
1488
1489       if (priv->video_preview != NULL)
1490         gtk_widget_destroy (priv->video_preview);
1491       priv->video_preview = NULL;
1492
1493       priv->liveadder = NULL;
1494       priv->funnel = NULL;
1495
1496       return TRUE;
1497     }
1498   else
1499     {
1500       g_message ("Error: could not destroy pipeline. Closing call window");
1501       gtk_widget_destroy (GTK_WIDGET (self));
1502
1503       return FALSE;
1504     }
1505 }
1506
1507 static gboolean
1508 empathy_call_window_disconnected (EmpathyCallWindow *self)
1509 {
1510   gboolean could_disconnect = FALSE;
1511   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1512   gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1513
1514   if (priv->call_state == CONNECTING)
1515       empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1516
1517   if (priv->call_state != REDIALING)
1518     priv->call_state = DISCONNECTED;
1519
1520   if (could_reset_pipeline)
1521     {
1522       g_mutex_lock (priv->lock);
1523
1524       g_timer_stop (priv->timer);
1525
1526       if (priv->timer_id != 0)
1527         g_source_remove (priv->timer_id);
1528       priv->timer_id = 0;
1529
1530       g_mutex_unlock (priv->lock);
1531
1532       empathy_call_window_status_message (self, _("Disconnected"));
1533
1534       gtk_action_set_sensitive (priv->redial, TRUE);
1535       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1536
1537       /* Reseting the send_video, camera_buton and mic_button to their
1538          initial state */
1539       gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1540       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1541       gtk_toggle_tool_button_set_active (
1542           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1543       gtk_toggle_tool_button_set_active (
1544           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1545
1546       /* FIXME: This is to workaround the fact that the pipeline has been
1547        * destroyed and so we can't display preview until a new call (and so a
1548        * new pipeline) is created. We should fix this properly by refactoring
1549        * the code managing the pipeline. This is bug #602937 */
1550       gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
1551       gtk_action_set_sensitive (priv->action_camera_preview, FALSE);
1552
1553       gtk_progress_bar_set_fraction (
1554           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1555
1556       gtk_widget_hide (priv->video_output);
1557       gtk_widget_show (priv->remote_user_avatar_widget);
1558
1559       priv->sending_video = FALSE;
1560       priv->call_started = FALSE;
1561
1562       could_disconnect = TRUE;
1563
1564       /* TODO: display the self avatar of the preview (depends if the "Always
1565        * Show Video Preview" is enabled or not) */
1566     }
1567
1568   return could_disconnect;
1569 }
1570
1571
1572 static void
1573 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1574     gpointer user_data)
1575 {
1576   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1577   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1578
1579   if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1580       empathy_call_window_restart_call (self);
1581 }
1582
1583
1584 static void
1585 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1586     TfStream *stream, gpointer user_data)
1587 {
1588   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1589   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1590   guint media_type;
1591
1592   g_object_get (stream, "media-type", &media_type, NULL);
1593
1594   /*
1595    * This assumes that there is only one video stream per channel...
1596    */
1597
1598   if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1599     {
1600       if (priv->funnel != NULL)
1601         {
1602           GstElement *output;
1603
1604           output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1605               (priv->video_output));
1606
1607           gst_element_set_state (output, GST_STATE_NULL);
1608           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1609
1610           gst_bin_remove (GST_BIN (priv->pipeline), output);
1611           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1612           priv->funnel = NULL;
1613         }
1614     }
1615   else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1616     {
1617       if (priv->liveadder != NULL)
1618         {
1619           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1620           gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1621
1622           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1623           gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1624           priv->liveadder = NULL;
1625         }
1626     }
1627 }
1628
1629 /* Called with global lock held */
1630 static GstPad *
1631 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1632 {
1633   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1634   GstPad *pad;
1635
1636   if (priv->funnel == NULL)
1637     {
1638       GstElement *output;
1639
1640       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1641         (priv->video_output));
1642
1643       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1644
1645       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1646       gst_bin_add (GST_BIN (priv->pipeline), output);
1647
1648       gst_element_link (priv->funnel, output);
1649
1650       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1651       gst_element_set_state (output, GST_STATE_PLAYING);
1652     }
1653
1654   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1655
1656   return pad;
1657 }
1658
1659 /* Called with global lock held */
1660 static GstPad *
1661 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1662 {
1663   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1664   GstPad *pad;
1665   GstElement *filter;
1666   GError *gerror = NULL;
1667
1668   if (priv->liveadder == NULL)
1669     {
1670       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1671
1672       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
1673         {
1674           g_warning ("Could not add liveadder to the pipeline");
1675           goto error_add_liveadder;
1676         }
1677       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
1678         {
1679           g_warning ("Could not add audio sink to pipeline");
1680           goto error_add_output;
1681         }
1682
1683       if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1684         {
1685           g_warning ("Could not start liveadder");
1686           goto error;
1687         }
1688
1689       if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1690         {
1691           g_warning ("Could not start audio sink");
1692           goto error;
1693         }
1694
1695       if (GST_PAD_LINK_FAILED (
1696               gst_element_link (priv->liveadder, priv->audio_output)))
1697         {
1698           g_warning ("Could not link liveadder to audio output");
1699           goto error;
1700         }
1701     }
1702
1703   filter = gst_parse_bin_from_description (
1704       "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
1705   if (!filter)
1706     {
1707       g_warning ("Could not make audio conversion filter: %s", gerror->message);
1708       g_clear_error (&gerror);
1709       goto error;
1710     }
1711
1712   if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
1713     {
1714       g_warning ("Could not add audio conversion filter to pipeline");
1715       gst_object_unref (filter);
1716       goto error;
1717     }
1718
1719   if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1720     {
1721       g_warning ("Could not start audio conversion filter");
1722       goto error_filter;
1723     }
1724
1725   if (!gst_element_link (filter, priv->liveadder))
1726     {
1727       g_warning ("Could not link audio conversion filter to liveadder");
1728       goto error_filter;
1729     }
1730
1731   pad = gst_element_get_static_pad (filter, "sink");
1732
1733   if (!pad)
1734     {
1735       g_warning ("Could not get sink pad from filter");
1736       goto error_filter;
1737     }
1738
1739   return pad;
1740
1741  error_filter:
1742
1743   gst_element_set_locked_state (filter, TRUE);
1744   gst_element_set_state (filter, GST_STATE_NULL);
1745   gst_bin_remove (GST_BIN (priv->pipeline), filter);
1746
1747  error:
1748
1749   gst_element_set_locked_state (priv->liveadder, TRUE);
1750   gst_element_set_locked_state (priv->audio_output, TRUE);
1751
1752   gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1753   gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1754
1755   gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1756
1757  error_add_output:
1758
1759   gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1760
1761   gst_element_set_locked_state (priv->liveadder, FALSE);
1762   gst_element_set_locked_state (priv->audio_output, FALSE);
1763
1764  error_add_liveadder:
1765
1766   if (priv->liveadder)
1767     {
1768       gst_object_unref (priv->liveadder);
1769       priv->liveadder = NULL;
1770     }
1771   return NULL;
1772 }
1773
1774 static gboolean
1775 empathy_call_window_update_timer (gpointer user_data)
1776 {
1777   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1778   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1779   gchar *str;
1780   gdouble time_;
1781
1782   time_ = g_timer_elapsed (priv->timer, NULL);
1783
1784   /* Translators: number of minutes:seconds the caller has been connected */
1785   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time_ / 60,
1786     (int) time_ % 60);
1787   empathy_call_window_status_message (self, str);
1788   g_free (str);
1789
1790   return TRUE;
1791 }
1792
1793 static void
1794 display_error (EmpathyCallWindow *self,
1795     EmpathyTpCall *call,
1796     const gchar *img,
1797     const gchar *title,
1798     const gchar *desc,
1799     const gchar *details)
1800 {
1801   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1802   GtkWidget *info_bar;
1803   GtkWidget *content_area;
1804   GtkWidget *hbox;
1805   GtkWidget *vbox;
1806   GtkWidget *image;
1807   GtkWidget *label;
1808   gchar *txt;
1809
1810   /* Create info bar */
1811   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1812       NULL);
1813
1814   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1815
1816   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1817
1818   /* hbox containing the image and the messages vbox */
1819   hbox = gtk_hbox_new (FALSE, 3);
1820   gtk_container_add (GTK_CONTAINER (content_area), hbox);
1821
1822   /* Add image */
1823   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1824   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1825
1826   /* vbox containing the main message and the details expander */
1827   vbox = gtk_vbox_new (FALSE, 3);
1828   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1829
1830   /* Add text */
1831   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1832
1833   label = gtk_label_new (NULL);
1834   gtk_label_set_markup (GTK_LABEL (label), txt);
1835   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1836   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1837   g_free (txt);
1838
1839   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1840
1841   /* Add details */
1842   if (details != NULL)
1843     {
1844       GtkWidget *expander;
1845
1846       expander = gtk_expander_new (_("Technical Details"));
1847
1848       txt = g_strdup_printf ("<i>%s</i>", details);
1849
1850       label = gtk_label_new (NULL);
1851       gtk_label_set_markup (GTK_LABEL (label), txt);
1852       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1853       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1854       g_free (txt);
1855
1856       gtk_container_add (GTK_CONTAINER (expander), label);
1857       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1858     }
1859
1860   g_signal_connect (info_bar, "response",
1861       G_CALLBACK (gtk_widget_destroy), NULL);
1862
1863   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1864       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1865   gtk_widget_show_all (info_bar);
1866 }
1867
1868 static gchar *
1869 media_stream_error_to_txt (EmpathyCallWindow *self,
1870     EmpathyTpCall *call,
1871     gboolean audio,
1872     TpMediaStreamError error)
1873 {
1874   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1875   const gchar *cm;
1876   gchar *url;
1877   gchar *result;
1878
1879   switch (error)
1880     {
1881       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1882         if (audio)
1883           return g_strdup_printf (
1884               _("%s's software does not understand any of the audio formats "
1885                 "supported by your computer"),
1886             empathy_contact_get_name (priv->contact));
1887         else
1888           return g_strdup_printf (
1889               _("%s's software does not understand any of the video formats "
1890                 "supported by your computer"),
1891             empathy_contact_get_name (priv->contact));
1892
1893       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1894         return g_strdup_printf (
1895             _("Can't establish a connection to %s. "
1896               "One of you might be on a network that does not allow "
1897               "direct connections."),
1898           empathy_contact_get_name (priv->contact));
1899
1900       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1901           return g_strdup (_("There was a failure on the network"));
1902
1903       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1904         if (audio)
1905           return g_strdup (_("The audio formats necessary for this call "
1906                 "are not installed on your computer"));
1907         else
1908           return g_strdup (_("The video formats necessary for this call "
1909                 "are not installed on your computer"));
1910
1911       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1912         cm = empathy_tp_call_get_connection_manager (call);
1913
1914         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1915             "product=Telepathy&amp;component=%s", cm);
1916
1917         result = g_strdup_printf (
1918             _("Something unexpected happened in a Telepathy component. "
1919               "Please <a href=\"%s\">report this bug</a> and attach "
1920               "logs gathered from the 'Debug' window in the Help menu."), url);
1921
1922         g_free (url);
1923         return result;
1924
1925       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1926         return g_strdup (_("There was a failure in the call engine"));
1927
1928       default:
1929         return NULL;
1930     }
1931 }
1932
1933 static void
1934 empathy_call_window_stream_error (EmpathyCallWindow *self,
1935     EmpathyTpCall *call,
1936     gboolean audio,
1937     guint code,
1938     const gchar *msg,
1939     const gchar *icon,
1940     const gchar *title)
1941 {
1942   gchar *desc;
1943
1944   desc = media_stream_error_to_txt (self, call, audio, code);
1945   if (desc == NULL)
1946     {
1947       /* No description, use the error message. That's not great as it's not
1948        * localized but it's better than nothing. */
1949       display_error (self, call, icon, title, msg, NULL);
1950     }
1951   else
1952     {
1953       display_error (self, call, icon, title, desc, msg);
1954       g_free (desc);
1955     }
1956 }
1957
1958 static void
1959 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1960     guint code,
1961     const gchar *msg,
1962     EmpathyCallWindow *self)
1963 {
1964   empathy_call_window_stream_error (self, call, TRUE, code, msg,
1965       "gnome-stock-mic", _("Can't establish audio stream"));
1966 }
1967
1968 static void
1969 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1970     guint code,
1971     const gchar *msg,
1972     EmpathyCallWindow *self)
1973 {
1974   empathy_call_window_stream_error (self, call, FALSE, code, msg,
1975       "camera-web", _("Can't establish video stream"));
1976 }
1977
1978 static gboolean
1979 empathy_call_window_connected (gpointer user_data)
1980 {
1981   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1982   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1983   EmpathyTpCall *call;
1984   gboolean can_send_video;
1985
1986   empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1987
1988   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1989     empathy_contact_can_voip_video (priv->contact);
1990
1991   g_object_get (priv->handler, "tp-call", &call, NULL);
1992
1993   g_signal_connect (call, "notify::video-stream",
1994     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1995
1996   if (empathy_tp_call_has_dtmf (call))
1997     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1998
1999   if (priv->video_input == NULL)
2000     empathy_call_window_set_send_video (self, FALSE);
2001
2002   priv->sending_video = can_send_video ?
2003     empathy_tp_call_is_sending_video (call) : FALSE;
2004
2005   gtk_toggle_tool_button_set_active (
2006       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
2007       priv->sending_video && priv->video_input != NULL);
2008   gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
2009
2010   gtk_action_set_sensitive (priv->redial, FALSE);
2011   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2012
2013   gtk_widget_set_sensitive (priv->mic_button, TRUE);
2014
2015   /* FIXME: this should won't be needed once bug #602937 is fixed
2016    * (see empathy_call_window_disconnected for details) */
2017   gtk_widget_set_sensitive (priv->tool_button_camera_preview, TRUE);
2018   gtk_action_set_sensitive (priv->action_camera_preview, TRUE);
2019
2020   empathy_call_window_update_avatars_visibility (call, self);
2021
2022   g_object_unref (call);
2023
2024   g_mutex_lock (priv->lock);
2025
2026   priv->timer_id = g_timeout_add_seconds (1,
2027     empathy_call_window_update_timer, self);
2028
2029   g_mutex_unlock (priv->lock);
2030
2031   empathy_call_window_update_timer (self);
2032
2033   return FALSE;
2034 }
2035
2036
2037 /* Called from the streaming thread */
2038 static gboolean
2039 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2040   GstPad *src, guint media_type, gpointer user_data)
2041 {
2042   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2043   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2044   gboolean retval = FALSE;
2045
2046   GstPad *pad;
2047
2048   g_mutex_lock (priv->lock);
2049
2050   if (priv->call_state != CONNECTED)
2051     {
2052       g_timer_start (priv->timer);
2053       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
2054       priv->call_state = CONNECTED;
2055     }
2056
2057   switch (media_type)
2058     {
2059       case TP_MEDIA_STREAM_TYPE_AUDIO:
2060         pad = empathy_call_window_get_audio_sink_pad (self);
2061         break;
2062       case TP_MEDIA_STREAM_TYPE_VIDEO:
2063         gtk_widget_hide (priv->remote_user_avatar_widget);
2064         gtk_widget_show (priv->video_output);
2065         pad = empathy_call_window_get_video_sink_pad (self);
2066         break;
2067       default:
2068         g_assert_not_reached ();
2069     }
2070
2071   if (!pad)
2072     goto out;
2073
2074   if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2075       g_warning ("Could not link %s sink pad",
2076           media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2077   else
2078       retval = TRUE;
2079
2080   gst_object_unref (pad);
2081
2082  out:
2083
2084   /* If no sink could be linked, try to add fakesink to prevent the whole call
2085    * aborting */
2086
2087   if (!retval)
2088     {
2089       GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2090
2091       if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2092         {
2093           GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2094           if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2095               GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2096             {
2097               gst_element_set_locked_state (fakesink, TRUE);
2098               gst_element_set_state (fakesink, GST_STATE_NULL);
2099               gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2100             }
2101           else
2102             {
2103               g_debug ("Could not link real sink, linked fakesink instead");
2104             }
2105           gst_object_unref (sinkpad);
2106         }
2107       else
2108         {
2109           gst_object_unref (fakesink);
2110         }
2111     }
2112
2113
2114   g_mutex_unlock (priv->lock);
2115
2116   return TRUE;
2117 }
2118
2119 static gboolean
2120 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2121   GstPad *sink, guint media_type, gpointer user_data)
2122 {
2123   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2124   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2125   GstPad *pad;
2126   gboolean retval = FALSE;
2127
2128   switch (media_type)
2129     {
2130       case TP_MEDIA_STREAM_TYPE_AUDIO:
2131         if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2132           {
2133             g_warning ("Could not add audio source to pipeline");
2134             break;
2135           }
2136
2137         pad = gst_element_get_static_pad (priv->audio_input, "src");
2138         if (!pad)
2139           {
2140             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2141             g_warning ("Could not get source pad from audio source");
2142             break;
2143           }
2144
2145         if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2146           {
2147             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2148             g_warning ("Could not link audio source to farsight");
2149             break;
2150           }
2151
2152         if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2153           {
2154             g_warning ("Could not start audio source");
2155             gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2156             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2157             break;
2158           }
2159
2160         retval = TRUE;
2161         break;
2162       case TP_MEDIA_STREAM_TYPE_VIDEO:
2163         if (priv->video_input != NULL)
2164           {
2165             if (priv->video_tee != NULL)
2166               {
2167                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2168                 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2169                   {
2170                     g_warning ("Could not link videp soure input pipeline");
2171                     break;
2172                   }
2173               }
2174
2175             retval = TRUE;
2176           }
2177         break;
2178       default:
2179         g_assert_not_reached ();
2180     }
2181
2182   return retval;
2183 }
2184
2185 static void
2186 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2187 {
2188   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2189   GstElement *preview;
2190
2191   DEBUG ("remove video input");
2192   preview = empathy_video_widget_get_element (
2193     EMPATHY_VIDEO_WIDGET (priv->video_preview));
2194
2195   gst_element_set_state (priv->video_input, GST_STATE_NULL);
2196   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2197   gst_element_set_state (preview, GST_STATE_NULL);
2198
2199   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2200     priv->video_tee, preview, NULL);
2201
2202   g_object_unref (priv->video_input);
2203   priv->video_input = NULL;
2204   g_object_unref (priv->video_tee);
2205   priv->video_tee = NULL;
2206   gtk_widget_destroy (priv->video_preview);
2207   priv->video_preview = NULL;
2208
2209   gtk_toggle_tool_button_set_active (
2210       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
2211   gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2212
2213   gtk_widget_show (priv->self_user_avatar_widget);
2214 }
2215
2216 static void
2217 start_call (EmpathyCallWindow *self)
2218 {
2219   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2220
2221   priv->call_started = TRUE;
2222   empathy_call_handler_start_call (priv->handler);
2223   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2224
2225   if (empathy_call_handler_has_initial_video (priv->handler))
2226     {
2227       /* Enable 'send video' buttons and display the preview */
2228       gtk_toggle_tool_button_set_active (
2229           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
2230     }
2231 }
2232
2233 static gboolean
2234 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2235   gpointer user_data)
2236 {
2237   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2238   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2239   GstState newstate;
2240
2241   empathy_call_handler_bus_message (priv->handler, bus, message);
2242
2243   switch (GST_MESSAGE_TYPE (message))
2244     {
2245       case GST_MESSAGE_STATE_CHANGED:
2246         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2247           {
2248             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2249             if (newstate == GST_STATE_PAUSED)
2250                 empathy_call_window_setup_video_input (self);
2251           }
2252         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2253             !priv->call_started)
2254           {
2255             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2256             if (newstate == GST_STATE_PAUSED)
2257               {
2258                 start_call (self);
2259               }
2260           }
2261         break;
2262       case GST_MESSAGE_ERROR:
2263         {
2264           GError *error = NULL;
2265           GstElement *gst_error;
2266           gchar *debug;
2267
2268           gst_message_parse_error (message, &error, &debug);
2269           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2270
2271           g_message ("Element error: %s -- %s\n", error->message, debug);
2272
2273           if (g_str_has_prefix (gst_element_get_name (gst_error),
2274                 VIDEO_INPUT_ERROR_PREFIX))
2275             {
2276               /* Remove the video input and continue */
2277               if (priv->video_input != NULL)
2278                 empathy_call_window_remove_video_input (self);
2279               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2280             }
2281           else
2282             {
2283               empathy_call_window_disconnected (self);
2284             }
2285           g_error_free (error);
2286           g_free (debug);
2287         }
2288       default:
2289         break;
2290     }
2291
2292   return TRUE;
2293 }
2294
2295 static void
2296 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2297     EmpathyCallWindow *window)
2298 {
2299   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2300
2301   if (empathy_tp_call_is_receiving_video (call))
2302     {
2303       gtk_widget_hide (priv->remote_user_avatar_widget);
2304       gtk_widget_show (priv->video_output);
2305     }
2306   else
2307     {
2308       gtk_widget_hide (priv->video_output);
2309       gtk_widget_show (priv->remote_user_avatar_widget);
2310     }
2311 }
2312
2313 static void
2314 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2315     GParamSpec *spec,
2316     EmpathyCallWindow *self)
2317 {
2318   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2319   EmpathyTpCall *call;
2320
2321   g_object_get (priv->handler, "tp-call", &call, NULL);
2322   if (call == NULL)
2323     return;
2324
2325   empathy_signal_connect_weak (call, "audio-stream-error",
2326       G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2327   empathy_signal_connect_weak (call, "video-stream-error",
2328       G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2329
2330   g_object_unref (call);
2331 }
2332
2333 static void
2334 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2335 {
2336   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2337   EmpathyTpCall *call;
2338
2339   g_signal_connect (priv->handler, "conference-added",
2340     G_CALLBACK (empathy_call_window_conference_added_cb), window);
2341   g_signal_connect (priv->handler, "request-resource",
2342     G_CALLBACK (empathy_call_window_request_resource_cb), window);
2343   g_signal_connect (priv->handler, "closed",
2344     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2345   g_signal_connect (priv->handler, "src-pad-added",
2346     G_CALLBACK (empathy_call_window_src_added_cb), window);
2347   g_signal_connect (priv->handler, "sink-pad-added",
2348     G_CALLBACK (empathy_call_window_sink_added_cb), window);
2349   g_signal_connect (priv->handler, "stream-closed",
2350     G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2351
2352   g_object_get (priv->handler, "tp-call", &call, NULL);
2353   if (call != NULL)
2354     {
2355       empathy_signal_connect_weak (call, "audio-stream-error",
2356         G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2357       empathy_signal_connect_weak (call, "video-stream-error",
2358         G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2359
2360       g_object_unref (call);
2361     }
2362   else
2363     {
2364       /* tp-call doesn't exist yet, we'll connect signals once it has been
2365        * set */
2366       g_signal_connect (priv->handler, "notify::tp-call",
2367         G_CALLBACK (call_handler_notify_tp_call_cb), window);
2368     }
2369
2370   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2371 }
2372
2373 static gboolean
2374 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2375   EmpathyCallWindow *window)
2376 {
2377   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2378
2379   if (priv->pipeline != NULL)
2380     {
2381       if (priv->bus_message_source_id != 0)
2382         {
2383           g_source_remove (priv->bus_message_source_id);
2384           priv->bus_message_source_id = 0;
2385         }
2386
2387       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2388     }
2389
2390   if (priv->call_state == CONNECTING)
2391     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2392
2393   return FALSE;
2394 }
2395
2396 static void
2397 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2398 {
2399   GtkWidget *menu;
2400   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2401
2402   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2403             "/menubar1");
2404
2405   if (set_fullscreen)
2406     {
2407       gtk_widget_hide (priv->sidebar);
2408       gtk_widget_hide (menu);
2409       gtk_widget_hide (priv->vbox);
2410       gtk_widget_hide (priv->statusbar);
2411       gtk_widget_hide (priv->toolbar);
2412     }
2413   else
2414     {
2415       if (priv->sidebar_was_visible_before_fs)
2416         gtk_widget_show (priv->sidebar);
2417
2418       gtk_widget_show (menu);
2419       gtk_widget_show (priv->vbox);
2420       gtk_widget_show (priv->statusbar);
2421       gtk_widget_show (priv->toolbar);
2422
2423       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2424           priv->original_height_before_fs);
2425     }
2426 }
2427
2428 static void
2429 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2430 {
2431   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2432
2433   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2434       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2435   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2436       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2437   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2438       priv->video_output, TRUE, TRUE,
2439       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2440       GTK_PACK_START);
2441   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2442       priv->vbox, TRUE, TRUE,
2443       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2444       GTK_PACK_START);
2445 }
2446
2447 static gboolean
2448 empathy_call_window_state_event_cb (GtkWidget *widget,
2449   GdkEventWindowState *event, EmpathyCallWindow *window)
2450 {
2451   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2452     {
2453       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2454       gboolean set_fullscreen = event->new_window_state &
2455         GDK_WINDOW_STATE_FULLSCREEN;
2456
2457       if (set_fullscreen)
2458         {
2459           gboolean sidebar_was_visible;
2460           GtkAllocation allocation;
2461           gint original_width, original_height;
2462
2463           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2464           original_width = allocation.width;
2465           original_height = allocation.height;
2466
2467           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2468
2469           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2470           priv->original_width_before_fs = original_width;
2471           priv->original_height_before_fs = original_height;
2472
2473           if (priv->video_output_motion_handler_id == 0 &&
2474                 priv->video_output != NULL)
2475             {
2476               priv->video_output_motion_handler_id = g_signal_connect (
2477                   G_OBJECT (priv->video_output), "motion-notify-event",
2478                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2479                   window);
2480             }
2481         }
2482       else
2483         {
2484           if (priv->video_output_motion_handler_id != 0)
2485             {
2486               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2487                   priv->video_output_motion_handler_id);
2488               priv->video_output_motion_handler_id = 0;
2489             }
2490         }
2491
2492       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2493           set_fullscreen);
2494       show_controls (window, set_fullscreen);
2495       show_borders (window, set_fullscreen);
2496       gtk_action_set_stock_id (priv->menu_fullscreen,
2497           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2498       priv->is_fullscreen = set_fullscreen;
2499   }
2500
2501   return FALSE;
2502 }
2503
2504 static void
2505 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2506   EmpathyCallWindow *window)
2507 {
2508   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2509   GtkWidget *arrow;
2510   int w, h, handle_size;
2511   GtkAllocation allocation, sidebar_allocation;
2512
2513   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2514   w = allocation.width;
2515   h = allocation.height;
2516
2517   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2518
2519   gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2520   if (gtk_toggle_button_get_active (toggle))
2521     {
2522       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2523       gtk_widget_show (priv->sidebar);
2524       w += sidebar_allocation.width + handle_size;
2525     }
2526   else
2527     {
2528       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2529       w -= sidebar_allocation.width + handle_size;
2530       gtk_widget_hide (priv->sidebar);
2531     }
2532
2533   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2534
2535   if (w > 0 && h > 0)
2536     gtk_window_resize (GTK_WINDOW (window), w, h);
2537 }
2538
2539 static void
2540 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2541   gboolean send)
2542 {
2543   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2544   EmpathyTpCall *call;
2545
2546   priv->sending_video = send;
2547
2548   /* When we start sending video, we want to show the video preview by
2549      default. */
2550   display_video_preview (window, send);
2551
2552   if (priv->call_state != CONNECTED)
2553     return;
2554
2555   g_object_get (priv->handler, "tp-call", &call, NULL);
2556   DEBUG ("%s sending video", send ? "start": "stop");
2557   empathy_tp_call_request_video_stream_direction (call, send);
2558   g_object_unref (call);
2559 }
2560
2561 static void
2562 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2563   EmpathyCallWindow *window)
2564 {
2565   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2566   gboolean active;
2567
2568   if (priv->audio_input == NULL)
2569     return;
2570
2571   active = (gtk_toggle_tool_button_get_active (toggle));
2572
2573   if (active)
2574     {
2575       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2576         priv->volume);
2577       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2578     }
2579   else
2580     {
2581       /* TODO, Instead of setting the input volume to 0 we should probably
2582        * stop sending but this would cause the audio call to drop if both
2583        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2584        * in the future. GNOME #574574
2585        */
2586       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2587         0);
2588       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2589     }
2590 }
2591
2592 static void
2593 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2594   EmpathyCallWindow *window)
2595 {
2596   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2597
2598   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2599     FALSE);
2600 }
2601
2602 static void
2603 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2604   EmpathyCallWindow *window)
2605 {
2606   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2607
2608   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2609     TRUE);
2610 }
2611
2612 static void
2613 empathy_call_window_hangup_cb (gpointer object,
2614                                EmpathyCallWindow *window)
2615 {
2616   if (empathy_call_window_disconnected (window))
2617     gtk_widget_destroy (GTK_WIDGET (window));
2618 }
2619
2620 static void
2621 empathy_call_window_restart_call (EmpathyCallWindow *window)
2622 {
2623   GstBus *bus;
2624   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2625
2626   gtk_widget_destroy (priv->remote_user_output_hbox);
2627   gtk_widget_destroy (priv->self_user_output_hbox);
2628
2629   priv->pipeline = gst_pipeline_new (NULL);
2630   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2631   priv->bus_message_source_id = gst_bus_add_watch (bus,
2632       empathy_call_window_bus_message, window);
2633
2634   empathy_call_window_setup_remote_frame (bus, window);
2635   empathy_call_window_setup_self_frame (bus, window);
2636
2637   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2638       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2639
2640   /* While the call was disconnected, the input volume might have changed.
2641    * However, since the audio_input source was destroyed, its volume has not
2642    * been updated during that time. That's why we manually update it here */
2643   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2644
2645   g_object_unref (bus);
2646
2647   gtk_widget_show_all (priv->content_hbox);
2648
2649   priv->outgoing = TRUE;
2650   empathy_call_window_set_state_connecting (window);
2651
2652   start_call (window);
2653   empathy_call_window_setup_avatars (window, priv->handler);
2654
2655   gtk_action_set_sensitive (priv->redial, FALSE);
2656   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2657 }
2658
2659 static void
2660 empathy_call_window_redial_cb (gpointer object,
2661     EmpathyCallWindow *window)
2662 {
2663   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2664
2665   if (priv->call_state == CONNECTED)
2666     priv->call_state = REDIALING;
2667
2668   empathy_call_handler_stop_call (priv->handler);
2669
2670   if (priv->call_state != CONNECTED)
2671     empathy_call_window_restart_call (window);
2672 }
2673
2674 static void
2675 empathy_call_window_fullscreen_cb (gpointer object,
2676                                    EmpathyCallWindow *window)
2677 {
2678   empathy_call_window_fullscreen_toggle (window);
2679 }
2680
2681 static void
2682 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2683 {
2684   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2685
2686   if (priv->is_fullscreen)
2687     gtk_window_unfullscreen (GTK_WINDOW (window));
2688   else
2689     gtk_window_fullscreen (GTK_WINDOW (window));
2690 }
2691
2692 static gboolean
2693 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2694   GdkEventButton *event, EmpathyCallWindow *window)
2695 {
2696   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2697     {
2698       empathy_call_window_video_menu_popup (window, event->button);
2699       return TRUE;
2700     }
2701
2702   return FALSE;
2703 }
2704
2705 static gboolean
2706 empathy_call_window_key_press_cb (GtkWidget *video_output,
2707   GdkEventKey *event, EmpathyCallWindow *window)
2708 {
2709   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2710
2711   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2712     {
2713       /* Since we are in fullscreen mode, toggling will bring us back to
2714          normal mode. */
2715       empathy_call_window_fullscreen_toggle (window);
2716       return TRUE;
2717     }
2718
2719   return FALSE;
2720 }
2721
2722 static gboolean
2723 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2724     GdkEventMotion *event, EmpathyCallWindow *window)
2725 {
2726   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2727
2728   if (priv->is_fullscreen)
2729     {
2730       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2731       return TRUE;
2732     }
2733   return FALSE;
2734 }
2735
2736 static void
2737 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2738   guint button)
2739 {
2740   GtkWidget *menu;
2741   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2742
2743   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2744             "/video-popup");
2745   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2746       button, gtk_get_current_event_time ());
2747   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2748 }
2749
2750 static void
2751 empathy_call_window_status_message (EmpathyCallWindow *window,
2752   gchar *message)
2753 {
2754   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2755
2756   if (priv->context_id == 0)
2757     {
2758       priv->context_id = gtk_statusbar_get_context_id (
2759         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2760     }
2761   else
2762     {
2763       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2764     }
2765
2766   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2767     message);
2768 }
2769
2770 static void
2771 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2772   gdouble value, EmpathyCallWindow *window)
2773 {
2774   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2775
2776   if (priv->audio_output == NULL)
2777     return;
2778
2779   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2780     value);
2781 }
2782
2783 /* block all the signals related to camera control widgets. This is useful
2784  * when we are manually updating the UI and so don't want to fire the
2785  * callbacks */
2786 static void
2787 block_camera_control_signals (EmpathyCallWindow *self)
2788 {
2789   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2790
2791   g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2792       tool_button_camera_off_toggled_cb, self);
2793   g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2794       tool_button_camera_preview_toggled_cb, self);
2795   g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2796       tool_button_camera_on_toggled_cb, self);
2797   g_signal_handlers_block_by_func (priv->action_camera,
2798       action_camera_change_cb, self);
2799 }
2800
2801 static void
2802 unblock_camera_control_signals (EmpathyCallWindow *self)
2803 {
2804   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2805
2806   g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2807       tool_button_camera_off_toggled_cb, self);
2808   g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2809       tool_button_camera_preview_toggled_cb, self);
2810   g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2811       tool_button_camera_on_toggled_cb, self);
2812   g_signal_handlers_unblock_by_func (priv->action_camera,
2813       action_camera_change_cb, self);
2814 }