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