]> git.0d.be Git - empathy.git/commitdiff
EmpathyCallWindow -> EmpathyStreamedMediaWindow
authorEmilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
Thu, 3 Feb 2011 13:57:28 +0000 (13:57 +0000)
committerEmilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
Thu, 3 Feb 2011 15:00:33 +0000 (15:00 +0000)
po/POTFILES.in
src/Makefile.am
src/empathy-av.c
src/empathy-call-window-fullscreen.c
src/empathy-call-window-fullscreen.h
src/empathy-call-window.c [deleted file]
src/empathy-call-window.h [deleted file]
src/empathy-streamed-media-window.c [new file with mode: 0644]
src/empathy-streamed-media-window.h [new file with mode: 0644]

index 4481dd9a94adf0bcc4d4aeabc3517a2adc413e53..eddcc87ee53261ade974ef527e3babd9fba7dadb 100644 (file)
@@ -81,7 +81,7 @@ src/empathy-accounts-dialog.c
 src/empathy-auth-client.c
 src/empathy-auto-salut-account-helper.c
 src/empathy-av.c
-src/empathy-call-window.c
+src/empathy-streamed-media-window.c
 [type: gettext/glade]src/empathy-call-window.ui
 src/empathy-chat-window.c
 [type: gettext/glade]src/empathy-chat-window.ui
index 8c51ded77f510fda352cbd57b9be95a781634154..b776290389fd3ecc2177845b29d41d68b3e98bd0 100644 (file)
@@ -103,7 +103,7 @@ empathy_debugger_SOURCES =                                          \
 empathy_handwritten_av_source = \
        empathy-av.c \
        empathy-call-window-fullscreen.c empathy-call-window-fullscreen.h \
-       empathy-call-window.c empathy-call-window.h      \
+       empathy-streamed-media-window.c empathy-streamed-media-window.h \
        $(NULL)
 
 empathy_av_SOURCES =                                           \
index e93382d34d7a29a844dd1d09268175a344430b1e..29d2efadba18a4bf5032d22d536a4f875731601c 100644 (file)
@@ -31,7 +31,7 @@
 #include <libempathy/empathy-call-factory.h>
 #include <libempathy-gtk/empathy-ui-utils.h>
 
-#include "empathy-call-window.h"
+#include "empathy-streamed-media-window.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
 #include <libempathy/empathy-debug.h>
@@ -55,11 +55,11 @@ new_call_handler_cb (EmpathyCallFactory *factory,
     gboolean outgoing,
     gpointer user_data)
 {
-  EmpathyCallWindow *window;
+  EmpathyStreamedMediaWindow *window;
 
   DEBUG ("Create a new call window");
 
-  window = empathy_call_window_new (handler);
+  window = empathy_streamed_media_window_new (handler);
 
   g_application_hold (G_APPLICATION (app));
 
index 966d49add466b778760136427185dc0bd909cd82..dad0b466cabc8ac0f0fd2bcecd1d09de937a8def 100644 (file)
@@ -42,7 +42,7 @@ typedef struct _EmpathyCallWindowFullscreenPriv
 
 struct _EmpathyCallWindowFullscreenPriv
 {
-  EmpathyCallWindow *parent_window;
+  EmpathyStreamedMediaWindow *parent_window;
 
   GtkWidget *leave_fullscreen_popup;
   GtkWidget *video_widget;
@@ -260,7 +260,7 @@ empathy_call_window_fullscreen_parent_window_notify (GtkWidget *parent_window,
 }
 
 EmpathyCallWindowFullscreen *
-empathy_call_window_fullscreen_new (EmpathyCallWindow *parent_window)
+empathy_call_window_fullscreen_new (EmpathyStreamedMediaWindow *parent_window)
 {
   EmpathyCallWindowFullscreen *self = EMPATHY_CALL_WINDOW_FULLSCREEN (
     g_object_new (EMPATHY_TYPE_CALL_WINDOW_FULLSCREEN, NULL));
index 8bde16ced575ae31b8a31795eeb614de25ef8a9a..967a52475260c5b947c0eaaddacef31dced6f262 100644 (file)
@@ -23,7 +23,7 @@
 #include <glib-object.h>
 #include <gtk/gtk.h>
 
-#include "empathy-call-window.h"
+#include "empathy-streamed-media-window.h"
 
 G_BEGIN_DECLS
 
@@ -61,7 +61,7 @@ GType empathy_call_window_fullscreen_get_type (void);
     EmpathyCallWindowFullscreenClass))
 
 EmpathyCallWindowFullscreen *empathy_call_window_fullscreen_new (
-    EmpathyCallWindow *parent);
+    EmpathyStreamedMediaWindow *parent);
 
 void empathy_call_window_fullscreen_set_fullscreen (
     EmpathyCallWindowFullscreen *fs,
diff --git a/src/empathy-call-window.c b/src/empathy-call-window.c
deleted file mode 100644 (file)
index ddfebea..0000000
+++ /dev/null
@@ -1,3323 +0,0 @@
-/*
- * empathy-call-window.c - Source for EmpathyCallWindow
- * Copyright (C) 2008-2009 Collabora Ltd.
- * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- */
-
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <math.h>
-
-#include <gdk/gdkkeysyms.h>
-#include <gst/gst.h>
-#include <gtk/gtk.h>
-#include <glib/gi18n.h>
-
-#include <telepathy-glib/util.h>
-#include <telepathy-farsight/channel.h>
-#include <telepathy-glib/util.h>
-
-#include <gst/farsight/fs-element-added-notifier.h>
-
-#include <libempathy/empathy-tp-contact-factory.h>
-#include <libempathy/empathy-call-factory.h>
-#include <libempathy/empathy-utils.h>
-#include <libempathy-gtk/empathy-avatar-image.h>
-#include <libempathy-gtk/empathy-video-widget.h>
-#include <libempathy-gtk/empathy-audio-src.h>
-#include <libempathy-gtk/empathy-audio-sink.h>
-#include <libempathy-gtk/empathy-video-src.h>
-#include <libempathy-gtk/empathy-ui-utils.h>
-#include <libempathy-gtk/empathy-sound-manager.h>
-#include <libempathy-gtk/empathy-geometry.h>
-#include <libempathy-gtk/empathy-images.h>
-
-#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
-#include <libempathy/empathy-debug.h>
-
-#include "empathy-call-window.h"
-#include "empathy-call-window-fullscreen.h"
-#include "ev-sidebar.h"
-
-#define BUTTON_ID "empathy-call-dtmf-button-id"
-
-#define CONTENT_HBOX_BORDER_WIDTH 6
-#define CONTENT_HBOX_SPACING 3
-#define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
-
-#define SELF_VIDEO_SECTION_WIDTH 160
-#define SELF_VIDEO_SECTION_HEIGTH 120
-
-/* The avatar's default width and height are set to the same value because we
-   want a square icon. */
-#define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
-#define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
-  EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
-
-/* If an video input error occurs, the error message will start with "v4l" */
-#define VIDEO_INPUT_ERROR_PREFIX "v4l"
-
-/* The time interval in milliseconds between 2 outgoing rings */
-#define MS_BETWEEN_RING 500
-
-G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
-
-/* signal enum */
-#if 0
-enum
-{
-    LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = {0};
-#endif
-
-enum {
-  PROP_STREAMED_MEDIA_HANDLER = 1,
-};
-
-typedef enum {
-  CONNECTING,
-  CONNECTED,
-  DISCONNECTED,
-  REDIALING
-} CallState;
-
-typedef enum {
-  CAMERA_STATE_OFF = 0,
-  CAMERA_STATE_PREVIEW,
-  CAMERA_STATE_ON,
-} CameraState;
-
-/* private structure */
-typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
-
-struct _EmpathyCallWindowPriv
-{
-  gboolean dispose_has_run;
-  EmpathyStreamedMediaHandler *handler;
-  EmpathyContact *contact;
-
-  guint call_state;
-  gboolean outgoing;
-
-  GtkUIManager *ui_manager;
-  GtkWidget *errors_vbox;
-  /* widget displays the video received from the remote user. This widget is
-   * alive only during call. */
-  GtkWidget *video_output;
-  GtkWidget *video_preview;
-  GtkWidget *remote_user_avatar_widget;
-  GtkWidget *self_user_avatar_widget;
-  GtkWidget *sidebar;
-  GtkWidget *sidebar_button;
-  GtkWidget *statusbar;
-  GtkWidget *volume_button;
-  GtkWidget *redial_button;
-  GtkWidget *mic_button;
-  GtkWidget *toolbar;
-  GtkWidget *pane;
-  GtkAction *redial;
-  GtkAction *menu_fullscreen;
-  GtkAction *action_camera_on;
-  GtkWidget *tool_button_camera_off;
-  GtkWidget *tool_button_camera_preview;
-  GtkWidget *tool_button_camera_on;
-
-  /* The frames and boxes that contain self and remote avatar and video
-     input/output. When we redial, we destroy and re-create the boxes */
-  GtkWidget *remote_user_output_frame;
-  GtkWidget *self_user_output_frame;
-  GtkWidget *remote_user_output_hbox;
-  GtkWidget *self_user_output_hbox;
-
-  /* We keep a reference on the hbox which contains the main content so we can
-     easilly repack everything when toggling fullscreen */
-  GtkWidget *content_hbox;
-
-  /* This vbox is contained in the content_hbox and it contains the
-     self_user_output_frame and the sidebar button. When toggling fullscreen,
-     it needs to be repacked. We keep a reference on it for easier access. */
-  GtkWidget *vbox;
-
-  gulong video_output_motion_handler_id;
-  guint bus_message_source_id;
-
-  gdouble volume;
-  GtkWidget *volume_scale;
-  GtkWidget *volume_progress_bar;
-  GtkAdjustment *audio_input_adj;
-
-  GtkWidget *dtmf_panel;
-
-  /* Details vbox */
-  GtkWidget *details_vbox;
-  GtkWidget *vcodec_encoding_label;
-  GtkWidget *acodec_encoding_label;
-  GtkWidget *vcodec_decoding_label;
-  GtkWidget *acodec_decoding_label;
-
-  GtkWidget *audio_remote_candidate_label;
-  GtkWidget *audio_local_candidate_label;
-  GtkWidget *video_remote_candidate_label;
-  GtkWidget *video_local_candidate_label;
-  GtkWidget *video_remote_candidate_info_img;
-  GtkWidget *video_local_candidate_info_img;
-  GtkWidget *audio_remote_candidate_info_img;
-  GtkWidget *audio_local_candidate_info_img;
-
-  GstElement *video_input;
-  GstElement *audio_input;
-  GstElement *audio_output;
-  GstElement *pipeline;
-  GstElement *video_tee;
-
-  GstElement *funnel;
-  GstElement *liveadder;
-
-  FsElementAddedNotifier *fsnotifier;
-
-  guint context_id;
-
-  GTimer *timer;
-  guint timer_id;
-
-  GtkWidget *video_contrast;
-  GtkWidget *video_brightness;
-  GtkWidget *video_gamma;
-
-  GMutex *lock;
-  gboolean call_started;
-  gboolean sending_video;
-  CameraState camera_state;
-
-  EmpathyCallWindowFullscreen *fullscreen;
-  gboolean is_fullscreen;
-
-  /* Those fields represent the state of the window before it actually was in
-     fullscreen mode. */
-  gboolean sidebar_was_visible_before_fs;
-  gint original_width_before_fs;
-  gint original_height_before_fs;
-
-  /* TRUE if the call should be started when the pipeline is playing */
-  gboolean start_call_when_playing;
-  /* TRUE if we requested to set the pipeline in the playing state */
-  gboolean pipeline_playing;
-
-  EmpathySoundManager *sound_mgr;
-};
-
-#define GET_PRIV(o) \
-  (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
-    EmpathyCallWindowPriv))
-
-static void empathy_call_window_realized_cb (GtkWidget *widget,
-  EmpathyCallWindow *window);
-
-static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
-  GdkEvent *event, EmpathyCallWindow *window);
-
-static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
-  GdkEventWindowState *event, EmpathyCallWindow *window);
-
-static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
-  EmpathyCallWindow *window);
-
-static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
-  CameraState state);
-
-static void empathy_call_window_mic_toggled_cb (
-  GtkToggleToolButton *toggle, EmpathyCallWindow *window);
-
-static void empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
-  EmpathyCallWindow *window);
-
-static void empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
-  EmpathyCallWindow *window);
-
-static void empathy_call_window_hangup_cb (gpointer object,
-  EmpathyCallWindow *window);
-
-static void empathy_call_window_fullscreen_cb (gpointer object,
-  EmpathyCallWindow *window);
-
-static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
-
-static gboolean empathy_call_window_video_button_press_cb (
-  GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
-
-static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
-  GdkEventKey *event, EmpathyCallWindow *window);
-
-static gboolean empathy_call_window_video_output_motion_notify (
-  GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
-
-static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
-  guint button);
-
-static void empathy_call_window_redial_cb (gpointer object,
-  EmpathyCallWindow *window);
-
-static void empathy_call_window_restart_call (EmpathyCallWindow *window);
-
-static void empathy_call_window_status_message (EmpathyCallWindow *window,
-  gchar *message);
-
-static void empathy_call_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
-  EmpathyCallWindow *window);
-
-static gboolean empathy_call_window_bus_message (GstBus *bus,
-  GstMessage *message, gpointer user_data);
-
-static void
-empathy_call_window_volume_changed_cb (GtkScaleButton *button,
-  gdouble value, EmpathyCallWindow *window);
-
-static void block_camera_control_signals (EmpathyCallWindow *self);
-static void unblock_camera_control_signals (EmpathyCallWindow *self);
-
-static void
-empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GtkToolItem *tool_item;
-  GtkWidget *camera_off_icon;
-  GdkPixbuf *pixbuf, *modded_pixbuf;
-
-  /* set the icon of the 'camera off' button by greying off the webcam icon */
-  pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
-      GTK_ICON_SIZE_SMALL_TOOLBAR);
-
-  modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
-      gdk_pixbuf_get_width (pixbuf),
-      gdk_pixbuf_get_height (pixbuf));
-
-  gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
-  g_object_unref (pixbuf);
-
-  camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
-  g_object_unref (modded_pixbuf);
-  gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
-        priv->tool_button_camera_off), camera_off_icon);
-
-  /* Add an empty expanded GtkToolItem so the volume button is at the end of
-   * the toolbar. */
-  tool_item = gtk_tool_item_new ();
-  gtk_tool_item_set_expand (tool_item, TRUE);
-  gtk_widget_show (GTK_WIDGET (tool_item));
-  gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
-
-  priv->volume_button = gtk_volume_button_new ();
-  /* FIXME listen to the audiosinks signals and update the button according to
-   * that, for now starting out at 1.0 and assuming only the app changes the
-   * volume will do */
-  gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
-  g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
-    G_CALLBACK (empathy_call_window_volume_changed_cb), self);
-
-  tool_item = gtk_tool_item_new ();
-  gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
-  gtk_widget_show_all (GTK_WIDGET (tool_item));
-  gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
-}
-
-static void
-dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  EmpathyTpStreamedMedia *call;
-  GQuark button_quark;
-  TpDTMFEvent event;
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-
-  button_quark = g_quark_from_static_string (BUTTON_ID);
-  event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
-    button_quark));
-
-  empathy_tp_streamed_media_start_tone (call, event);
-
-  g_object_unref (call);
-}
-
-static void
-dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  EmpathyTpStreamedMedia *call;
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-
-  empathy_tp_streamed_media_stop_tone (call);
-
-  g_object_unref (call);
-}
-
-static GtkWidget *
-empathy_call_window_create_dtmf (EmpathyCallWindow *self)
-{
-  GtkWidget *table;
-  int i;
-  GQuark button_quark;
-  struct {
-    const gchar *label;
-    TpDTMFEvent event;
-  } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
-                      { "2", TP_DTMF_EVENT_DIGIT_2 },
-                      { "3", TP_DTMF_EVENT_DIGIT_3 },
-                      { "4", TP_DTMF_EVENT_DIGIT_4 },
-                      { "5", TP_DTMF_EVENT_DIGIT_5 },
-                      { "6", TP_DTMF_EVENT_DIGIT_6 },
-                      { "7", TP_DTMF_EVENT_DIGIT_7 },
-                      { "8", TP_DTMF_EVENT_DIGIT_8 },
-                      { "9", TP_DTMF_EVENT_DIGIT_9 },
-                      { "#", TP_DTMF_EVENT_HASH },
-                      { "0", TP_DTMF_EVENT_DIGIT_0 },
-                      { "*", TP_DTMF_EVENT_ASTERISK },
-                      { NULL, } };
-
-  button_quark = g_quark_from_static_string (BUTTON_ID);
-
-  table = gtk_table_new (4, 3, TRUE);
-
-  for (i = 0; dtmfbuttons[i].label != NULL; i++)
-    {
-      GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
-      gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
-        i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
-
-      g_object_set_qdata (G_OBJECT (button), button_quark,
-        GUINT_TO_POINTER (dtmfbuttons[i].event));
-
-      g_signal_connect (G_OBJECT (button), "pressed",
-        G_CALLBACK (dtmf_button_pressed_cb), self);
-      g_signal_connect (G_OBJECT (button), "released",
-        G_CALLBACK (dtmf_button_released_cb), self);
-    }
-
-  return table;
-}
-
-static GtkWidget *
-empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
-  gchar *label_text, GtkWidget *bin)
-{
-   GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
-   GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
-   GtkWidget *label = gtk_label_new (label_text);
-
-   gtk_widget_set_sensitive (scale, FALSE);
-
-   gtk_container_add (GTK_CONTAINER (bin), vbox);
-
-   gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
-   gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
-   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
-
-   return scale;
-}
-
-static void
-empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
-  EmpathyCallWindow *self)
-
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  empathy_video_src_set_channel (priv->video_input,
-    EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
-}
-
-static void
-empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
-  EmpathyCallWindow *self)
-
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  empathy_video_src_set_channel (priv->video_input,
-    EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
-}
-
-static void
-empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
-  EmpathyCallWindow *self)
-
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  empathy_video_src_set_channel (priv->video_input,
-    EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
-}
-
-
-static GtkWidget *
-empathy_call_window_create_video_input (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GtkWidget *hbox;
-
-  hbox = gtk_hbox_new (TRUE, 3);
-
-  priv->video_contrast = empathy_call_window_create_video_input_add_slider (
-    self,  _("Contrast"), hbox);
-
-  priv->video_brightness = empathy_call_window_create_video_input_add_slider (
-    self,  _("Brightness"), hbox);
-
-  priv->video_gamma = empathy_call_window_create_video_input_add_slider (
-    self,  _("Gamma"), hbox);
-
-  return hbox;
-}
-
-static void
-empathy_call_window_setup_video_input (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  guint supported;
-  GtkAdjustment *adj;
-
-  supported = empathy_video_src_get_supported_channels (priv->video_input);
-
-  if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
-    {
-      adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
-
-      gtk_adjustment_set_value (adj,
-        empathy_video_src_get_channel (priv->video_input,
-          EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
-
-      g_signal_connect (G_OBJECT (adj), "value-changed",
-        G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
-
-      gtk_widget_set_sensitive (priv->video_contrast, TRUE);
-    }
-
-  if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
-    {
-      adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
-
-      gtk_adjustment_set_value (adj,
-        empathy_video_src_get_channel (priv->video_input,
-          EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
-
-      g_signal_connect (G_OBJECT (adj), "value-changed",
-        G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
-      gtk_widget_set_sensitive (priv->video_brightness, TRUE);
-    }
-
-  if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
-    {
-      adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
-
-      gtk_adjustment_set_value (adj,
-        empathy_video_src_get_channel (priv->video_input,
-          EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
-
-      g_signal_connect (G_OBJECT (adj), "value-changed",
-        G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
-      gtk_widget_set_sensitive (priv->video_gamma, TRUE);
-    }
-}
-
-static void
-empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
-  EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  gdouble volume;
-
-  volume = gtk_adjustment_get_value (adj)/100.0;
-
-  /* Don't store the volume because of muting */
-  if (volume > 0 || gtk_toggle_tool_button_get_active (
-        GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
-    priv->volume = volume;
-
-  /* Ensure that the toggle button is active if the volume is > 0 and inactive
-   * if it's smaller than 0 */
-  if ((volume > 0) != gtk_toggle_tool_button_get_active (
-        GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
-    gtk_toggle_tool_button_set_active (
-      GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
-
-  empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
-    volume);
-}
-
-static void
-empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
-  gdouble level, EmpathyCallWindow *window)
-{
-  gdouble value;
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  value = CLAMP (pow (10, level / 20), 0.0, 1.0);
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
-      value);
-}
-
-static GtkWidget *
-empathy_call_window_create_audio_input (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GtkWidget *hbox, *vbox, *label;
-
-  hbox = gtk_hbox_new (TRUE, 3);
-
-  vbox = gtk_vbox_new (FALSE, 3);
-  gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
-
-  priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
-  gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
-  label = gtk_label_new (_("Volume"));
-
-  priv->audio_input_adj = gtk_range_get_adjustment (
-    GTK_RANGE (priv->volume_scale));
-  priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
-    (priv->audio_input));
-  gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
-
-  g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
-    G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
-
-  gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
-  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
-
-  priv->volume_progress_bar = gtk_progress_bar_new ();
-
-  gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar),
-      GTK_ORIENTATION_VERTICAL);
-
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
-      0);
-
-  gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
-      3);
-
-  return hbox;
-}
-
-static void
-create_video_output_widget (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GstBus *bus;
-
-  g_assert (priv->video_output == NULL);
-  g_assert (priv->pipeline != NULL);
-
-  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
-  priv->video_output = empathy_video_widget_new (bus);
-
-  gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
-      priv->video_output, TRUE, TRUE, 0);
-
-  gtk_widget_add_events (priv->video_output,
-      GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
-  g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
-      G_CALLBACK (empathy_call_window_video_button_press_cb), self);
-
-  g_object_unref (bus);
-}
-
-static void
-create_audio_output (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  g_assert (priv->audio_output == NULL);
-  priv->audio_output = empathy_audio_sink_new ();
-  gst_object_ref (priv->audio_output);
-  gst_object_sink (priv->audio_output);
-}
-
-static void
-create_video_input (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  g_assert (priv->video_input == NULL);
-  priv->video_input = empathy_video_src_new ();
-  gst_object_ref (priv->video_input);
-  gst_object_sink (priv->video_input);
-}
-
-static void
-create_audio_input (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  g_assert (priv->audio_input == NULL);
-  priv->audio_input = empathy_audio_src_new ();
-  gst_object_ref (priv->audio_input);
-  gst_object_sink (priv->audio_input);
-
-  tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
-    G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
-    self, 0);
-}
-
-static void
-add_video_preview_to_pipeline (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GstElement *preview;
-
-  g_assert (priv->video_preview != NULL);
-  g_assert (priv->pipeline != NULL);
-  g_assert (priv->video_input != NULL);
-  g_assert (priv->video_tee != NULL);
-
-  preview = empathy_video_widget_get_element (
-      EMPATHY_VIDEO_WIDGET (priv->video_preview));
-
-  if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
-    {
-      g_warning ("Could not add video input to pipeline");
-      return;
-    }
-
-  if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee))
-    {
-      g_warning ("Could not add video tee to pipeline");
-      return;
-    }
-
-  if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
-    {
-      g_warning ("Could not add video preview to pipeline");
-      return;
-    }
-
-  if (!gst_element_link (priv->video_input, priv->video_tee))
-    {
-      g_warning ("Could not link video input to video tee");
-      return;
-    }
-
-  if (!gst_element_link (priv->video_tee, preview))
-    {
-      g_warning ("Could not link video tee to video preview");
-      return;
-    }
-}
-
-static void
-create_video_preview (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GstBus *bus;
-
-  g_assert (priv->video_preview == NULL);
-  g_assert (priv->video_tee == NULL);
-
-  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
-
-  priv->video_preview = empathy_video_widget_new_with_size (bus,
-      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
-  g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
-
-  gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
-      priv->video_preview, TRUE, TRUE, 0);
-
-  priv->video_tee = gst_element_factory_make ("tee", NULL);
-  gst_object_ref (priv->video_tee);
-  gst_object_sink (priv->video_tee);
-
-  g_object_unref (bus);
-}
-
-static void
-play_camera (EmpathyCallWindow *window,
-    gboolean play)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  GstElement *preview;
-  GstState state;
-
-  if (priv->video_preview == NULL)
-    {
-      create_video_preview (window);
-      add_video_preview_to_pipeline (window);
-    }
-
-  if (play)
-    state = GST_STATE_PLAYING;
-  else
-    state = GST_STATE_NULL;
-
-  preview = empathy_video_widget_get_element (
-      EMPATHY_VIDEO_WIDGET (priv->video_preview));
-
-  gst_element_set_state (preview, state);
-  gst_element_set_state (priv->video_input, state);
-  gst_element_set_state (priv->video_tee, state);
-}
-
-static void
-display_video_preview (EmpathyCallWindow *self,
-    gboolean display)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (display)
-    {
-      /* Display the preview and hide the self avatar */
-      DEBUG ("Show video preview");
-
-      play_camera (self, TRUE);
-      gtk_widget_show (priv->video_preview);
-      gtk_widget_hide (priv->self_user_avatar_widget);
-    }
-  else
-    {
-      /* Display the self avatar and hide the preview */
-      DEBUG ("Show self avatar");
-
-      if (priv->video_preview != NULL)
-        {
-          gtk_widget_hide (priv->video_preview);
-          play_camera (self, FALSE);
-        }
-      gtk_widget_show (priv->self_user_avatar_widget);
-    }
-}
-
-static void
-empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  empathy_call_window_status_message (window, _("Connecting…"));
-  priv->call_state = CONNECTING;
-
-  if (priv->outgoing)
-    empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
-        EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
-}
-
-static void
-disable_camera (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (priv->camera_state == CAMERA_STATE_OFF)
-    return;
-
-  DEBUG ("Disable camera");
-
-  display_video_preview (self, FALSE);
-
-  if (priv->camera_state == CAMERA_STATE_ON)
-    empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
-
-  block_camera_control_signals (self);
-  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
-        priv->tool_button_camera_on), FALSE);
-  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
-      priv->tool_button_camera_preview), FALSE);
-
-  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
-      priv->tool_button_camera_off), TRUE);
-  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
-      CAMERA_STATE_OFF);
-  unblock_camera_control_signals (self);
-
-  priv->camera_state = CAMERA_STATE_OFF;
-}
-
-static void
-tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
-  EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (!gtk_toggle_tool_button_get_active (toggle))
-    {
-      if (priv->camera_state == CAMERA_STATE_OFF)
-        {
-          /* We can't change the state by disabling the button */
-          block_camera_control_signals (self);
-          gtk_toggle_tool_button_set_active (toggle, TRUE);
-          unblock_camera_control_signals (self);
-        }
-
-      return;
-    }
-
-  disable_camera (self);
-}
-
-static void
-enable_preview (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (priv->camera_state == CAMERA_STATE_PREVIEW)
-    return;
-
-  DEBUG ("Enable preview");
-
-  if (priv->camera_state == CAMERA_STATE_ON)
-    {
-      /* preview is already displayed so we just have to stop sending */
-      empathy_call_window_set_send_video (self, CAMERA_STATE_PREVIEW);
-    }
-  else
-    {
-      display_video_preview (self, TRUE);
-    }
-
-  block_camera_control_signals (self);
-  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
-      priv->tool_button_camera_off), FALSE);
-  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
-        priv->tool_button_camera_on), FALSE);
-
-  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
-        priv->tool_button_camera_preview), TRUE);
-  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
-      CAMERA_STATE_PREVIEW);
-  unblock_camera_control_signals (self);
-
-  priv->camera_state = CAMERA_STATE_PREVIEW;
-}
-
-static void
-tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
-  EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (!gtk_toggle_tool_button_get_active (toggle))
-    {
-      if (priv->camera_state == CAMERA_STATE_PREVIEW)
-        {
-          /* We can't change the state by disabling the button */
-          block_camera_control_signals (self);
-          gtk_toggle_tool_button_set_active (toggle, TRUE);
-          unblock_camera_control_signals (self);
-        }
-
-      return;
-    }
-
-  enable_preview (self);
-}
-
-static void
-enable_camera (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (priv->camera_state == CAMERA_STATE_ON)
-    return;
-
-  if (priv->video_input == NULL)
-    {
-      DEBUG ("Can't enable camera, no input");
-      return;
-    }
-
-
-  DEBUG ("Enable camera");
-
-  empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
-
-  block_camera_control_signals (self);
-  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
-      priv->tool_button_camera_off), FALSE);
-  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
-        priv->tool_button_camera_preview), FALSE);
-
-  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
-      priv->tool_button_camera_on), TRUE);
-  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
-      CAMERA_STATE_ON);
-  unblock_camera_control_signals (self);
-
-  priv->camera_state = CAMERA_STATE_ON;
-}
-
-static void
-tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
-  EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (!gtk_toggle_tool_button_get_active (toggle))
-    {
-      if (priv->camera_state == CAMERA_STATE_ON)
-        {
-          /* We can't change the state by disabling the button */
-          block_camera_control_signals (self);
-          gtk_toggle_tool_button_set_active (toggle, TRUE);
-          unblock_camera_control_signals (self);
-        }
-
-      return;
-    }
-
-  enable_camera (self);
-}
-
-static void
-action_camera_change_cb (GtkRadioAction *action,
-    GtkRadioAction *current,
-    EmpathyCallWindow *self)
-{
-  CameraState state;
-
-  state = gtk_radio_action_get_current_value (current);
-
-  switch (state)
-    {
-      case CAMERA_STATE_OFF:
-        disable_camera (self);
-        break;
-
-      case CAMERA_STATE_PREVIEW:
-        enable_preview (self);
-        break;
-
-      case CAMERA_STATE_ON:
-        enable_camera (self);
-        break;
-
-      default:
-        g_assert_not_reached ();
-    }
-}
-
-static void
-create_pipeline (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GstBus *bus;
-
-  g_assert (priv->pipeline == NULL);
-
-  priv->pipeline = gst_pipeline_new (NULL);
-  priv->pipeline_playing = FALSE;
-
-  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
-  priv->bus_message_source_id = gst_bus_add_watch (bus,
-      empathy_call_window_bus_message, self);
-
-  g_object_unref (bus);
-}
-
-
-static void
-empathy_call_window_init (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GtkBuilder *gui;
-  GtkWidget *top_vbox;
-  GtkWidget *h;
-  GtkWidget *arrow;
-  GtkWidget *page;
-  gchar *filename;
-  GKeyFile *keyfile;
-  GError *error = NULL;
-
-  filename = empathy_file_lookup ("empathy-call-window.ui", "src");
-  gui = empathy_builder_get_file (filename,
-    "call_window_vbox", &top_vbox,
-    "errors_vbox", &priv->errors_vbox,
-    "pane", &priv->pane,
-    "statusbar", &priv->statusbar,
-    "redial", &priv->redial_button,
-    "microphone", &priv->mic_button,
-    "toolbar", &priv->toolbar,
-    "menuredial", &priv->redial,
-    "ui_manager", &priv->ui_manager,
-    "menufullscreen", &priv->menu_fullscreen,
-    "camera_off", &priv->tool_button_camera_off,
-    "camera_preview", &priv->tool_button_camera_preview,
-    "camera_on", &priv->tool_button_camera_on,
-    "action_camera_on",  &priv->action_camera_on,
-    "details_vbox",  &priv->details_vbox,
-    "vcodec_encoding_label", &priv->vcodec_encoding_label,
-    "acodec_encoding_label", &priv->acodec_encoding_label,
-    "acodec_decoding_label", &priv->acodec_decoding_label,
-    "vcodec_decoding_label", &priv->vcodec_decoding_label,
-    "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
-    "audio_local_candidate_label", &priv->audio_local_candidate_label,
-    "video_remote_candidate_label", &priv->video_remote_candidate_label,
-    "video_local_candidate_label", &priv->video_local_candidate_label,
-    "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
-    "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
-    "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
-    "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
-    NULL);
-  g_free (filename);
-
-  empathy_builder_connect (gui, self,
-    "menuhangup", "activate", empathy_call_window_hangup_cb,
-    "hangup", "clicked", empathy_call_window_hangup_cb,
-    "menuredial", "activate", empathy_call_window_redial_cb,
-    "redial", "clicked", empathy_call_window_redial_cb,
-    "microphone", "toggled", empathy_call_window_mic_toggled_cb,
-    "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
-    "camera_off", "toggled", tool_button_camera_off_toggled_cb,
-    "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
-    "camera_on", "toggled", tool_button_camera_on_toggled_cb,
-    "action_camera_on", "changed", action_camera_change_cb,
-    NULL);
-
-  gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
-
-  priv->lock = g_mutex_new ();
-
-  gtk_container_add (GTK_CONTAINER (self), top_vbox);
-
-  priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
-  gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
-                                  CONTENT_HBOX_BORDER_WIDTH);
-  gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
-
-  /* remote user output frame */
-  priv->remote_user_output_frame = gtk_frame_new (NULL);
-  gtk_widget_set_size_request (priv->remote_user_output_frame,
-      EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
-  gtk_box_pack_start (GTK_BOX (priv->content_hbox),
-      priv->remote_user_output_frame, TRUE, TRUE,
-      CONTENT_HBOX_CHILDREN_PACKING_PADDING);
-
-  priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
-
-  priv->remote_user_avatar_widget = gtk_image_new ();
-
-  gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
-      priv->remote_user_avatar_widget, TRUE, TRUE, 0);
-
-  gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
-      priv->remote_user_output_hbox);
-
-  /* self user output frame */
-  priv->self_user_output_frame = gtk_frame_new (NULL);
-  gtk_widget_set_size_request (priv->self_user_output_frame,
-      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
-
-  priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
-
-  priv->self_user_avatar_widget = gtk_image_new ();
-  gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
-      priv->self_user_avatar_widget, TRUE, TRUE, 0);
-
-  gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
-      priv->self_user_output_hbox);
-
-  create_pipeline (self);
-  create_video_output_widget (self);
-  create_audio_input (self);
-  create_audio_output (self);
-  create_video_input (self);
-
-  priv->fsnotifier = fs_element_added_notifier_new ();
-  fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
-
-  /* The call will be started as soon the pipeline is playing */
-  priv->start_call_when_playing = TRUE;
-
-  keyfile = g_key_file_new ();
-  filename = empathy_file_lookup ("element-properties", "data");
-  if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
-    {
-      fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
-          keyfile);
-    }
-  else
-    {
-      g_warning ("Could not load element-properties file: %s", error->message);
-      g_key_file_free (keyfile);
-      g_clear_error (&error);
-    }
-  g_free (filename);
-
-  priv->vbox = gtk_vbox_new (FALSE, 3);
-  gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
-      FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
-  gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
-      FALSE, FALSE, 0);
-
-  empathy_call_window_setup_toolbar (self);
-
-  priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
-  arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
-  g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
-    G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
-
-  gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
-
-  h = gtk_hbox_new (FALSE, 3);
-  gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
-  gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
-
-  priv->sidebar = ev_sidebar_new ();
-  g_signal_connect (G_OBJECT (priv->sidebar),
-    "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
-  g_signal_connect (G_OBJECT (priv->sidebar),
-    "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
-  gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
-
-  page = empathy_call_window_create_audio_input (self);
-  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input",
-      _("Audio input"), page);
-
-  page = empathy_call_window_create_video_input (self);
-  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input",
-      _("Video input"), page);
-
-  priv->dtmf_panel = empathy_call_window_create_dtmf (self);
-  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
-      _("Dialpad"), priv->dtmf_panel);
-
-  gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
-
-  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details",
-      _("Details"), priv->details_vbox);
-
-  gtk_widget_show_all (top_vbox);
-
-  gtk_widget_hide (priv->sidebar);
-
-  priv->fullscreen = empathy_call_window_fullscreen_new (self);
-  empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
-      priv->video_output);
-  g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
-      "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
-
-  g_signal_connect (G_OBJECT (self), "realize",
-    G_CALLBACK (empathy_call_window_realized_cb), self);
-
-  g_signal_connect (G_OBJECT (self), "delete-event",
-    G_CALLBACK (empathy_call_window_delete_cb), self);
-
-  g_signal_connect (G_OBJECT (self), "window-state-event",
-    G_CALLBACK (empathy_call_window_state_event_cb), self);
-
-  g_signal_connect (G_OBJECT (self), "key-press-event",
-      G_CALLBACK (empathy_call_window_key_press_cb), self);
-
-  priv->timer = g_timer_new ();
-
-  g_object_ref (priv->ui_manager);
-  g_object_unref (gui);
-
-  priv->sound_mgr = empathy_sound_manager_dup_singleton ();
-
-  empathy_geometry_bind (GTK_WINDOW (self), "call-window");
-}
-
-/* Instead of specifying a width and a height, we specify only one size. That's
-   because we want a square avatar icon.  */
-static void
-init_contact_avatar_with_size (EmpathyContact *contact,
-    GtkWidget *image_widget,
-    gint size)
-{
-  GdkPixbuf *pixbuf_avatar = NULL;
-
-  if (contact != NULL)
-    {
-      pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
-        size, size);
-    }
-
-  if (pixbuf_avatar == NULL)
-    {
-      pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
-          EMPATHY_IMAGE_AVATAR_DEFAULT, size);
-    }
-
-  gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
-
-  if (pixbuf_avatar != NULL)
-    g_object_unref (pixbuf_avatar);
-}
-
-static void
-set_window_title (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  gchar *tmp;
-
-  /* translators: Call is a noun and %s is the contact name. This string
-   * is used in the window title */
-  tmp = g_strdup_printf (_("Call with %s"),
-      empathy_contact_get_alias (priv->contact));
-  gtk_window_set_title (GTK_WINDOW (self), tmp);
-  g_free (tmp);
-}
-
-static void
-contact_name_changed_cb (EmpathyContact *contact,
-    GParamSpec *pspec, EmpathyCallWindow *self)
-{
-  set_window_title (self);
-}
-
-static void
-contact_avatar_changed_cb (EmpathyContact *contact,
-    GParamSpec *pspec, GtkWidget *avatar_widget)
-{
-  int size;
-  GtkAllocation allocation;
-
-  gtk_widget_get_allocation (avatar_widget, &allocation);
-  size = allocation.height;
-
-  if (size == 0)
-    {
-      /* the widget is not allocated yet, set a default size */
-      size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
-          REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
-    }
-
-  init_contact_avatar_with_size (contact, avatar_widget, size);
-}
-
-static void
-empathy_call_window_got_self_contact_cb (TpConnection *connection,
-    EmpathyContact *contact, const GError *error, gpointer user_data,
-    GObject *weak_object)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
-      MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
-
-  g_signal_connect (contact, "notify::avatar",
-      G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
-}
-
-static void
-empathy_call_window_setup_avatars (EmpathyCallWindow *self,
-    EmpathyStreamedMediaHandler *handler)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  g_object_get (handler, "contact", &(priv->contact), NULL);
-
-  if (priv->contact != NULL)
-    {
-      TpConnection *connection;
-
-      set_window_title (self);
-
-      g_signal_connect (priv->contact, "notify::name",
-          G_CALLBACK (contact_name_changed_cb), self);
-      g_signal_connect (priv->contact, "notify::avatar",
-          G_CALLBACK (contact_avatar_changed_cb),
-          priv->remote_user_avatar_widget);
-
-      /* Retreiving the self avatar */
-      connection = empathy_contact_get_connection (priv->contact);
-      empathy_tp_contact_factory_get_from_handle (connection,
-          tp_connection_get_self_handle (connection),
-          empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
-    }
-  else
-    {
-      g_warning ("call handler doesn't have a contact");
-      /* translators: Call is a noun. This string is used in the window
-       * title */
-      gtk_window_set_title (GTK_WINDOW (self), _("Call"));
-
-      /* Since we can't access the remote contact, we can't get a connection
-         to it and can't get the self contact (and its avatar). This means
-         that we have to manually set the self avatar. */
-      init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
-          MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
-    }
-
-  init_contact_avatar_with_size (priv->contact,
-      priv->remote_user_avatar_widget,
-      MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
-          REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
-
-  /* The remote avatar is shown by default and will be hidden when we receive
-     video from the remote side. */
-  gtk_widget_hide (priv->video_output);
-  gtk_widget_show (priv->remote_user_avatar_widget);
-}
-
-static void
-update_send_codec (EmpathyCallWindow *self,
-    gboolean audio)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  FsCodec *codec;
-  GtkWidget *widget;
-  gchar *tmp;
-
-  if (audio)
-    {
-      codec = empathy_streamed_media_handler_get_send_audio_codec (priv->handler);
-      widget = priv->acodec_encoding_label;
-    }
-  else
-    {
-      codec = empathy_streamed_media_handler_get_send_video_codec (priv->handler);
-      widget = priv->vcodec_encoding_label;
-    }
-
-  if (codec == NULL)
-    return;
-
-  tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
-  gtk_label_set_text (GTK_LABEL (widget), tmp);
-  g_free (tmp);
-}
-
-static void
-send_audio_codec_notify_cb (GObject *object,
-    GParamSpec *pspec,
-    gpointer user_data)
-{
-  EmpathyCallWindow *self = user_data;
-
-  update_send_codec (self, TRUE);
-}
-
-static void
-send_video_codec_notify_cb (GObject *object,
-    GParamSpec *pspec,
-    gpointer user_data)
-{
-  EmpathyCallWindow *self = user_data;
-
-  update_send_codec (self, FALSE);
-}
-
-static void
-update_recv_codec (EmpathyCallWindow *self,
-    gboolean audio)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GList *codecs, *l;
-  GtkWidget *widget;
-  GString *str = NULL;
-
-  if (audio)
-    {
-      codecs = empathy_streamed_media_handler_get_recv_audio_codecs (priv->handler);
-      widget = priv->acodec_decoding_label;
-    }
-  else
-    {
-      codecs = empathy_streamed_media_handler_get_recv_video_codecs (priv->handler);
-      widget = priv->vcodec_decoding_label;
-    }
-
-  if (codecs == NULL)
-    return;
-
-  for (l = codecs; l != NULL; l = g_list_next (l))
-    {
-      FsCodec *codec = l->data;
-
-      if (str == NULL)
-        str = g_string_new (NULL);
-      else
-        g_string_append (str, ", ");
-
-      g_string_append_printf (str, "%s/%u", codec->encoding_name,
-          codec->clock_rate);
-    }
-
-  gtk_label_set_text (GTK_LABEL (widget), str->str);
-  g_string_free (str, TRUE);
-}
-
-static void
-recv_audio_codecs_notify_cb (GObject *object,
-    GParamSpec *pspec,
-    gpointer user_data)
-{
-  EmpathyCallWindow *self = user_data;
-
-  update_recv_codec (self, TRUE);
-}
-
-static void
-recv_video_codecs_notify_cb (GObject *object,
-    GParamSpec *pspec,
-    gpointer user_data)
-{
-  EmpathyCallWindow *self = user_data;
-
-  update_recv_codec (self, FALSE);
-}
-
-static const gchar *
-candidate_type_to_str (FsCandidate *candidate)
-{
-  switch (candidate->type)
-    {
-      case FS_CANDIDATE_TYPE_HOST:
-        return "host";
-      case FS_CANDIDATE_TYPE_SRFLX:
-        return "server reflexive";
-      case FS_CANDIDATE_TYPE_PRFLX:
-        return "peer reflexive";
-      case FS_CANDIDATE_TYPE_RELAY:
-        return "relay";
-      case FS_CANDIDATE_TYPE_MULTICAST:
-        return "multicast";
-    }
-
-  return NULL;
-}
-
-static const gchar *
-candidate_type_to_desc (FsCandidate *candidate)
-{
-  switch (candidate->type)
-    {
-      case FS_CANDIDATE_TYPE_HOST:
-        return _("The IP address as seen by the machine");
-      case FS_CANDIDATE_TYPE_SRFLX:
-        return _("The IP address as seen by a server on the Internet");
-      case FS_CANDIDATE_TYPE_PRFLX:
-        return _("The IP address of the peer as seen by the other side");
-      case FS_CANDIDATE_TYPE_RELAY:
-        return _("The IP address of a relay server");
-      case FS_CANDIDATE_TYPE_MULTICAST:
-        return _("The IP address of the multicast group");
-    }
-
-  return NULL;
-}
-
-static void
-update_candidat_widget (EmpathyCallWindow *self,
-    GtkWidget *label,
-    GtkWidget *img,
-    FsCandidate *candidate)
-{
-  gchar *str;
-
-  g_assert (candidate != NULL);
-  str = g_strdup_printf ("%s %u (%s)", candidate->ip,
-      candidate->port, candidate_type_to_str (candidate));
-
-  gtk_label_set_text (GTK_LABEL (label), str);
-  gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
-
-  g_free (str);
-}
-
-static void
-candidates_changed_cb (GObject *object,
-    FsMediaType type,
-    EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  FsCandidate *candidate = NULL;
-
-  if (type == FS_MEDIA_TYPE_VIDEO)
-    {
-      /* Update remote candidate */
-      candidate = empathy_streamed_media_handler_get_video_remote_candidate (
-          priv->handler);
-
-      update_candidat_widget (self, priv->video_remote_candidate_label,
-          priv->video_remote_candidate_info_img, candidate);
-
-      /* Update local candidate */
-      candidate = empathy_streamed_media_handler_get_video_local_candidate (
-          priv->handler);
-
-      update_candidat_widget (self, priv->video_local_candidate_label,
-          priv->video_local_candidate_info_img, candidate);
-    }
-  else
-    {
-      /* Update remote candidate */
-      candidate = empathy_streamed_media_handler_get_audio_remote_candidate (
-          priv->handler);
-
-      update_candidat_widget (self, priv->audio_remote_candidate_label,
-          priv->audio_remote_candidate_info_img, candidate);
-
-      /* Update local candidate */
-      candidate = empathy_streamed_media_handler_get_audio_local_candidate (
-          priv->handler);
-
-      update_candidat_widget (self, priv->audio_local_candidate_label,
-          priv->audio_local_candidate_info_img, candidate);
-    }
-}
-
-static void
-empathy_call_window_constructed (GObject *object)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  EmpathyTpStreamedMedia *call;
-
-  g_assert (priv->handler != NULL);
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-  priv->outgoing = (call == NULL);
-  if (call != NULL)
-    g_object_unref (call);
-
-  empathy_call_window_setup_avatars (self, priv->handler);
-  empathy_call_window_set_state_connecting (self);
-
-  if (!empathy_streamed_media_handler_has_initial_video (priv->handler))
-    {
-      gtk_toggle_tool_button_set_active (
-          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
-    }
-  /* If call has InitialVideo, the preview will be started once the call has
-   * been started (start_call()). */
-
-  update_send_codec (self, TRUE);
-  update_send_codec (self, FALSE);
-  update_recv_codec (self, TRUE);
-  update_recv_codec (self, FALSE);
-
-  tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
-      G_CALLBACK (send_audio_codec_notify_cb), self, 0);
-  tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
-      G_CALLBACK (send_video_codec_notify_cb), self, 0);
-  tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
-      G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
-  tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
-      G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
-
-  tp_g_signal_connect_object (priv->handler, "candidates-changed",
-      G_CALLBACK (candidates_changed_cb), self, 0);
-}
-
-static void empathy_call_window_dispose (GObject *object);
-static void empathy_call_window_finalize (GObject *object);
-
-static void
-empathy_call_window_set_property (GObject *object,
-  guint property_id, const GValue *value, GParamSpec *pspec)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (object);
-
-  switch (property_id)
-    {
-      case PROP_STREAMED_MEDIA_HANDLER:
-        priv->handler = g_value_dup_object (value);
-        break;
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-    }
-}
-
-static void
-empathy_call_window_get_property (GObject *object,
-  guint property_id, GValue *value, GParamSpec *pspec)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (object);
-
-  switch (property_id)
-    {
-      case PROP_STREAMED_MEDIA_HANDLER:
-        g_value_set_object (value, priv->handler);
-        break;
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-    }
-}
-
-static void
-empathy_call_window_class_init (
-  EmpathyCallWindowClass *empathy_call_window_class)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
-  GParamSpec *param_spec;
-
-  g_type_class_add_private (empathy_call_window_class,
-    sizeof (EmpathyCallWindowPriv));
-
-  object_class->constructed = empathy_call_window_constructed;
-  object_class->set_property = empathy_call_window_set_property;
-  object_class->get_property = empathy_call_window_get_property;
-
-  object_class->dispose = empathy_call_window_dispose;
-  object_class->finalize = empathy_call_window_finalize;
-
-  param_spec = g_param_spec_object ("handler",
-    "handler", "The call handler",
-    EMPATHY_TYPE_STREAMED_MEDIA_HANDLER,
-    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-  g_object_class_install_property (object_class,
-    PROP_STREAMED_MEDIA_HANDLER, param_spec);
-}
-
-static void
-empathy_call_window_video_stream_changed_cb (EmpathyTpStreamedMedia *call,
-    GParamSpec *property, EmpathyCallWindow *self)
-{
-  DEBUG ("video stream changed");
-  empathy_call_window_update_avatars_visibility (call, self);
-}
-
-void
-empathy_call_window_dispose (GObject *object)
-{
-  EmpathyTpStreamedMedia *call;
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (priv->dispose_has_run)
-    return;
-
-  priv->dispose_has_run = TRUE;
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-
-  if (call != NULL)
-    {
-      g_object_unref (call);
-    }
-
-  if (priv->handler != NULL)
-    {
-      empathy_streamed_media_handler_stop_call (priv->handler);
-      g_object_unref (priv->handler);
-    }
-  priv->handler = NULL;
-
-  if (priv->bus_message_source_id != 0)
-    {
-      g_source_remove (priv->bus_message_source_id);
-      priv->bus_message_source_id = 0;
-    }
-
-  if (priv->pipeline != NULL)
-    g_object_unref (priv->pipeline);
-  priv->pipeline = NULL;
-
-  if (priv->video_input != NULL)
-    g_object_unref (priv->video_input);
-  priv->video_input = NULL;
-
-  if (priv->audio_input != NULL)
-    g_object_unref (priv->audio_input);
-  priv->audio_input = NULL;
-
-  if (priv->audio_output != NULL)
-    g_object_unref (priv->audio_output);
-  priv->audio_output = NULL;
-
-  if (priv->video_tee != NULL)
-    g_object_unref (priv->video_tee);
-  priv->video_tee = NULL;
-
-  if (priv->liveadder != NULL)
-    gst_object_unref (priv->liveadder);
-  priv->liveadder = NULL;
-
-  if (priv->fsnotifier != NULL)
-    g_object_unref (priv->fsnotifier);
-  priv->fsnotifier = NULL;
-
-  if (priv->timer_id != 0)
-    g_source_remove (priv->timer_id);
-  priv->timer_id = 0;
-
-  if (priv->ui_manager != NULL)
-    g_object_unref (priv->ui_manager);
-  priv->ui_manager = NULL;
-
-  if (priv->fullscreen != NULL)
-    g_object_unref (priv->fullscreen);
-  priv->fullscreen = NULL;
-
-  if (priv->contact != NULL)
-    {
-      g_signal_handlers_disconnect_by_func (priv->contact,
-          contact_name_changed_cb, self);
-      g_object_unref (priv->contact);
-      priv->contact = NULL;
-    }
-
-  tp_clear_object (&priv->sound_mgr);
-
-  /* release any references held by the object here */
-  if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
-    G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
-}
-
-static void
-disconnect_video_output_motion_handler (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (priv->video_output_motion_handler_id != 0)
-    {
-      g_signal_handler_disconnect (G_OBJECT (priv->video_output),
-          priv->video_output_motion_handler_id);
-      priv->video_output_motion_handler_id = 0;
-    }
-}
-
-void
-empathy_call_window_finalize (GObject *object)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  disconnect_video_output_motion_handler (self);
-
-  /* free any data held directly by the object here */
-  g_mutex_free (priv->lock);
-
-  g_timer_destroy (priv->timer);
-
-  G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
-}
-
-
-EmpathyCallWindow *
-empathy_call_window_new (EmpathyStreamedMediaHandler *handler)
-{
-  return EMPATHY_CALL_WINDOW (
-    g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
-}
-
-static void
-empathy_call_window_conference_added_cb (EmpathyStreamedMediaHandler *handler,
-  GstElement *conference, gpointer user_data)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  gst_bin_add (GST_BIN (priv->pipeline), conference);
-
-  gst_element_set_state (conference, GST_STATE_PLAYING);
-}
-
-static gboolean
-empathy_call_window_request_resource_cb (EmpathyStreamedMediaHandler *handler,
-  FsMediaType type, FsStreamDirection direction, gpointer user_data)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (type != FS_MEDIA_TYPE_VIDEO)
-    return TRUE;
-
-  if (direction == FS_DIRECTION_RECV)
-    return TRUE;
-
-  /* video and direction is send */
-  return priv->video_input != NULL;
-}
-
-static gboolean
-empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
-{
-  GstStateChangeReturn state_change_return;
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (priv->pipeline == NULL)
-    return TRUE;
-
-  if (priv->bus_message_source_id != 0)
-    {
-      g_source_remove (priv->bus_message_source_id);
-      priv->bus_message_source_id = 0;
-    }
-
-  state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
-
-  if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
-        state_change_return == GST_STATE_CHANGE_NO_PREROLL)
-    {
-      if (priv->pipeline != NULL)
-        g_object_unref (priv->pipeline);
-      priv->pipeline = NULL;
-
-      g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
-          empathy_call_window_mic_volume_changed_cb, self);
-
-      if (priv->video_tee != NULL)
-        g_object_unref (priv->video_tee);
-      priv->video_tee = NULL;
-
-      if (priv->video_preview != NULL)
-        gtk_widget_destroy (priv->video_preview);
-      priv->video_preview = NULL;
-
-      priv->liveadder = NULL;
-      priv->funnel = NULL;
-
-      create_pipeline (self);
-      /* Call will be started when user will hit the 'redial' button */
-      priv->start_call_when_playing = FALSE;
-      gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
-
-      return TRUE;
-    }
-  else
-    {
-      g_message ("Error: could not destroy pipeline. Closing call window");
-      gtk_widget_destroy (GTK_WIDGET (self));
-
-      return FALSE;
-    }
-}
-
-static void
-reset_details_pane (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
-  gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
-  gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
-  gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
-}
-
-static gboolean
-empathy_call_window_disconnected (EmpathyCallWindow *self,
-    gboolean restart)
-{
-  gboolean could_disconnect = FALSE;
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  gboolean could_reset_pipeline;
-
-  /* Leave full screen mode if needed */
-  gtk_window_unfullscreen (GTK_WINDOW (self));
-
-  gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
-
-  could_reset_pipeline = empathy_call_window_reset_pipeline (self);
-
-  if (priv->call_state == CONNECTING)
-      empathy_sound_manager_stop (priv->sound_mgr,
-          EMPATHY_SOUND_PHONE_OUTGOING);
-
-  if (priv->call_state != REDIALING)
-    priv->call_state = DISCONNECTED;
-
-  if (could_reset_pipeline)
-    {
-      g_mutex_lock (priv->lock);
-
-      g_timer_stop (priv->timer);
-
-      if (priv->timer_id != 0)
-        g_source_remove (priv->timer_id);
-      priv->timer_id = 0;
-
-      g_mutex_unlock (priv->lock);
-
-      if (!restart)
-        /* We are about to destroy the window, no need to update it or create
-         * a video preview */
-        return TRUE;
-
-      empathy_call_window_status_message (self, _("Disconnected"));
-
-      gtk_action_set_sensitive (priv->redial, TRUE);
-      gtk_widget_set_sensitive (priv->redial_button, TRUE);
-
-      /* Unsensitive the camera and mic button */
-      gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
-      gtk_action_set_sensitive (priv->action_camera_on, FALSE);
-      gtk_widget_set_sensitive (priv->mic_button, FALSE);
-
-      /* Be sure that the mic button is enabled */
-      gtk_toggle_tool_button_set_active (
-          GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
-
-      if (priv->camera_state == CAMERA_STATE_ON)
-        {
-          /* Enable the 'preview' button as we are not sending atm. */
-          gtk_toggle_tool_button_set_active (
-              GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_preview), TRUE);
-        }
-      else if (priv->camera_state == CAMERA_STATE_PREVIEW)
-        {
-          /* Restart the preview with the new pipeline. */
-          display_video_preview (self, TRUE);
-        }
-
-      gtk_progress_bar_set_fraction (
-          GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
-
-      /* destroy the video output; it will be recreated when we'll redial */
-      disconnect_video_output_motion_handler (self);
-      gtk_widget_destroy (priv->video_output);
-      priv->video_output = NULL;
-
-      gtk_widget_show (priv->remote_user_avatar_widget);
-
-      reset_details_pane (self);
-
-      priv->sending_video = FALSE;
-      priv->call_started = FALSE;
-
-      could_disconnect = TRUE;
-
-      /* TODO: display the self avatar of the preview (depends if the "Always
-       * Show Video Preview" is enabled or not) */
-    }
-
-  return could_disconnect;
-}
-
-
-static void
-empathy_call_window_channel_closed_cb (EmpathyStreamedMediaHandler *handler,
-    gpointer user_data)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  if (empathy_call_window_disconnected (self, TRUE) &&
-      priv->call_state == REDIALING)
-      empathy_call_window_restart_call (self);
-}
-
-
-static void
-empathy_call_window_channel_stream_closed_cb (EmpathyStreamedMediaHandler *handler,
-    TfStream *stream, gpointer user_data)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  guint media_type;
-
-  g_object_get (stream, "media-type", &media_type, NULL);
-
-  /*
-   * This assumes that there is only one video stream per channel...
-   */
-
-  if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
-    {
-      if (priv->funnel != NULL)
-        {
-          GstElement *output;
-
-          output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
-              (priv->video_output));
-
-          gst_element_set_state (output, GST_STATE_NULL);
-          gst_element_set_state (priv->funnel, GST_STATE_NULL);
-
-          gst_bin_remove (GST_BIN (priv->pipeline), output);
-          gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
-          priv->funnel = NULL;
-        }
-    }
-  else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
-    {
-      if (priv->liveadder != NULL)
-        {
-          gst_element_set_state (priv->audio_output, GST_STATE_NULL);
-          gst_element_set_state (priv->liveadder, GST_STATE_NULL);
-
-          gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
-          gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
-          priv->liveadder = NULL;
-        }
-    }
-}
-
-/* Called with global lock held */
-static GstPad *
-empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GstPad *pad;
-  GstElement *output;
-
-  if (priv->funnel == NULL)
-    {
-      output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
-        (priv->video_output));
-
-      priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
-
-      if (!priv->funnel)
-        {
-          g_warning ("Could not create fsfunnel");
-          return NULL;
-        }
-
-      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
-        {
-          gst_object_unref (priv->funnel);
-          priv->funnel = NULL;
-          g_warning ("Could  not add funnel to pipeline");
-          return NULL;
-        }
-
-      if (!gst_bin_add (GST_BIN (priv->pipeline), output))
-        {
-          g_warning ("Could not add the video output widget to the pipeline");
-          goto error;
-        }
-
-      if (!gst_element_link (priv->funnel, output))
-        {
-          g_warning ("Could not link output sink to funnel");
-          goto error_output_added;
-        }
-
-      if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
-        {
-          g_warning ("Could not start video sink");
-          goto error_output_added;
-        }
-
-      if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
-        {
-          g_warning ("Could not start funnel");
-          goto error_output_added;
-        }
-    }
-
-  pad = gst_element_get_request_pad (priv->funnel, "sink%d");
-
-  if (!pad)
-    g_warning ("Could not get request pad from funnel");
-
-  return pad;
-
-
- error_output_added:
-
-  gst_element_set_locked_state (priv->funnel, TRUE);
-  gst_element_set_locked_state (output, TRUE);
-
-  gst_element_set_state (priv->funnel, GST_STATE_NULL);
-  gst_element_set_state (output, GST_STATE_NULL);
-
-  gst_bin_remove (GST_BIN (priv->pipeline), output);
-  gst_element_set_locked_state (output, FALSE);
-
- error:
-
-  gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
-  priv->funnel = NULL;
-
-  return NULL;
-}
-
-/* Called with global lock held */
-static GstPad *
-empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GstPad *pad;
-  GstElement *filter;
-  GError *gerror = NULL;
-
-  if (priv->liveadder == NULL)
-    {
-      priv->liveadder = gst_element_factory_make ("liveadder", NULL);
-
-      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
-        {
-          g_warning ("Could not add liveadder to the pipeline");
-          goto error_add_liveadder;
-        }
-      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
-        {
-          g_warning ("Could not add audio sink to pipeline");
-          goto error_add_output;
-        }
-
-      if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
-        {
-          g_warning ("Could not start liveadder");
-          goto error;
-        }
-
-      if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
-        {
-          g_warning ("Could not start audio sink");
-          goto error;
-        }
-
-      if (GST_PAD_LINK_FAILED (
-              gst_element_link (priv->liveadder, priv->audio_output)))
-        {
-          g_warning ("Could not link liveadder to audio output");
-          goto error;
-        }
-    }
-
-  filter = gst_parse_bin_from_description (
-      "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
-  if (filter == NULL)
-    {
-      g_warning ("Could not make audio conversion filter: %s", gerror->message);
-      g_clear_error (&gerror);
-      goto error;
-    }
-
-  if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
-    {
-      g_warning ("Could not add audio conversion filter to pipeline");
-      gst_object_unref (filter);
-      goto error;
-    }
-
-  if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
-    {
-      g_warning ("Could not start audio conversion filter");
-      goto error_filter;
-    }
-
-  if (!gst_element_link (filter, priv->liveadder))
-    {
-      g_warning ("Could not link audio conversion filter to liveadder");
-      goto error_filter;
-    }
-
-  pad = gst_element_get_static_pad (filter, "sink");
-
-  if (pad == NULL)
-    {
-      g_warning ("Could not get sink pad from filter");
-      goto error_filter;
-    }
-
-  return pad;
-
- error_filter:
-
-  gst_element_set_locked_state (filter, TRUE);
-  gst_element_set_state (filter, GST_STATE_NULL);
-  gst_bin_remove (GST_BIN (priv->pipeline), filter);
-
- error:
-
-  gst_element_set_locked_state (priv->liveadder, TRUE);
-  gst_element_set_locked_state (priv->audio_output, TRUE);
-
-  gst_element_set_state (priv->liveadder, GST_STATE_NULL);
-  gst_element_set_state (priv->audio_output, GST_STATE_NULL);
-
-  gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
-
- error_add_output:
-
-  gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
-
-  gst_element_set_locked_state (priv->liveadder, FALSE);
-  gst_element_set_locked_state (priv->audio_output, FALSE);
-
- error_add_liveadder:
-
-  if (priv->liveadder != NULL)
-    {
-      gst_object_unref (priv->liveadder);
-      priv->liveadder = NULL;
-    }
-
-  return NULL;
-}
-
-static gboolean
-empathy_call_window_update_timer (gpointer user_data)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  gchar *str;
-  gdouble time_;
-
-  time_ = g_timer_elapsed (priv->timer, NULL);
-
-  /* Translators: number of minutes:seconds the caller has been connected */
-  str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time_ / 60,
-    (int) time_ % 60);
-  empathy_call_window_status_message (self, str);
-  g_free (str);
-
-  return TRUE;
-}
-
-static void
-display_error (EmpathyCallWindow *self,
-    EmpathyTpStreamedMedia *call,
-    const gchar *img,
-    const gchar *title,
-    const gchar *desc,
-    const gchar *details)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GtkWidget *info_bar;
-  GtkWidget *content_area;
-  GtkWidget *hbox;
-  GtkWidget *vbox;
-  GtkWidget *image;
-  GtkWidget *label;
-  gchar *txt;
-
-  /* Create info bar */
-  info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
-      NULL);
-
-  gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
-
-  content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
-
-  /* hbox containing the image and the messages vbox */
-  hbox = gtk_hbox_new (FALSE, 3);
-  gtk_container_add (GTK_CONTAINER (content_area), hbox);
-
-  /* Add image */
-  image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
-  gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
-
-  /* vbox containing the main message and the details expander */
-  vbox = gtk_vbox_new (FALSE, 3);
-  gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
-
-  /* Add text */
-  txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
-
-  label = gtk_label_new (NULL);
-  gtk_label_set_markup (GTK_LABEL (label), txt);
-  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
-  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
-  g_free (txt);
-
-  gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
-
-  /* Add details */
-  if (details != NULL)
-    {
-      GtkWidget *expander;
-
-      expander = gtk_expander_new (_("Technical Details"));
-
-      txt = g_strdup_printf ("<i>%s</i>", details);
-
-      label = gtk_label_new (NULL);
-      gtk_label_set_markup (GTK_LABEL (label), txt);
-      gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
-      gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
-      g_free (txt);
-
-      gtk_container_add (GTK_CONTAINER (expander), label);
-      gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
-    }
-
-  g_signal_connect (info_bar, "response",
-      G_CALLBACK (gtk_widget_destroy), NULL);
-
-  gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
-      FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
-  gtk_widget_show_all (info_bar);
-}
-
-static gchar *
-media_stream_error_to_txt (EmpathyCallWindow *self,
-    EmpathyTpStreamedMedia *call,
-    gboolean audio,
-    TpMediaStreamError error)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  const gchar *cm;
-  gchar *url;
-  gchar *result;
-
-  switch (error)
-    {
-      case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
-        if (audio)
-          return g_strdup_printf (
-              _("%s's software does not understand any of the audio formats "
-                "supported by your computer"),
-            empathy_contact_get_alias (priv->contact));
-        else
-          return g_strdup_printf (
-              _("%s's software does not understand any of the video formats "
-                "supported by your computer"),
-            empathy_contact_get_alias (priv->contact));
-
-      case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
-        return g_strdup_printf (
-            _("Can't establish a connection to %s. "
-              "One of you might be on a network that does not allow "
-              "direct connections."),
-          empathy_contact_get_alias (priv->contact));
-
-      case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
-          return g_strdup (_("There was a failure on the network"));
-
-      case TP_MEDIA_STREAM_ERROR_NO_CODECS:
-        if (audio)
-          return g_strdup (_("The audio formats necessary for this call "
-                "are not installed on your computer"));
-        else
-          return g_strdup (_("The video formats necessary for this call "
-                "are not installed on your computer"));
-
-      case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
-        cm = empathy_tp_streamed_media_get_connection_manager (call);
-
-        url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
-            "product=Telepathy&amp;component=%s", cm);
-
-        result = g_strdup_printf (
-            _("Something unexpected happened in a Telepathy component. "
-              "Please <a href=\"%s\">report this bug</a> and attach "
-              "logs gathered from the 'Debug' window in the Help menu."), url);
-
-        g_free (url);
-        return result;
-
-      case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
-        return g_strdup (_("There was a failure in the call engine"));
-
-      case TP_MEDIA_STREAM_ERROR_EOS:
-        return g_strdup (_("The end of the stream was reached"));
-
-      case TP_MEDIA_STREAM_ERROR_UNKNOWN:
-      default:
-        return NULL;
-    }
-}
-
-static void
-empathy_call_window_stream_error (EmpathyCallWindow *self,
-    EmpathyTpStreamedMedia *call,
-    gboolean audio,
-    guint code,
-    const gchar *msg,
-    const gchar *icon,
-    const gchar *title)
-{
-  gchar *desc;
-
-  desc = media_stream_error_to_txt (self, call, audio, code);
-  if (desc == NULL)
-    {
-      /* No description, use the error message. That's not great as it's not
-       * localized but it's better than nothing. */
-      display_error (self, call, icon, title, msg, NULL);
-    }
-  else
-    {
-      display_error (self, call, icon, title, desc, msg);
-      g_free (desc);
-    }
-}
-
-static void
-empathy_call_window_audio_stream_error (EmpathyTpStreamedMedia *call,
-    guint code,
-    const gchar *msg,
-    EmpathyCallWindow *self)
-{
-  empathy_call_window_stream_error (self, call, TRUE, code, msg,
-      "gnome-stock-mic", _("Can't establish audio stream"));
-}
-
-static void
-empathy_call_window_video_stream_error (EmpathyTpStreamedMedia *call,
-    guint code,
-    const gchar *msg,
-    EmpathyCallWindow *self)
-{
-  empathy_call_window_stream_error (self, call, FALSE, code, msg,
-      "camera-web", _("Can't establish video stream"));
-}
-
-static gboolean
-empathy_call_window_connected (gpointer user_data)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  EmpathyTpStreamedMedia *call;
-  gboolean can_send_video;
-
-  empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
-
-  can_send_video = priv->video_input != NULL && priv->contact != NULL &&
-    empathy_contact_can_voip_video (priv->contact);
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-
-  tp_g_signal_connect_object (call, "notify::video-stream",
-    G_CALLBACK (empathy_call_window_video_stream_changed_cb),
-    self, 0);
-
-  if (empathy_tp_streamed_media_has_dtmf (call))
-    gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
-
-  if (priv->video_input == NULL)
-    empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
-
-  priv->sending_video = can_send_video ?
-    empathy_tp_streamed_media_is_sending_video (call) : FALSE;
-
-  gtk_toggle_tool_button_set_active (
-      GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
-      priv->sending_video && priv->video_input != NULL);
-  gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
-  gtk_action_set_sensitive (priv->action_camera_on, can_send_video);
-
-  gtk_action_set_sensitive (priv->redial, FALSE);
-  gtk_widget_set_sensitive (priv->redial_button, FALSE);
-
-  gtk_widget_set_sensitive (priv->mic_button, TRUE);
-
-  empathy_call_window_update_avatars_visibility (call, self);
-
-  g_object_unref (call);
-
-  g_mutex_lock (priv->lock);
-
-  priv->timer_id = g_timeout_add_seconds (1,
-    empathy_call_window_update_timer, self);
-
-  g_mutex_unlock (priv->lock);
-
-  empathy_call_window_update_timer (self);
-
-  gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
-
-  return FALSE;
-}
-
-
-/* Called from the streaming thread */
-static gboolean
-empathy_call_window_src_added_cb (EmpathyStreamedMediaHandler *handler,
-  GstPad *src, guint media_type, gpointer user_data)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  gboolean retval = FALSE;
-
-  GstPad *pad;
-
-  g_mutex_lock (priv->lock);
-
-  if (priv->call_state != CONNECTED)
-    {
-      g_timer_start (priv->timer);
-      priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
-      priv->call_state = CONNECTED;
-    }
-
-  switch (media_type)
-    {
-      case TP_MEDIA_STREAM_TYPE_AUDIO:
-        pad = empathy_call_window_get_audio_sink_pad (self);
-        break;
-      case TP_MEDIA_STREAM_TYPE_VIDEO:
-        gtk_widget_hide (priv->remote_user_avatar_widget);
-        gtk_widget_show (priv->video_output);
-        pad = empathy_call_window_get_video_sink_pad (self);
-        break;
-      default:
-        g_assert_not_reached ();
-    }
-
-  if (pad == NULL)
-    goto out;
-
-  if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
-      g_warning ("Could not link %s sink pad",
-          media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
-  else
-      retval = TRUE;
-
-  gst_object_unref (pad);
-
- out:
-
-  /* If no sink could be linked, try to add fakesink to prevent the whole call
-   * aborting */
-
-  if (!retval)
-    {
-      GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
-
-      if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
-        {
-          GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
-          if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
-              GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
-            {
-              gst_element_set_locked_state (fakesink, TRUE);
-              gst_element_set_state (fakesink, GST_STATE_NULL);
-              gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
-            }
-          else
-            {
-              g_debug ("Could not link real sink, linked fakesink instead");
-            }
-          gst_object_unref (sinkpad);
-        }
-      else
-        {
-          gst_object_unref (fakesink);
-        }
-    }
-
-
-  g_mutex_unlock (priv->lock);
-
-  return TRUE;
-}
-
-static gboolean
-empathy_call_window_sink_added_cb (EmpathyStreamedMediaHandler *handler,
-  GstPad *sink, guint media_type, gpointer user_data)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GstPad *pad;
-  gboolean retval = FALSE;
-
-  switch (media_type)
-    {
-      case TP_MEDIA_STREAM_TYPE_AUDIO:
-        if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
-          {
-            g_warning ("Could not add audio source to pipeline");
-            break;
-          }
-
-        pad = gst_element_get_static_pad (priv->audio_input, "src");
-        if (!pad)
-          {
-            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
-            g_warning ("Could not get source pad from audio source");
-            break;
-          }
-
-        if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
-          {
-            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
-            g_warning ("Could not link audio source to farsight");
-            break;
-          }
-
-        if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
-          {
-            g_warning ("Could not start audio source");
-            gst_element_set_state (priv->audio_input, GST_STATE_NULL);
-            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
-            break;
-          }
-
-        retval = TRUE;
-        break;
-      case TP_MEDIA_STREAM_TYPE_VIDEO:
-        if (priv->video_input != NULL)
-          {
-            if (priv->video_tee != NULL)
-              {
-                pad = gst_element_get_request_pad (priv->video_tee, "src%d");
-                if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
-                  {
-                    g_warning ("Could not link videp soure input pipeline");
-                    break;
-                  }
-                gst_object_unref (pad);
-              }
-
-            retval = TRUE;
-          }
-        break;
-      default:
-        g_assert_not_reached ();
-    }
-
-  return retval;
-}
-
-static void
-empathy_call_window_remove_video_input (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GstElement *preview;
-
-  disable_camera (self);
-
-  DEBUG ("remove video input");
-  preview = empathy_video_widget_get_element (
-    EMPATHY_VIDEO_WIDGET (priv->video_preview));
-
-  gst_element_set_state (priv->video_input, GST_STATE_NULL);
-  gst_element_set_state (priv->video_tee, GST_STATE_NULL);
-  gst_element_set_state (preview, GST_STATE_NULL);
-
-  gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
-    priv->video_tee, preview, NULL);
-
-  g_object_unref (priv->video_input);
-  priv->video_input = NULL;
-  g_object_unref (priv->video_tee);
-  priv->video_tee = NULL;
-  gtk_widget_destroy (priv->video_preview);
-  priv->video_preview = NULL;
-
-  gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
-  gtk_action_set_sensitive (priv->action_camera_on, FALSE);
-  gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
-}
-
-static void
-start_call (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  priv->call_started = TRUE;
-  empathy_streamed_media_handler_start_call (priv->handler,
-      gtk_get_current_event_time ());
-
-  if (empathy_streamed_media_handler_has_initial_video (priv->handler))
-    {
-      /* Enable 'send video' buttons and display the preview */
-      gtk_toggle_tool_button_set_active (
-          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
-    }
-}
-
-static gboolean
-empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
-  gpointer user_data)
-{
-  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  GstState newstate;
-
-  empathy_streamed_media_handler_bus_message (priv->handler, bus, message);
-
-  switch (GST_MESSAGE_TYPE (message))
-    {
-      case GST_MESSAGE_STATE_CHANGED:
-        if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
-          {
-            gst_message_parse_state_changed (message, NULL, &newstate, NULL);
-            if (newstate == GST_STATE_PAUSED)
-                empathy_call_window_setup_video_input (self);
-          }
-        if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
-            !priv->call_started)
-          {
-            gst_message_parse_state_changed (message, NULL, &newstate, NULL);
-            if (newstate == GST_STATE_PAUSED)
-              {
-                gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
-                priv->pipeline_playing = TRUE;
-
-                if (priv->start_call_when_playing)
-                  start_call (self);
-              }
-          }
-        break;
-      case GST_MESSAGE_ERROR:
-        {
-          GError *error = NULL;
-          GstElement *gst_error;
-          gchar *debug;
-
-          gst_message_parse_error (message, &error, &debug);
-          gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
-
-          g_message ("Element error: %s -- %s\n", error->message, debug);
-
-          if (g_str_has_prefix (gst_element_get_name (gst_error),
-                VIDEO_INPUT_ERROR_PREFIX))
-            {
-              /* Remove the video input and continue */
-              if (priv->video_input != NULL)
-                empathy_call_window_remove_video_input (self);
-              gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
-            }
-          else
-            {
-              empathy_call_window_disconnected (self, TRUE);
-            }
-          g_error_free (error);
-          g_free (debug);
-        }
-      case GST_MESSAGE_UNKNOWN:
-      case GST_MESSAGE_EOS:
-      case GST_MESSAGE_WARNING:
-      case GST_MESSAGE_INFO:
-      case GST_MESSAGE_TAG:
-      case GST_MESSAGE_BUFFERING:
-      case GST_MESSAGE_STATE_DIRTY:
-      case GST_MESSAGE_STEP_DONE:
-      case GST_MESSAGE_CLOCK_PROVIDE:
-      case GST_MESSAGE_CLOCK_LOST:
-      case GST_MESSAGE_NEW_CLOCK:
-      case GST_MESSAGE_STRUCTURE_CHANGE:
-      case GST_MESSAGE_STREAM_STATUS:
-      case GST_MESSAGE_APPLICATION:
-      case GST_MESSAGE_ELEMENT:
-      case GST_MESSAGE_SEGMENT_START:
-      case GST_MESSAGE_SEGMENT_DONE:
-      case GST_MESSAGE_DURATION:
-      case GST_MESSAGE_ANY:
-      default:
-        break;
-    }
-
-  return TRUE;
-}
-
-static void
-empathy_call_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
-    EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (empathy_tp_streamed_media_is_receiving_video (call))
-    {
-      gtk_widget_hide (priv->remote_user_avatar_widget);
-      gtk_widget_show (priv->video_output);
-    }
-  else
-    {
-      gtk_widget_hide (priv->video_output);
-      gtk_widget_show (priv->remote_user_avatar_widget);
-    }
-}
-
-static void
-call_handler_notify_tp_streamed_media_cb (EmpathyStreamedMediaHandler *handler,
-    GParamSpec *spec,
-    EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  EmpathyTpStreamedMedia *call;
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-  if (call == NULL)
-    return;
-
-  tp_g_signal_connect_object (call, "audio-stream-error",
-      G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
-  tp_g_signal_connect_object (call, "video-stream-error",
-      G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
-
-  g_object_unref (call);
-}
-
-static void
-empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  EmpathyTpStreamedMedia *call;
-
-  g_signal_connect (priv->handler, "conference-added",
-    G_CALLBACK (empathy_call_window_conference_added_cb), window);
-  g_signal_connect (priv->handler, "request-resource",
-    G_CALLBACK (empathy_call_window_request_resource_cb), window);
-  g_signal_connect (priv->handler, "closed",
-    G_CALLBACK (empathy_call_window_channel_closed_cb), window);
-  g_signal_connect (priv->handler, "src-pad-added",
-    G_CALLBACK (empathy_call_window_src_added_cb), window);
-  g_signal_connect (priv->handler, "sink-pad-added",
-    G_CALLBACK (empathy_call_window_sink_added_cb), window);
-  g_signal_connect (priv->handler, "stream-closed",
-    G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-  if (call != NULL)
-    {
-      tp_g_signal_connect_object (call, "audio-stream-error",
-        G_CALLBACK (empathy_call_window_audio_stream_error), window,
-        0);
-      tp_g_signal_connect_object (call, "video-stream-error",
-        G_CALLBACK (empathy_call_window_video_stream_error), window,
-        0);
-
-      g_object_unref (call);
-    }
-  else
-    {
-      /* tp-call doesn't exist yet, we'll connect signals once it has been
-       * set */
-      g_signal_connect (priv->handler, "notify::tp-call",
-        G_CALLBACK (call_handler_notify_tp_streamed_media_cb), window);
-    }
-
-  gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
-}
-
-static gboolean
-empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (priv->pipeline != NULL)
-    {
-      if (priv->bus_message_source_id != 0)
-        {
-          g_source_remove (priv->bus_message_source_id);
-          priv->bus_message_source_id = 0;
-        }
-
-      gst_element_set_state (priv->pipeline, GST_STATE_NULL);
-    }
-
-  if (priv->call_state == CONNECTING)
-    empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
-
-  return FALSE;
-}
-
-static void
-show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
-{
-  GtkWidget *menu;
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  menu = gtk_ui_manager_get_widget (priv->ui_manager,
-            "/menubar1");
-
-  if (set_fullscreen)
-    {
-      gtk_widget_hide (priv->sidebar);
-      gtk_widget_hide (menu);
-      gtk_widget_hide (priv->vbox);
-      gtk_widget_hide (priv->statusbar);
-      gtk_widget_hide (priv->toolbar);
-    }
-  else
-    {
-      if (priv->sidebar_was_visible_before_fs)
-        gtk_widget_show (priv->sidebar);
-
-      gtk_widget_show (menu);
-      gtk_widget_show (priv->vbox);
-      gtk_widget_show (priv->statusbar);
-      gtk_widget_show (priv->toolbar);
-
-      gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
-          priv->original_height_before_fs);
-    }
-}
-
-static void
-show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
-      set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
-  gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
-      set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
-
-  if (priv->video_output != NULL)
-    {
-      gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
-          priv->video_output, TRUE, TRUE,
-          set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
-          GTK_PACK_START);
-    }
-
-  gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
-      priv->vbox, TRUE, TRUE,
-      set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
-      GTK_PACK_START);
-}
-
-static gboolean
-empathy_call_window_state_event_cb (GtkWidget *widget,
-  GdkEventWindowState *event, EmpathyCallWindow *window)
-{
-  if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
-    {
-      EmpathyCallWindowPriv *priv = GET_PRIV (window);
-      gboolean set_fullscreen = event->new_window_state &
-        GDK_WINDOW_STATE_FULLSCREEN;
-
-      if (set_fullscreen)
-        {
-          gboolean sidebar_was_visible;
-          GtkAllocation allocation;
-          gint original_width, original_height;
-
-          gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
-          original_width = allocation.width;
-          original_height = allocation.height;
-
-          g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
-
-          priv->sidebar_was_visible_before_fs = sidebar_was_visible;
-          priv->original_width_before_fs = original_width;
-          priv->original_height_before_fs = original_height;
-
-          if (priv->video_output_motion_handler_id == 0 &&
-                priv->video_output != NULL)
-            {
-              priv->video_output_motion_handler_id = g_signal_connect (
-                  G_OBJECT (priv->video_output), "motion-notify-event",
-                  G_CALLBACK (empathy_call_window_video_output_motion_notify),
-                  window);
-            }
-        }
-      else
-        {
-          disconnect_video_output_motion_handler (window);
-        }
-
-      empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
-          set_fullscreen);
-      show_controls (window, set_fullscreen);
-      show_borders (window, set_fullscreen);
-      gtk_action_set_stock_id (priv->menu_fullscreen,
-          (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
-      priv->is_fullscreen = set_fullscreen;
-  }
-
-  return FALSE;
-}
-
-static void
-empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  GtkWidget *arrow;
-  int w, h, handle_size;
-  GtkAllocation allocation, sidebar_allocation;
-
-  gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
-  w = allocation.width;
-  h = allocation.height;
-
-  gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
-
-  gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
-  if (gtk_toggle_button_get_active (toggle))
-    {
-      arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
-      gtk_widget_show (priv->sidebar);
-      w += sidebar_allocation.width + handle_size;
-    }
-  else
-    {
-      arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
-      w -= sidebar_allocation.width + handle_size;
-      gtk_widget_hide (priv->sidebar);
-    }
-
-  gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
-
-  if (w > 0 && h > 0)
-    gtk_window_resize (GTK_WINDOW (window), w, h);
-}
-
-static void
-empathy_call_window_set_send_video (EmpathyCallWindow *window,
-  CameraState state)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  EmpathyTpStreamedMedia *call;
-
-  priv->sending_video = (state == CAMERA_STATE_ON);
-
-  if (state == CAMERA_STATE_PREVIEW ||
-      state == CAMERA_STATE_ON)
-    {
-      /* When we start sending video, we want to show the video preview by
-         default. */
-      display_video_preview (window, TRUE);
-    }
-  else
-    {
-      display_video_preview (window, FALSE);
-    }
-
-  if (priv->call_state != CONNECTED)
-    return;
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-  DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
-  empathy_tp_streamed_media_request_video_stream_direction (call, priv->sending_video);
-  g_object_unref (call);
-}
-
-static void
-empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  gboolean active;
-
-  active = (gtk_toggle_tool_button_get_active (toggle));
-
-  if (active)
-    {
-      empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
-        priv->volume);
-      gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
-    }
-  else
-    {
-      /* TODO, Instead of setting the input volume to 0 we should probably
-       * stop sending but this would cause the audio call to drop if both
-       * sides mute at the same time on certain CMs AFAIK. Need to revisit this
-       * in the future. GNOME #574574
-       */
-      empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
-        0);
-      gtk_adjustment_set_value (priv->audio_input_adj, 0);
-    }
-}
-
-static void
-empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
-    FALSE);
-}
-
-static void
-empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
-    TRUE);
-}
-
-static void
-empathy_call_window_hangup_cb (gpointer object,
-                               EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  empathy_streamed_media_handler_stop_call (priv->handler);
-
-  if (empathy_call_window_disconnected (window, FALSE))
-    gtk_widget_destroy (GTK_WIDGET (window));
-}
-
-static void
-empathy_call_window_restart_call (EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  /* Remove error info bars */
-  gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
-      (GtkCallback) gtk_widget_destroy, NULL);
-
-  create_video_output_widget (window);
-
-  g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
-      G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
-
-  /* While the call was disconnected, the input volume might have changed.
-   * However, since the audio_input source was destroyed, its volume has not
-   * been updated during that time. That's why we manually update it here */
-  empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
-
-  priv->outgoing = TRUE;
-  empathy_call_window_set_state_connecting (window);
-
-  if (priv->pipeline_playing)
-    start_call (window);
-  else
-    /* call will be started when the pipeline is ready */
-    priv->start_call_when_playing = TRUE;
-
-
-  empathy_call_window_setup_avatars (window, priv->handler);
-
-  gtk_action_set_sensitive (priv->redial, FALSE);
-  gtk_widget_set_sensitive (priv->redial_button, FALSE);
-}
-
-static void
-empathy_call_window_redial_cb (gpointer object,
-    EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (priv->call_state == CONNECTED)
-    priv->call_state = REDIALING;
-
-  empathy_streamed_media_handler_stop_call (priv->handler);
-
-  if (priv->call_state != CONNECTED)
-    empathy_call_window_restart_call (window);
-}
-
-static void
-empathy_call_window_fullscreen_cb (gpointer object,
-                                   EmpathyCallWindow *window)
-{
-  empathy_call_window_fullscreen_toggle (window);
-}
-
-static void
-empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (priv->is_fullscreen)
-    gtk_window_unfullscreen (GTK_WINDOW (window));
-  else
-    gtk_window_fullscreen (GTK_WINDOW (window));
-}
-
-static gboolean
-empathy_call_window_video_button_press_cb (GtkWidget *video_output,
-  GdkEventButton *event, EmpathyCallWindow *window)
-{
-  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
-    {
-      empathy_call_window_video_menu_popup (window, event->button);
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-static gboolean
-empathy_call_window_key_press_cb (GtkWidget *video_output,
-  GdkEventKey *event, EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
-    {
-      /* Since we are in fullscreen mode, toggling will bring us back to
-         normal mode. */
-      empathy_call_window_fullscreen_toggle (window);
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-static gboolean
-empathy_call_window_video_output_motion_notify (GtkWidget *widget,
-    GdkEventMotion *event, EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (priv->is_fullscreen)
-    {
-      empathy_call_window_fullscreen_show_popup (priv->fullscreen);
-      return TRUE;
-    }
-  return FALSE;
-}
-
-static void
-empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
-  guint button)
-{
-  GtkWidget *menu;
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  menu = gtk_ui_manager_get_widget (priv->ui_manager,
-            "/video-popup");
-  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
-      button, gtk_get_current_event_time ());
-  gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
-}
-
-static void
-empathy_call_window_status_message (EmpathyCallWindow *window,
-  gchar *message)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (priv->context_id == 0)
-    {
-      priv->context_id = gtk_statusbar_get_context_id (
-        GTK_STATUSBAR (priv->statusbar), "voip call status messages");
-    }
-  else
-    {
-      gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
-    }
-
-  gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
-    message);
-}
-
-static void
-empathy_call_window_volume_changed_cb (GtkScaleButton *button,
-  gdouble value, EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (priv->audio_output == NULL)
-    return;
-
-  empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
-    value);
-}
-
-/* block all the signals related to camera control widgets. This is useful
- * when we are manually updating the UI and so don't want to fire the
- * callbacks */
-static void
-block_camera_control_signals (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  g_signal_handlers_block_by_func (priv->tool_button_camera_off,
-      tool_button_camera_off_toggled_cb, self);
-  g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
-      tool_button_camera_preview_toggled_cb, self);
-  g_signal_handlers_block_by_func (priv->tool_button_camera_on,
-      tool_button_camera_on_toggled_cb, self);
-  g_signal_handlers_block_by_func (priv->action_camera_on,
-      action_camera_change_cb, self);
-}
-
-static void
-unblock_camera_control_signals (EmpathyCallWindow *self)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-
-  g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
-      tool_button_camera_off_toggled_cb, self);
-  g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
-      tool_button_camera_preview_toggled_cb, self);
-  g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
-      tool_button_camera_on_toggled_cb, self);
-  g_signal_handlers_unblock_by_func (priv->action_camera_on,
-      action_camera_change_cb, self);
-}
diff --git a/src/empathy-call-window.h b/src/empathy-call-window.h
deleted file mode 100644 (file)
index e6a59c2..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * empathy-call-window.h - Header for EmpathyCallWindow
- * Copyright (C) 2008 Collabora Ltd.
- * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- */
-
-#ifndef __EMPATHY_CALL_WINDOW_H__
-#define __EMPATHY_CALL_WINDOW_H__
-
-#include <glib-object.h>
-#include <gtk/gtk.h>
-#include <libempathy/empathy-streamed-media-handler.h>
-
-G_BEGIN_DECLS
-
-typedef struct _EmpathyCallWindow EmpathyCallWindow;
-typedef struct _EmpathyCallWindowClass EmpathyCallWindowClass;
-
-struct _EmpathyCallWindowClass {
-    GtkWindowClass parent_class;
-};
-
-struct _EmpathyCallWindow {
-    GtkWindow parent;
-};
-
-GType empathy_call_window_get_type (void);
-
-/* TYPE MACROS */
-#define EMPATHY_TYPE_CALL_WINDOW \
-  (empathy_call_window_get_type ())
-#define EMPATHY_CALL_WINDOW(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST((obj), EMPATHY_TYPE_CALL_WINDOW, \
-    EmpathyCallWindow))
-#define EMPATHY_CALL_WINDOW_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST((klass), EMPATHY_TYPE_CALL_WINDOW, \
-    EmpathyCallWindowClass))
-#define EMPATHY_IS_CALL_WINDOW(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EMPATHY_TYPE_CALL_WINDOW))
-#define EMPATHY_IS_CALL_WINDOW_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE((klass), EMPATHY_TYPE_CALL_WINDOW))
-#define EMPATHY_CALL_WINDOW_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_CALL_WINDOW, \
-    EmpathyCallWindowClass))
-
-EmpathyCallWindow *empathy_call_window_new (EmpathyStreamedMediaHandler *handler);
-
-G_END_DECLS
-
-#endif /* #ifndef __EMPATHY_CALL_WINDOW_H__*/
diff --git a/src/empathy-streamed-media-window.c b/src/empathy-streamed-media-window.c
new file mode 100644 (file)
index 0000000..7ccca4c
--- /dev/null
@@ -0,0 +1,3323 @@
+/*
+ * empathy-streamed-media-window.c - Source for EmpathyStreamedMediaWindow
+ * Copyright (C) 2008-2009 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <math.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gst/gst.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <telepathy-glib/util.h>
+#include <telepathy-farsight/channel.h>
+#include <telepathy-glib/util.h>
+
+#include <gst/farsight/fs-element-added-notifier.h>
+
+#include <libempathy/empathy-tp-contact-factory.h>
+#include <libempathy/empathy-call-factory.h>
+#include <libempathy/empathy-utils.h>
+#include <libempathy-gtk/empathy-avatar-image.h>
+#include <libempathy-gtk/empathy-video-widget.h>
+#include <libempathy-gtk/empathy-audio-src.h>
+#include <libempathy-gtk/empathy-audio-sink.h>
+#include <libempathy-gtk/empathy-video-src.h>
+#include <libempathy-gtk/empathy-ui-utils.h>
+#include <libempathy-gtk/empathy-sound-manager.h>
+#include <libempathy-gtk/empathy-geometry.h>
+#include <libempathy-gtk/empathy-images.h>
+
+#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
+#include <libempathy/empathy-debug.h>
+
+#include "empathy-streamed-media-window.h"
+#include "empathy-call-window-fullscreen.h"
+#include "ev-sidebar.h"
+
+#define BUTTON_ID "empathy-call-dtmf-button-id"
+
+#define CONTENT_HBOX_BORDER_WIDTH 6
+#define CONTENT_HBOX_SPACING 3
+#define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
+
+#define SELF_VIDEO_SECTION_WIDTH 160
+#define SELF_VIDEO_SECTION_HEIGTH 120
+
+/* The avatar's default width and height are set to the same value because we
+   want a square icon. */
+#define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
+#define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
+  EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
+
+/* If an video input error occurs, the error message will start with "v4l" */
+#define VIDEO_INPUT_ERROR_PREFIX "v4l"
+
+/* The time interval in milliseconds between 2 outgoing rings */
+#define MS_BETWEEN_RING 500
+
+G_DEFINE_TYPE(EmpathyStreamedMediaWindow, empathy_streamed_media_window, GTK_TYPE_WINDOW)
+
+/* signal enum */
+#if 0
+enum
+{
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+#endif
+
+enum {
+  PROP_STREAMED_MEDIA_HANDLER = 1,
+};
+
+typedef enum {
+  CONNECTING,
+  CONNECTED,
+  DISCONNECTED,
+  REDIALING
+} CallState;
+
+typedef enum {
+  CAMERA_STATE_OFF = 0,
+  CAMERA_STATE_PREVIEW,
+  CAMERA_STATE_ON,
+} CameraState;
+
+/* private structure */
+typedef struct _EmpathyStreamedMediaWindowPriv EmpathyStreamedMediaWindowPriv;
+
+struct _EmpathyStreamedMediaWindowPriv
+{
+  gboolean dispose_has_run;
+  EmpathyStreamedMediaHandler *handler;
+  EmpathyContact *contact;
+
+  guint call_state;
+  gboolean outgoing;
+
+  GtkUIManager *ui_manager;
+  GtkWidget *errors_vbox;
+  /* widget displays the video received from the remote user. This widget is
+   * alive only during call. */
+  GtkWidget *video_output;
+  GtkWidget *video_preview;
+  GtkWidget *remote_user_avatar_widget;
+  GtkWidget *self_user_avatar_widget;
+  GtkWidget *sidebar;
+  GtkWidget *sidebar_button;
+  GtkWidget *statusbar;
+  GtkWidget *volume_button;
+  GtkWidget *redial_button;
+  GtkWidget *mic_button;
+  GtkWidget *toolbar;
+  GtkWidget *pane;
+  GtkAction *redial;
+  GtkAction *menu_fullscreen;
+  GtkAction *action_camera_on;
+  GtkWidget *tool_button_camera_off;
+  GtkWidget *tool_button_camera_preview;
+  GtkWidget *tool_button_camera_on;
+
+  /* The frames and boxes that contain self and remote avatar and video
+     input/output. When we redial, we destroy and re-create the boxes */
+  GtkWidget *remote_user_output_frame;
+  GtkWidget *self_user_output_frame;
+  GtkWidget *remote_user_output_hbox;
+  GtkWidget *self_user_output_hbox;
+
+  /* We keep a reference on the hbox which contains the main content so we can
+     easilly repack everything when toggling fullscreen */
+  GtkWidget *content_hbox;
+
+  /* This vbox is contained in the content_hbox and it contains the
+     self_user_output_frame and the sidebar button. When toggling fullscreen,
+     it needs to be repacked. We keep a reference on it for easier access. */
+  GtkWidget *vbox;
+
+  gulong video_output_motion_handler_id;
+  guint bus_message_source_id;
+
+  gdouble volume;
+  GtkWidget *volume_scale;
+  GtkWidget *volume_progress_bar;
+  GtkAdjustment *audio_input_adj;
+
+  GtkWidget *dtmf_panel;
+
+  /* Details vbox */
+  GtkWidget *details_vbox;
+  GtkWidget *vcodec_encoding_label;
+  GtkWidget *acodec_encoding_label;
+  GtkWidget *vcodec_decoding_label;
+  GtkWidget *acodec_decoding_label;
+
+  GtkWidget *audio_remote_candidate_label;
+  GtkWidget *audio_local_candidate_label;
+  GtkWidget *video_remote_candidate_label;
+  GtkWidget *video_local_candidate_label;
+  GtkWidget *video_remote_candidate_info_img;
+  GtkWidget *video_local_candidate_info_img;
+  GtkWidget *audio_remote_candidate_info_img;
+  GtkWidget *audio_local_candidate_info_img;
+
+  GstElement *video_input;
+  GstElement *audio_input;
+  GstElement *audio_output;
+  GstElement *pipeline;
+  GstElement *video_tee;
+
+  GstElement *funnel;
+  GstElement *liveadder;
+
+  FsElementAddedNotifier *fsnotifier;
+
+  guint context_id;
+
+  GTimer *timer;
+  guint timer_id;
+
+  GtkWidget *video_contrast;
+  GtkWidget *video_brightness;
+  GtkWidget *video_gamma;
+
+  GMutex *lock;
+  gboolean call_started;
+  gboolean sending_video;
+  CameraState camera_state;
+
+  EmpathyCallWindowFullscreen *fullscreen;
+  gboolean is_fullscreen;
+
+  /* Those fields represent the state of the window before it actually was in
+     fullscreen mode. */
+  gboolean sidebar_was_visible_before_fs;
+  gint original_width_before_fs;
+  gint original_height_before_fs;
+
+  /* TRUE if the call should be started when the pipeline is playing */
+  gboolean start_call_when_playing;
+  /* TRUE if we requested to set the pipeline in the playing state */
+  gboolean pipeline_playing;
+
+  EmpathySoundManager *sound_mgr;
+};
+
+#define GET_PRIV(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, \
+    EmpathyStreamedMediaWindowPriv))
+
+static void empathy_streamed_media_window_realized_cb (GtkWidget *widget,
+  EmpathyStreamedMediaWindow *window);
+
+static gboolean empathy_streamed_media_window_delete_cb (GtkWidget *widget,
+  GdkEvent *event, EmpathyStreamedMediaWindow *window);
+
+static gboolean empathy_streamed_media_window_state_event_cb (GtkWidget *widget,
+  GdkEventWindowState *event, EmpathyStreamedMediaWindow *window);
+
+static void empathy_streamed_media_window_sidebar_toggled_cb (GtkToggleButton *toggle,
+  EmpathyStreamedMediaWindow *window);
+
+static void empathy_streamed_media_window_set_send_video (EmpathyStreamedMediaWindow *window,
+  CameraState state);
+
+static void empathy_streamed_media_window_mic_toggled_cb (
+  GtkToggleToolButton *toggle, EmpathyStreamedMediaWindow *window);
+
+static void empathy_streamed_media_window_sidebar_hidden_cb (EvSidebar *sidebar,
+  EmpathyStreamedMediaWindow *window);
+
+static void empathy_streamed_media_window_sidebar_shown_cb (EvSidebar *sidebar,
+  EmpathyStreamedMediaWindow *window);
+
+static void empathy_streamed_media_window_hangup_cb (gpointer object,
+  EmpathyStreamedMediaWindow *window);
+
+static void empathy_call_window_fullscreen_cb (gpointer object,
+  EmpathyStreamedMediaWindow *window);
+
+static void empathy_call_window_fullscreen_toggle (EmpathyStreamedMediaWindow *window);
+
+static gboolean empathy_streamed_media_window_video_button_press_cb (
+  GtkWidget *video_output, GdkEventButton *event, EmpathyStreamedMediaWindow *window);
+
+static gboolean empathy_streamed_media_window_key_press_cb (GtkWidget *video_output,
+  GdkEventKey *event, EmpathyStreamedMediaWindow *window);
+
+static gboolean empathy_streamed_media_window_video_output_motion_notify (
+  GtkWidget *widget, GdkEventMotion *event, EmpathyStreamedMediaWindow *window);
+
+static void empathy_streamed_media_window_video_menu_popup (EmpathyStreamedMediaWindow *window,
+  guint button);
+
+static void empathy_streamed_media_window_redial_cb (gpointer object,
+  EmpathyStreamedMediaWindow *window);
+
+static void empathy_streamed_media_window_restart_call (EmpathyStreamedMediaWindow *window);
+
+static void empathy_streamed_media_window_status_message (EmpathyStreamedMediaWindow *window,
+  gchar *message);
+
+static void empathy_streamed_media_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
+  EmpathyStreamedMediaWindow *window);
+
+static gboolean empathy_streamed_media_window_bus_message (GstBus *bus,
+  GstMessage *message, gpointer user_data);
+
+static void
+empathy_streamed_media_window_volume_changed_cb (GtkScaleButton *button,
+  gdouble value, EmpathyStreamedMediaWindow *window);
+
+static void block_camera_control_signals (EmpathyStreamedMediaWindow *self);
+static void unblock_camera_control_signals (EmpathyStreamedMediaWindow *self);
+
+static void
+empathy_streamed_media_window_setup_toolbar (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GtkToolItem *tool_item;
+  GtkWidget *camera_off_icon;
+  GdkPixbuf *pixbuf, *modded_pixbuf;
+
+  /* set the icon of the 'camera off' button by greying off the webcam icon */
+  pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
+      GTK_ICON_SIZE_SMALL_TOOLBAR);
+
+  modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+      gdk_pixbuf_get_width (pixbuf),
+      gdk_pixbuf_get_height (pixbuf));
+
+  gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
+  g_object_unref (pixbuf);
+
+  camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
+  g_object_unref (modded_pixbuf);
+  gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
+        priv->tool_button_camera_off), camera_off_icon);
+
+  /* Add an empty expanded GtkToolItem so the volume button is at the end of
+   * the toolbar. */
+  tool_item = gtk_tool_item_new ();
+  gtk_tool_item_set_expand (tool_item, TRUE);
+  gtk_widget_show (GTK_WIDGET (tool_item));
+  gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
+
+  priv->volume_button = gtk_volume_button_new ();
+  /* FIXME listen to the audiosinks signals and update the button according to
+   * that, for now starting out at 1.0 and assuming only the app changes the
+   * volume will do */
+  gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
+  g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
+    G_CALLBACK (empathy_streamed_media_window_volume_changed_cb), self);
+
+  tool_item = gtk_tool_item_new ();
+  gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
+  gtk_widget_show_all (GTK_WIDGET (tool_item));
+  gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
+}
+
+static void
+dtmf_button_pressed_cb (GtkButton *button, EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+  EmpathyTpStreamedMedia *call;
+  GQuark button_quark;
+  TpDTMFEvent event;
+
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+
+  button_quark = g_quark_from_static_string (BUTTON_ID);
+  event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
+    button_quark));
+
+  empathy_tp_streamed_media_start_tone (call, event);
+
+  g_object_unref (call);
+}
+
+static void
+dtmf_button_released_cb (GtkButton *button, EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+  EmpathyTpStreamedMedia *call;
+
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+
+  empathy_tp_streamed_media_stop_tone (call);
+
+  g_object_unref (call);
+}
+
+static GtkWidget *
+empathy_streamed_media_window_create_dtmf (EmpathyStreamedMediaWindow *self)
+{
+  GtkWidget *table;
+  int i;
+  GQuark button_quark;
+  struct {
+    const gchar *label;
+    TpDTMFEvent event;
+  } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
+                      { "2", TP_DTMF_EVENT_DIGIT_2 },
+                      { "3", TP_DTMF_EVENT_DIGIT_3 },
+                      { "4", TP_DTMF_EVENT_DIGIT_4 },
+                      { "5", TP_DTMF_EVENT_DIGIT_5 },
+                      { "6", TP_DTMF_EVENT_DIGIT_6 },
+                      { "7", TP_DTMF_EVENT_DIGIT_7 },
+                      { "8", TP_DTMF_EVENT_DIGIT_8 },
+                      { "9", TP_DTMF_EVENT_DIGIT_9 },
+                      { "#", TP_DTMF_EVENT_HASH },
+                      { "0", TP_DTMF_EVENT_DIGIT_0 },
+                      { "*", TP_DTMF_EVENT_ASTERISK },
+                      { NULL, } };
+
+  button_quark = g_quark_from_static_string (BUTTON_ID);
+
+  table = gtk_table_new (4, 3, TRUE);
+
+  for (i = 0; dtmfbuttons[i].label != NULL; i++)
+    {
+      GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
+      gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
+        i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
+
+      g_object_set_qdata (G_OBJECT (button), button_quark,
+        GUINT_TO_POINTER (dtmfbuttons[i].event));
+
+      g_signal_connect (G_OBJECT (button), "pressed",
+        G_CALLBACK (dtmf_button_pressed_cb), self);
+      g_signal_connect (G_OBJECT (button), "released",
+        G_CALLBACK (dtmf_button_released_cb), self);
+    }
+
+  return table;
+}
+
+static GtkWidget *
+empathy_streamed_media_window_create_video_input_add_slider (EmpathyStreamedMediaWindow *self,
+  gchar *label_text, GtkWidget *bin)
+{
+   GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
+   GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
+   GtkWidget *label = gtk_label_new (label_text);
+
+   gtk_widget_set_sensitive (scale, FALSE);
+
+   gtk_container_add (GTK_CONTAINER (bin), vbox);
+
+   gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
+   gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
+   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+   return scale;
+}
+
+static void
+empathy_streamed_media_window_video_contrast_changed_cb (GtkAdjustment *adj,
+  EmpathyStreamedMediaWindow *self)
+
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  empathy_video_src_set_channel (priv->video_input,
+    EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
+}
+
+static void
+empathy_streamed_media_window_video_brightness_changed_cb (GtkAdjustment *adj,
+  EmpathyStreamedMediaWindow *self)
+
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  empathy_video_src_set_channel (priv->video_input,
+    EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
+}
+
+static void
+empathy_streamed_media_window_video_gamma_changed_cb (GtkAdjustment *adj,
+  EmpathyStreamedMediaWindow *self)
+
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  empathy_video_src_set_channel (priv->video_input,
+    EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
+}
+
+
+static GtkWidget *
+empathy_streamed_media_window_create_video_input (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GtkWidget *hbox;
+
+  hbox = gtk_hbox_new (TRUE, 3);
+
+  priv->video_contrast = empathy_streamed_media_window_create_video_input_add_slider (
+    self,  _("Contrast"), hbox);
+
+  priv->video_brightness = empathy_streamed_media_window_create_video_input_add_slider (
+    self,  _("Brightness"), hbox);
+
+  priv->video_gamma = empathy_streamed_media_window_create_video_input_add_slider (
+    self,  _("Gamma"), hbox);
+
+  return hbox;
+}
+
+static void
+empathy_streamed_media_window_setup_video_input (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  guint supported;
+  GtkAdjustment *adj;
+
+  supported = empathy_video_src_get_supported_channels (priv->video_input);
+
+  if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
+    {
+      adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
+
+      gtk_adjustment_set_value (adj,
+        empathy_video_src_get_channel (priv->video_input,
+          EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
+
+      g_signal_connect (G_OBJECT (adj), "value-changed",
+        G_CALLBACK (empathy_streamed_media_window_video_contrast_changed_cb), self);
+
+      gtk_widget_set_sensitive (priv->video_contrast, TRUE);
+    }
+
+  if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
+    {
+      adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
+
+      gtk_adjustment_set_value (adj,
+        empathy_video_src_get_channel (priv->video_input,
+          EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
+
+      g_signal_connect (G_OBJECT (adj), "value-changed",
+        G_CALLBACK (empathy_streamed_media_window_video_brightness_changed_cb), self);
+      gtk_widget_set_sensitive (priv->video_brightness, TRUE);
+    }
+
+  if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
+    {
+      adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
+
+      gtk_adjustment_set_value (adj,
+        empathy_video_src_get_channel (priv->video_input,
+          EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
+
+      g_signal_connect (G_OBJECT (adj), "value-changed",
+        G_CALLBACK (empathy_streamed_media_window_video_gamma_changed_cb), self);
+      gtk_widget_set_sensitive (priv->video_gamma, TRUE);
+    }
+}
+
+static void
+empathy_streamed_media_window_mic_volume_changed_cb (GtkAdjustment *adj,
+  EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  gdouble volume;
+
+  volume = gtk_adjustment_get_value (adj)/100.0;
+
+  /* Don't store the volume because of muting */
+  if (volume > 0 || gtk_toggle_tool_button_get_active (
+        GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
+    priv->volume = volume;
+
+  /* Ensure that the toggle button is active if the volume is > 0 and inactive
+   * if it's smaller than 0 */
+  if ((volume > 0) != gtk_toggle_tool_button_get_active (
+        GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
+    gtk_toggle_tool_button_set_active (
+      GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
+
+  empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
+    volume);
+}
+
+static void
+empathy_streamed_media_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
+  gdouble level, EmpathyStreamedMediaWindow *window)
+{
+  gdouble value;
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  value = CLAMP (pow (10, level / 20), 0.0, 1.0);
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
+      value);
+}
+
+static GtkWidget *
+empathy_streamed_media_window_create_audio_input (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GtkWidget *hbox, *vbox, *label;
+
+  hbox = gtk_hbox_new (TRUE, 3);
+
+  vbox = gtk_vbox_new (FALSE, 3);
+  gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
+
+  priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
+  gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
+  label = gtk_label_new (_("Volume"));
+
+  priv->audio_input_adj = gtk_range_get_adjustment (
+    GTK_RANGE (priv->volume_scale));
+  priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
+    (priv->audio_input));
+  gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
+
+  g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
+    G_CALLBACK (empathy_streamed_media_window_mic_volume_changed_cb), self);
+
+  gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
+  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
+
+  priv->volume_progress_bar = gtk_progress_bar_new ();
+
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar),
+      GTK_ORIENTATION_VERTICAL);
+
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
+      0);
+
+  gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
+      3);
+
+  return hbox;
+}
+
+static void
+create_video_output_widget (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GstBus *bus;
+
+  g_assert (priv->video_output == NULL);
+  g_assert (priv->pipeline != NULL);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
+  priv->video_output = empathy_video_widget_new (bus);
+
+  gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
+      priv->video_output, TRUE, TRUE, 0);
+
+  gtk_widget_add_events (priv->video_output,
+      GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
+  g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
+      G_CALLBACK (empathy_streamed_media_window_video_button_press_cb), self);
+
+  g_object_unref (bus);
+}
+
+static void
+create_audio_output (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  g_assert (priv->audio_output == NULL);
+  priv->audio_output = empathy_audio_sink_new ();
+  gst_object_ref (priv->audio_output);
+  gst_object_sink (priv->audio_output);
+}
+
+static void
+create_video_input (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  g_assert (priv->video_input == NULL);
+  priv->video_input = empathy_video_src_new ();
+  gst_object_ref (priv->video_input);
+  gst_object_sink (priv->video_input);
+}
+
+static void
+create_audio_input (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  g_assert (priv->audio_input == NULL);
+  priv->audio_input = empathy_audio_src_new ();
+  gst_object_ref (priv->audio_input);
+  gst_object_sink (priv->audio_input);
+
+  tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
+    G_CALLBACK (empathy_streamed_media_window_audio_input_level_changed_cb),
+    self, 0);
+}
+
+static void
+add_video_preview_to_pipeline (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GstElement *preview;
+
+  g_assert (priv->video_preview != NULL);
+  g_assert (priv->pipeline != NULL);
+  g_assert (priv->video_input != NULL);
+  g_assert (priv->video_tee != NULL);
+
+  preview = empathy_video_widget_get_element (
+      EMPATHY_VIDEO_WIDGET (priv->video_preview));
+
+  if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
+    {
+      g_warning ("Could not add video input to pipeline");
+      return;
+    }
+
+  if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee))
+    {
+      g_warning ("Could not add video tee to pipeline");
+      return;
+    }
+
+  if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
+    {
+      g_warning ("Could not add video preview to pipeline");
+      return;
+    }
+
+  if (!gst_element_link (priv->video_input, priv->video_tee))
+    {
+      g_warning ("Could not link video input to video tee");
+      return;
+    }
+
+  if (!gst_element_link (priv->video_tee, preview))
+    {
+      g_warning ("Could not link video tee to video preview");
+      return;
+    }
+}
+
+static void
+create_video_preview (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GstBus *bus;
+
+  g_assert (priv->video_preview == NULL);
+  g_assert (priv->video_tee == NULL);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
+
+  priv->video_preview = empathy_video_widget_new_with_size (bus,
+      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
+  g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
+
+  gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
+      priv->video_preview, TRUE, TRUE, 0);
+
+  priv->video_tee = gst_element_factory_make ("tee", NULL);
+  gst_object_ref (priv->video_tee);
+  gst_object_sink (priv->video_tee);
+
+  g_object_unref (bus);
+}
+
+static void
+play_camera (EmpathyStreamedMediaWindow *window,
+    gboolean play)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+  GstElement *preview;
+  GstState state;
+
+  if (priv->video_preview == NULL)
+    {
+      create_video_preview (window);
+      add_video_preview_to_pipeline (window);
+    }
+
+  if (play)
+    state = GST_STATE_PLAYING;
+  else
+    state = GST_STATE_NULL;
+
+  preview = empathy_video_widget_get_element (
+      EMPATHY_VIDEO_WIDGET (priv->video_preview));
+
+  gst_element_set_state (preview, state);
+  gst_element_set_state (priv->video_input, state);
+  gst_element_set_state (priv->video_tee, state);
+}
+
+static void
+display_video_preview (EmpathyStreamedMediaWindow *self,
+    gboolean display)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (display)
+    {
+      /* Display the preview and hide the self avatar */
+      DEBUG ("Show video preview");
+
+      play_camera (self, TRUE);
+      gtk_widget_show (priv->video_preview);
+      gtk_widget_hide (priv->self_user_avatar_widget);
+    }
+  else
+    {
+      /* Display the self avatar and hide the preview */
+      DEBUG ("Show self avatar");
+
+      if (priv->video_preview != NULL)
+        {
+          gtk_widget_hide (priv->video_preview);
+          play_camera (self, FALSE);
+        }
+      gtk_widget_show (priv->self_user_avatar_widget);
+    }
+}
+
+static void
+empathy_streamed_media_window_set_state_connecting (EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  empathy_streamed_media_window_status_message (window, _("Connecting…"));
+  priv->call_state = CONNECTING;
+
+  if (priv->outgoing)
+    empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
+        EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
+}
+
+static void
+disable_camera (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_OFF)
+    return;
+
+  DEBUG ("Disable camera");
+
+  display_video_preview (self, FALSE);
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_OFF);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_on), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_preview), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
+      CAMERA_STATE_OFF);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_OFF;
+}
+
+static void
+tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_OFF)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  disable_camera (self);
+}
+
+static void
+enable_preview (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_PREVIEW)
+    return;
+
+  DEBUG ("Enable preview");
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    {
+      /* preview is already displayed so we just have to stop sending */
+      empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_PREVIEW);
+    }
+  else
+    {
+      display_video_preview (self, TRUE);
+    }
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_on), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_preview), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
+      CAMERA_STATE_PREVIEW);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_PREVIEW;
+}
+
+static void
+tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_PREVIEW)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  enable_preview (self);
+}
+
+static void
+enable_camera (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    return;
+
+  if (priv->video_input == NULL)
+    {
+      DEBUG ("Can't enable camera, no input");
+      return;
+    }
+
+
+  DEBUG ("Enable camera");
+
+  empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_ON);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_preview), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_on), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
+      CAMERA_STATE_ON);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_ON;
+}
+
+static void
+tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_ON)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  enable_camera (self);
+}
+
+static void
+action_camera_change_cb (GtkRadioAction *action,
+    GtkRadioAction *current,
+    EmpathyStreamedMediaWindow *self)
+{
+  CameraState state;
+
+  state = gtk_radio_action_get_current_value (current);
+
+  switch (state)
+    {
+      case CAMERA_STATE_OFF:
+        disable_camera (self);
+        break;
+
+      case CAMERA_STATE_PREVIEW:
+        enable_preview (self);
+        break;
+
+      case CAMERA_STATE_ON:
+        enable_camera (self);
+        break;
+
+      default:
+        g_assert_not_reached ();
+    }
+}
+
+static void
+create_pipeline (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GstBus *bus;
+
+  g_assert (priv->pipeline == NULL);
+
+  priv->pipeline = gst_pipeline_new (NULL);
+  priv->pipeline_playing = FALSE;
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
+  priv->bus_message_source_id = gst_bus_add_watch (bus,
+      empathy_streamed_media_window_bus_message, self);
+
+  g_object_unref (bus);
+}
+
+
+static void
+empathy_streamed_media_window_init (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GtkBuilder *gui;
+  GtkWidget *top_vbox;
+  GtkWidget *h;
+  GtkWidget *arrow;
+  GtkWidget *page;
+  gchar *filename;
+  GKeyFile *keyfile;
+  GError *error = NULL;
+
+  filename = empathy_file_lookup ("empathy-call-window.ui", "src");
+  gui = empathy_builder_get_file (filename,
+    "call_window_vbox", &top_vbox,
+    "errors_vbox", &priv->errors_vbox,
+    "pane", &priv->pane,
+    "statusbar", &priv->statusbar,
+    "redial", &priv->redial_button,
+    "microphone", &priv->mic_button,
+    "toolbar", &priv->toolbar,
+    "menuredial", &priv->redial,
+    "ui_manager", &priv->ui_manager,
+    "menufullscreen", &priv->menu_fullscreen,
+    "camera_off", &priv->tool_button_camera_off,
+    "camera_preview", &priv->tool_button_camera_preview,
+    "camera_on", &priv->tool_button_camera_on,
+    "action_camera_on",  &priv->action_camera_on,
+    "details_vbox",  &priv->details_vbox,
+    "vcodec_encoding_label", &priv->vcodec_encoding_label,
+    "acodec_encoding_label", &priv->acodec_encoding_label,
+    "acodec_decoding_label", &priv->acodec_decoding_label,
+    "vcodec_decoding_label", &priv->vcodec_decoding_label,
+    "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
+    "audio_local_candidate_label", &priv->audio_local_candidate_label,
+    "video_remote_candidate_label", &priv->video_remote_candidate_label,
+    "video_local_candidate_label", &priv->video_local_candidate_label,
+    "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
+    "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
+    "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
+    "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
+    NULL);
+  g_free (filename);
+
+  empathy_builder_connect (gui, self,
+    "menuhangup", "activate", empathy_streamed_media_window_hangup_cb,
+    "hangup", "clicked", empathy_streamed_media_window_hangup_cb,
+    "menuredial", "activate", empathy_streamed_media_window_redial_cb,
+    "redial", "clicked", empathy_streamed_media_window_redial_cb,
+    "microphone", "toggled", empathy_streamed_media_window_mic_toggled_cb,
+    "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
+    "camera_off", "toggled", tool_button_camera_off_toggled_cb,
+    "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
+    "camera_on", "toggled", tool_button_camera_on_toggled_cb,
+    "action_camera_on", "changed", action_camera_change_cb,
+    NULL);
+
+  gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
+
+  priv->lock = g_mutex_new ();
+
+  gtk_container_add (GTK_CONTAINER (self), top_vbox);
+
+  priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
+  gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
+                                  CONTENT_HBOX_BORDER_WIDTH);
+  gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
+
+  /* remote user output frame */
+  priv->remote_user_output_frame = gtk_frame_new (NULL);
+  gtk_widget_set_size_request (priv->remote_user_output_frame,
+      EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
+  gtk_box_pack_start (GTK_BOX (priv->content_hbox),
+      priv->remote_user_output_frame, TRUE, TRUE,
+      CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+
+  priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
+
+  priv->remote_user_avatar_widget = gtk_image_new ();
+
+  gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
+      priv->remote_user_avatar_widget, TRUE, TRUE, 0);
+
+  gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
+      priv->remote_user_output_hbox);
+
+  /* self user output frame */
+  priv->self_user_output_frame = gtk_frame_new (NULL);
+  gtk_widget_set_size_request (priv->self_user_output_frame,
+      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
+
+  priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
+
+  priv->self_user_avatar_widget = gtk_image_new ();
+  gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
+      priv->self_user_avatar_widget, TRUE, TRUE, 0);
+
+  gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
+      priv->self_user_output_hbox);
+
+  create_pipeline (self);
+  create_video_output_widget (self);
+  create_audio_input (self);
+  create_audio_output (self);
+  create_video_input (self);
+
+  priv->fsnotifier = fs_element_added_notifier_new ();
+  fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
+
+  /* The call will be started as soon the pipeline is playing */
+  priv->start_call_when_playing = TRUE;
+
+  keyfile = g_key_file_new ();
+  filename = empathy_file_lookup ("element-properties", "data");
+  if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
+    {
+      fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
+          keyfile);
+    }
+  else
+    {
+      g_warning ("Could not load element-properties file: %s", error->message);
+      g_key_file_free (keyfile);
+      g_clear_error (&error);
+    }
+  g_free (filename);
+
+  priv->vbox = gtk_vbox_new (FALSE, 3);
+  gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
+      FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+  gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
+      FALSE, FALSE, 0);
+
+  empathy_streamed_media_window_setup_toolbar (self);
+
+  priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
+  arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
+  g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
+    G_CALLBACK (empathy_streamed_media_window_sidebar_toggled_cb), self);
+
+  gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
+
+  h = gtk_hbox_new (FALSE, 3);
+  gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
+  gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
+
+  priv->sidebar = ev_sidebar_new ();
+  g_signal_connect (G_OBJECT (priv->sidebar),
+    "hide", G_CALLBACK (empathy_streamed_media_window_sidebar_hidden_cb), self);
+  g_signal_connect (G_OBJECT (priv->sidebar),
+    "show", G_CALLBACK (empathy_streamed_media_window_sidebar_shown_cb), self);
+  gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
+
+  page = empathy_streamed_media_window_create_audio_input (self);
+  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input",
+      _("Audio input"), page);
+
+  page = empathy_streamed_media_window_create_video_input (self);
+  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input",
+      _("Video input"), page);
+
+  priv->dtmf_panel = empathy_streamed_media_window_create_dtmf (self);
+  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
+      _("Dialpad"), priv->dtmf_panel);
+
+  gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
+
+  ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details",
+      _("Details"), priv->details_vbox);
+
+  gtk_widget_show_all (top_vbox);
+
+  gtk_widget_hide (priv->sidebar);
+
+  priv->fullscreen = empathy_call_window_fullscreen_new (self);
+  empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
+      priv->video_output);
+  g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
+      "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
+
+  g_signal_connect (G_OBJECT (self), "realize",
+    G_CALLBACK (empathy_streamed_media_window_realized_cb), self);
+
+  g_signal_connect (G_OBJECT (self), "delete-event",
+    G_CALLBACK (empathy_streamed_media_window_delete_cb), self);
+
+  g_signal_connect (G_OBJECT (self), "window-state-event",
+    G_CALLBACK (empathy_streamed_media_window_state_event_cb), self);
+
+  g_signal_connect (G_OBJECT (self), "key-press-event",
+      G_CALLBACK (empathy_streamed_media_window_key_press_cb), self);
+
+  priv->timer = g_timer_new ();
+
+  g_object_ref (priv->ui_manager);
+  g_object_unref (gui);
+
+  priv->sound_mgr = empathy_sound_manager_dup_singleton ();
+
+  empathy_geometry_bind (GTK_WINDOW (self), "call-window");
+}
+
+/* Instead of specifying a width and a height, we specify only one size. That's
+   because we want a square avatar icon.  */
+static void
+init_contact_avatar_with_size (EmpathyContact *contact,
+    GtkWidget *image_widget,
+    gint size)
+{
+  GdkPixbuf *pixbuf_avatar = NULL;
+
+  if (contact != NULL)
+    {
+      pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
+        size, size);
+    }
+
+  if (pixbuf_avatar == NULL)
+    {
+      pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
+          EMPATHY_IMAGE_AVATAR_DEFAULT, size);
+    }
+
+  gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
+
+  if (pixbuf_avatar != NULL)
+    g_object_unref (pixbuf_avatar);
+}
+
+static void
+set_window_title (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  gchar *tmp;
+
+  /* translators: Call is a noun and %s is the contact name. This string
+   * is used in the window title */
+  tmp = g_strdup_printf (_("Call with %s"),
+      empathy_contact_get_alias (priv->contact));
+  gtk_window_set_title (GTK_WINDOW (self), tmp);
+  g_free (tmp);
+}
+
+static void
+contact_name_changed_cb (EmpathyContact *contact,
+    GParamSpec *pspec, EmpathyStreamedMediaWindow *self)
+{
+  set_window_title (self);
+}
+
+static void
+contact_avatar_changed_cb (EmpathyContact *contact,
+    GParamSpec *pspec, GtkWidget *avatar_widget)
+{
+  int size;
+  GtkAllocation allocation;
+
+  gtk_widget_get_allocation (avatar_widget, &allocation);
+  size = allocation.height;
+
+  if (size == 0)
+    {
+      /* the widget is not allocated yet, set a default size */
+      size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
+          REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
+    }
+
+  init_contact_avatar_with_size (contact, avatar_widget, size);
+}
+
+static void
+empathy_streamed_media_window_got_self_contact_cb (TpConnection *connection,
+    EmpathyContact *contact, const GError *error, gpointer user_data,
+    GObject *weak_object)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
+      MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
+
+  g_signal_connect (contact, "notify::avatar",
+      G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
+}
+
+static void
+empathy_streamed_media_window_setup_avatars (EmpathyStreamedMediaWindow *self,
+    EmpathyStreamedMediaHandler *handler)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  g_object_get (handler, "contact", &(priv->contact), NULL);
+
+  if (priv->contact != NULL)
+    {
+      TpConnection *connection;
+
+      set_window_title (self);
+
+      g_signal_connect (priv->contact, "notify::name",
+          G_CALLBACK (contact_name_changed_cb), self);
+      g_signal_connect (priv->contact, "notify::avatar",
+          G_CALLBACK (contact_avatar_changed_cb),
+          priv->remote_user_avatar_widget);
+
+      /* Retreiving the self avatar */
+      connection = empathy_contact_get_connection (priv->contact);
+      empathy_tp_contact_factory_get_from_handle (connection,
+          tp_connection_get_self_handle (connection),
+          empathy_streamed_media_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
+    }
+  else
+    {
+      g_warning ("call handler doesn't have a contact");
+      /* translators: Call is a noun. This string is used in the window
+       * title */
+      gtk_window_set_title (GTK_WINDOW (self), _("Call"));
+
+      /* Since we can't access the remote contact, we can't get a connection
+         to it and can't get the self contact (and its avatar). This means
+         that we have to manually set the self avatar. */
+      init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
+          MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
+    }
+
+  init_contact_avatar_with_size (priv->contact,
+      priv->remote_user_avatar_widget,
+      MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
+          REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
+
+  /* The remote avatar is shown by default and will be hidden when we receive
+     video from the remote side. */
+  gtk_widget_hide (priv->video_output);
+  gtk_widget_show (priv->remote_user_avatar_widget);
+}
+
+static void
+update_send_codec (EmpathyStreamedMediaWindow *self,
+    gboolean audio)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  FsCodec *codec;
+  GtkWidget *widget;
+  gchar *tmp;
+
+  if (audio)
+    {
+      codec = empathy_streamed_media_handler_get_send_audio_codec (priv->handler);
+      widget = priv->acodec_encoding_label;
+    }
+  else
+    {
+      codec = empathy_streamed_media_handler_get_send_video_codec (priv->handler);
+      widget = priv->vcodec_encoding_label;
+    }
+
+  if (codec == NULL)
+    return;
+
+  tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
+  gtk_label_set_text (GTK_LABEL (widget), tmp);
+  g_free (tmp);
+}
+
+static void
+send_audio_codec_notify_cb (GObject *object,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = user_data;
+
+  update_send_codec (self, TRUE);
+}
+
+static void
+send_video_codec_notify_cb (GObject *object,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = user_data;
+
+  update_send_codec (self, FALSE);
+}
+
+static void
+update_recv_codec (EmpathyStreamedMediaWindow *self,
+    gboolean audio)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GList *codecs, *l;
+  GtkWidget *widget;
+  GString *str = NULL;
+
+  if (audio)
+    {
+      codecs = empathy_streamed_media_handler_get_recv_audio_codecs (priv->handler);
+      widget = priv->acodec_decoding_label;
+    }
+  else
+    {
+      codecs = empathy_streamed_media_handler_get_recv_video_codecs (priv->handler);
+      widget = priv->vcodec_decoding_label;
+    }
+
+  if (codecs == NULL)
+    return;
+
+  for (l = codecs; l != NULL; l = g_list_next (l))
+    {
+      FsCodec *codec = l->data;
+
+      if (str == NULL)
+        str = g_string_new (NULL);
+      else
+        g_string_append (str, ", ");
+
+      g_string_append_printf (str, "%s/%u", codec->encoding_name,
+          codec->clock_rate);
+    }
+
+  gtk_label_set_text (GTK_LABEL (widget), str->str);
+  g_string_free (str, TRUE);
+}
+
+static void
+recv_audio_codecs_notify_cb (GObject *object,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = user_data;
+
+  update_recv_codec (self, TRUE);
+}
+
+static void
+recv_video_codecs_notify_cb (GObject *object,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = user_data;
+
+  update_recv_codec (self, FALSE);
+}
+
+static const gchar *
+candidate_type_to_str (FsCandidate *candidate)
+{
+  switch (candidate->type)
+    {
+      case FS_CANDIDATE_TYPE_HOST:
+        return "host";
+      case FS_CANDIDATE_TYPE_SRFLX:
+        return "server reflexive";
+      case FS_CANDIDATE_TYPE_PRFLX:
+        return "peer reflexive";
+      case FS_CANDIDATE_TYPE_RELAY:
+        return "relay";
+      case FS_CANDIDATE_TYPE_MULTICAST:
+        return "multicast";
+    }
+
+  return NULL;
+}
+
+static const gchar *
+candidate_type_to_desc (FsCandidate *candidate)
+{
+  switch (candidate->type)
+    {
+      case FS_CANDIDATE_TYPE_HOST:
+        return _("The IP address as seen by the machine");
+      case FS_CANDIDATE_TYPE_SRFLX:
+        return _("The IP address as seen by a server on the Internet");
+      case FS_CANDIDATE_TYPE_PRFLX:
+        return _("The IP address of the peer as seen by the other side");
+      case FS_CANDIDATE_TYPE_RELAY:
+        return _("The IP address of a relay server");
+      case FS_CANDIDATE_TYPE_MULTICAST:
+        return _("The IP address of the multicast group");
+    }
+
+  return NULL;
+}
+
+static void
+update_candidat_widget (EmpathyStreamedMediaWindow *self,
+    GtkWidget *label,
+    GtkWidget *img,
+    FsCandidate *candidate)
+{
+  gchar *str;
+
+  g_assert (candidate != NULL);
+  str = g_strdup_printf ("%s %u (%s)", candidate->ip,
+      candidate->port, candidate_type_to_str (candidate));
+
+  gtk_label_set_text (GTK_LABEL (label), str);
+  gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
+
+  g_free (str);
+}
+
+static void
+candidates_changed_cb (GObject *object,
+    FsMediaType type,
+    EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  FsCandidate *candidate = NULL;
+
+  if (type == FS_MEDIA_TYPE_VIDEO)
+    {
+      /* Update remote candidate */
+      candidate = empathy_streamed_media_handler_get_video_remote_candidate (
+          priv->handler);
+
+      update_candidat_widget (self, priv->video_remote_candidate_label,
+          priv->video_remote_candidate_info_img, candidate);
+
+      /* Update local candidate */
+      candidate = empathy_streamed_media_handler_get_video_local_candidate (
+          priv->handler);
+
+      update_candidat_widget (self, priv->video_local_candidate_label,
+          priv->video_local_candidate_info_img, candidate);
+    }
+  else
+    {
+      /* Update remote candidate */
+      candidate = empathy_streamed_media_handler_get_audio_remote_candidate (
+          priv->handler);
+
+      update_candidat_widget (self, priv->audio_remote_candidate_label,
+          priv->audio_remote_candidate_info_img, candidate);
+
+      /* Update local candidate */
+      candidate = empathy_streamed_media_handler_get_audio_local_candidate (
+          priv->handler);
+
+      update_candidat_widget (self, priv->audio_local_candidate_label,
+          priv->audio_local_candidate_info_img, candidate);
+    }
+}
+
+static void
+empathy_streamed_media_window_constructed (GObject *object)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  EmpathyTpStreamedMedia *call;
+
+  g_assert (priv->handler != NULL);
+
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+  priv->outgoing = (call == NULL);
+  if (call != NULL)
+    g_object_unref (call);
+
+  empathy_streamed_media_window_setup_avatars (self, priv->handler);
+  empathy_streamed_media_window_set_state_connecting (self);
+
+  if (!empathy_streamed_media_handler_has_initial_video (priv->handler))
+    {
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
+    }
+  /* If call has InitialVideo, the preview will be started once the call has
+   * been started (start_call()). */
+
+  update_send_codec (self, TRUE);
+  update_send_codec (self, FALSE);
+  update_recv_codec (self, TRUE);
+  update_recv_codec (self, FALSE);
+
+  tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
+      G_CALLBACK (send_audio_codec_notify_cb), self, 0);
+  tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
+      G_CALLBACK (send_video_codec_notify_cb), self, 0);
+  tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
+      G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
+  tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
+      G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
+
+  tp_g_signal_connect_object (priv->handler, "candidates-changed",
+      G_CALLBACK (candidates_changed_cb), self, 0);
+}
+
+static void empathy_streamed_media_window_dispose (GObject *object);
+static void empathy_streamed_media_window_finalize (GObject *object);
+
+static void
+empathy_streamed_media_window_set_property (GObject *object,
+  guint property_id, const GValue *value, GParamSpec *pspec)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (object);
+
+  switch (property_id)
+    {
+      case PROP_STREAMED_MEDIA_HANDLER:
+        priv->handler = g_value_dup_object (value);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+empathy_streamed_media_window_get_property (GObject *object,
+  guint property_id, GValue *value, GParamSpec *pspec)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (object);
+
+  switch (property_id)
+    {
+      case PROP_STREAMED_MEDIA_HANDLER:
+        g_value_set_object (value, priv->handler);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+empathy_streamed_media_window_class_init (
+  EmpathyStreamedMediaWindowClass *empathy_streamed_media_window_class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (empathy_streamed_media_window_class);
+  GParamSpec *param_spec;
+
+  g_type_class_add_private (empathy_streamed_media_window_class,
+    sizeof (EmpathyStreamedMediaWindowPriv));
+
+  object_class->constructed = empathy_streamed_media_window_constructed;
+  object_class->set_property = empathy_streamed_media_window_set_property;
+  object_class->get_property = empathy_streamed_media_window_get_property;
+
+  object_class->dispose = empathy_streamed_media_window_dispose;
+  object_class->finalize = empathy_streamed_media_window_finalize;
+
+  param_spec = g_param_spec_object ("handler",
+    "handler", "The call handler",
+    EMPATHY_TYPE_STREAMED_MEDIA_HANDLER,
+    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+    PROP_STREAMED_MEDIA_HANDLER, param_spec);
+}
+
+static void
+empathy_streamed_media_window_video_stream_changed_cb (EmpathyTpStreamedMedia *call,
+    GParamSpec *property, EmpathyStreamedMediaWindow *self)
+{
+  DEBUG ("video stream changed");
+  empathy_streamed_media_window_update_avatars_visibility (call, self);
+}
+
+void
+empathy_streamed_media_window_dispose (GObject *object)
+{
+  EmpathyTpStreamedMedia *call;
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->dispose_has_run)
+    return;
+
+  priv->dispose_has_run = TRUE;
+
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+
+  if (call != NULL)
+    {
+      g_object_unref (call);
+    }
+
+  if (priv->handler != NULL)
+    {
+      empathy_streamed_media_handler_stop_call (priv->handler);
+      g_object_unref (priv->handler);
+    }
+  priv->handler = NULL;
+
+  if (priv->bus_message_source_id != 0)
+    {
+      g_source_remove (priv->bus_message_source_id);
+      priv->bus_message_source_id = 0;
+    }
+
+  if (priv->pipeline != NULL)
+    g_object_unref (priv->pipeline);
+  priv->pipeline = NULL;
+
+  if (priv->video_input != NULL)
+    g_object_unref (priv->video_input);
+  priv->video_input = NULL;
+
+  if (priv->audio_input != NULL)
+    g_object_unref (priv->audio_input);
+  priv->audio_input = NULL;
+
+  if (priv->audio_output != NULL)
+    g_object_unref (priv->audio_output);
+  priv->audio_output = NULL;
+
+  if (priv->video_tee != NULL)
+    g_object_unref (priv->video_tee);
+  priv->video_tee = NULL;
+
+  if (priv->liveadder != NULL)
+    gst_object_unref (priv->liveadder);
+  priv->liveadder = NULL;
+
+  if (priv->fsnotifier != NULL)
+    g_object_unref (priv->fsnotifier);
+  priv->fsnotifier = NULL;
+
+  if (priv->timer_id != 0)
+    g_source_remove (priv->timer_id);
+  priv->timer_id = 0;
+
+  if (priv->ui_manager != NULL)
+    g_object_unref (priv->ui_manager);
+  priv->ui_manager = NULL;
+
+  if (priv->fullscreen != NULL)
+    g_object_unref (priv->fullscreen);
+  priv->fullscreen = NULL;
+
+  if (priv->contact != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (priv->contact,
+          contact_name_changed_cb, self);
+      g_object_unref (priv->contact);
+      priv->contact = NULL;
+    }
+
+  tp_clear_object (&priv->sound_mgr);
+
+  /* release any references held by the object here */
+  if (G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->dispose)
+    G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->dispose (object);
+}
+
+static void
+disconnect_video_output_motion_handler (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->video_output_motion_handler_id != 0)
+    {
+      g_signal_handler_disconnect (G_OBJECT (priv->video_output),
+          priv->video_output_motion_handler_id);
+      priv->video_output_motion_handler_id = 0;
+    }
+}
+
+void
+empathy_streamed_media_window_finalize (GObject *object)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  disconnect_video_output_motion_handler (self);
+
+  /* free any data held directly by the object here */
+  g_mutex_free (priv->lock);
+
+  g_timer_destroy (priv->timer);
+
+  G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->finalize (object);
+}
+
+
+EmpathyStreamedMediaWindow *
+empathy_streamed_media_window_new (EmpathyStreamedMediaHandler *handler)
+{
+  return EMPATHY_STREAMED_MEDIA_WINDOW (
+    g_object_new (EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, "handler", handler, NULL));
+}
+
+static void
+empathy_streamed_media_window_conference_added_cb (EmpathyStreamedMediaHandler *handler,
+  GstElement *conference, gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  gst_bin_add (GST_BIN (priv->pipeline), conference);
+
+  gst_element_set_state (conference, GST_STATE_PLAYING);
+}
+
+static gboolean
+empathy_streamed_media_window_request_resource_cb (EmpathyStreamedMediaHandler *handler,
+  FsMediaType type, FsStreamDirection direction, gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (type != FS_MEDIA_TYPE_VIDEO)
+    return TRUE;
+
+  if (direction == FS_DIRECTION_RECV)
+    return TRUE;
+
+  /* video and direction is send */
+  return priv->video_input != NULL;
+}
+
+static gboolean
+empathy_streamed_media_window_reset_pipeline (EmpathyStreamedMediaWindow *self)
+{
+  GstStateChangeReturn state_change_return;
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->pipeline == NULL)
+    return TRUE;
+
+  if (priv->bus_message_source_id != 0)
+    {
+      g_source_remove (priv->bus_message_source_id);
+      priv->bus_message_source_id = 0;
+    }
+
+  state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+
+  if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
+        state_change_return == GST_STATE_CHANGE_NO_PREROLL)
+    {
+      if (priv->pipeline != NULL)
+        g_object_unref (priv->pipeline);
+      priv->pipeline = NULL;
+
+      g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
+          empathy_streamed_media_window_mic_volume_changed_cb, self);
+
+      if (priv->video_tee != NULL)
+        g_object_unref (priv->video_tee);
+      priv->video_tee = NULL;
+
+      if (priv->video_preview != NULL)
+        gtk_widget_destroy (priv->video_preview);
+      priv->video_preview = NULL;
+
+      priv->liveadder = NULL;
+      priv->funnel = NULL;
+
+      create_pipeline (self);
+      /* Call will be started when user will hit the 'redial' button */
+      priv->start_call_when_playing = FALSE;
+      gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+
+      return TRUE;
+    }
+  else
+    {
+      g_message ("Error: could not destroy pipeline. Closing call window");
+      gtk_widget_destroy (GTK_WIDGET (self));
+
+      return FALSE;
+    }
+}
+
+static void
+reset_details_pane (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
+  gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
+  gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
+  gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
+}
+
+static gboolean
+empathy_streamed_media_window_disconnected (EmpathyStreamedMediaWindow *self,
+    gboolean restart)
+{
+  gboolean could_disconnect = FALSE;
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  gboolean could_reset_pipeline;
+
+  /* Leave full screen mode if needed */
+  gtk_window_unfullscreen (GTK_WINDOW (self));
+
+  gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
+
+  could_reset_pipeline = empathy_streamed_media_window_reset_pipeline (self);
+
+  if (priv->call_state == CONNECTING)
+      empathy_sound_manager_stop (priv->sound_mgr,
+          EMPATHY_SOUND_PHONE_OUTGOING);
+
+  if (priv->call_state != REDIALING)
+    priv->call_state = DISCONNECTED;
+
+  if (could_reset_pipeline)
+    {
+      g_mutex_lock (priv->lock);
+
+      g_timer_stop (priv->timer);
+
+      if (priv->timer_id != 0)
+        g_source_remove (priv->timer_id);
+      priv->timer_id = 0;
+
+      g_mutex_unlock (priv->lock);
+
+      if (!restart)
+        /* We are about to destroy the window, no need to update it or create
+         * a video preview */
+        return TRUE;
+
+      empathy_streamed_media_window_status_message (self, _("Disconnected"));
+
+      gtk_action_set_sensitive (priv->redial, TRUE);
+      gtk_widget_set_sensitive (priv->redial_button, TRUE);
+
+      /* Unsensitive the camera and mic button */
+      gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
+      gtk_action_set_sensitive (priv->action_camera_on, FALSE);
+      gtk_widget_set_sensitive (priv->mic_button, FALSE);
+
+      /* Be sure that the mic button is enabled */
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
+
+      if (priv->camera_state == CAMERA_STATE_ON)
+        {
+          /* Enable the 'preview' button as we are not sending atm. */
+          gtk_toggle_tool_button_set_active (
+              GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_preview), TRUE);
+        }
+      else if (priv->camera_state == CAMERA_STATE_PREVIEW)
+        {
+          /* Restart the preview with the new pipeline. */
+          display_video_preview (self, TRUE);
+        }
+
+      gtk_progress_bar_set_fraction (
+          GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
+
+      /* destroy the video output; it will be recreated when we'll redial */
+      disconnect_video_output_motion_handler (self);
+      gtk_widget_destroy (priv->video_output);
+      priv->video_output = NULL;
+
+      gtk_widget_show (priv->remote_user_avatar_widget);
+
+      reset_details_pane (self);
+
+      priv->sending_video = FALSE;
+      priv->call_started = FALSE;
+
+      could_disconnect = TRUE;
+
+      /* TODO: display the self avatar of the preview (depends if the "Always
+       * Show Video Preview" is enabled or not) */
+    }
+
+  return could_disconnect;
+}
+
+
+static void
+empathy_streamed_media_window_channel_closed_cb (EmpathyStreamedMediaHandler *handler,
+    gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  if (empathy_streamed_media_window_disconnected (self, TRUE) &&
+      priv->call_state == REDIALING)
+      empathy_streamed_media_window_restart_call (self);
+}
+
+
+static void
+empathy_streamed_media_window_channel_stream_closed_cb (EmpathyStreamedMediaHandler *handler,
+    TfStream *stream, gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  guint media_type;
+
+  g_object_get (stream, "media-type", &media_type, NULL);
+
+  /*
+   * This assumes that there is only one video stream per channel...
+   */
+
+  if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
+    {
+      if (priv->funnel != NULL)
+        {
+          GstElement *output;
+
+          output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
+              (priv->video_output));
+
+          gst_element_set_state (output, GST_STATE_NULL);
+          gst_element_set_state (priv->funnel, GST_STATE_NULL);
+
+          gst_bin_remove (GST_BIN (priv->pipeline), output);
+          gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
+          priv->funnel = NULL;
+        }
+    }
+  else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
+    {
+      if (priv->liveadder != NULL)
+        {
+          gst_element_set_state (priv->audio_output, GST_STATE_NULL);
+          gst_element_set_state (priv->liveadder, GST_STATE_NULL);
+
+          gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
+          gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
+          priv->liveadder = NULL;
+        }
+    }
+}
+
+/* Called with global lock held */
+static GstPad *
+empathy_streamed_media_window_get_video_sink_pad (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GstPad *pad;
+  GstElement *output;
+
+  if (priv->funnel == NULL)
+    {
+      output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
+        (priv->video_output));
+
+      priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
+
+      if (!priv->funnel)
+        {
+          g_warning ("Could not create fsfunnel");
+          return NULL;
+        }
+
+      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
+        {
+          gst_object_unref (priv->funnel);
+          priv->funnel = NULL;
+          g_warning ("Could  not add funnel to pipeline");
+          return NULL;
+        }
+
+      if (!gst_bin_add (GST_BIN (priv->pipeline), output))
+        {
+          g_warning ("Could not add the video output widget to the pipeline");
+          goto error;
+        }
+
+      if (!gst_element_link (priv->funnel, output))
+        {
+          g_warning ("Could not link output sink to funnel");
+          goto error_output_added;
+        }
+
+      if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start video sink");
+          goto error_output_added;
+        }
+
+      if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start funnel");
+          goto error_output_added;
+        }
+    }
+
+  pad = gst_element_get_request_pad (priv->funnel, "sink%d");
+
+  if (!pad)
+    g_warning ("Could not get request pad from funnel");
+
+  return pad;
+
+
+ error_output_added:
+
+  gst_element_set_locked_state (priv->funnel, TRUE);
+  gst_element_set_locked_state (output, TRUE);
+
+  gst_element_set_state (priv->funnel, GST_STATE_NULL);
+  gst_element_set_state (output, GST_STATE_NULL);
+
+  gst_bin_remove (GST_BIN (priv->pipeline), output);
+  gst_element_set_locked_state (output, FALSE);
+
+ error:
+
+  gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
+  priv->funnel = NULL;
+
+  return NULL;
+}
+
+/* Called with global lock held */
+static GstPad *
+empathy_streamed_media_window_get_audio_sink_pad (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GstPad *pad;
+  GstElement *filter;
+  GError *gerror = NULL;
+
+  if (priv->liveadder == NULL)
+    {
+      priv->liveadder = gst_element_factory_make ("liveadder", NULL);
+
+      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
+        {
+          g_warning ("Could not add liveadder to the pipeline");
+          goto error_add_liveadder;
+        }
+      if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
+        {
+          g_warning ("Could not add audio sink to pipeline");
+          goto error_add_output;
+        }
+
+      if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start liveadder");
+          goto error;
+        }
+
+      if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+        {
+          g_warning ("Could not start audio sink");
+          goto error;
+        }
+
+      if (GST_PAD_LINK_FAILED (
+              gst_element_link (priv->liveadder, priv->audio_output)))
+        {
+          g_warning ("Could not link liveadder to audio output");
+          goto error;
+        }
+    }
+
+  filter = gst_parse_bin_from_description (
+      "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
+  if (filter == NULL)
+    {
+      g_warning ("Could not make audio conversion filter: %s", gerror->message);
+      g_clear_error (&gerror);
+      goto error;
+    }
+
+  if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
+    {
+      g_warning ("Could not add audio conversion filter to pipeline");
+      gst_object_unref (filter);
+      goto error;
+    }
+
+  if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+    {
+      g_warning ("Could not start audio conversion filter");
+      goto error_filter;
+    }
+
+  if (!gst_element_link (filter, priv->liveadder))
+    {
+      g_warning ("Could not link audio conversion filter to liveadder");
+      goto error_filter;
+    }
+
+  pad = gst_element_get_static_pad (filter, "sink");
+
+  if (pad == NULL)
+    {
+      g_warning ("Could not get sink pad from filter");
+      goto error_filter;
+    }
+
+  return pad;
+
+ error_filter:
+
+  gst_element_set_locked_state (filter, TRUE);
+  gst_element_set_state (filter, GST_STATE_NULL);
+  gst_bin_remove (GST_BIN (priv->pipeline), filter);
+
+ error:
+
+  gst_element_set_locked_state (priv->liveadder, TRUE);
+  gst_element_set_locked_state (priv->audio_output, TRUE);
+
+  gst_element_set_state (priv->liveadder, GST_STATE_NULL);
+  gst_element_set_state (priv->audio_output, GST_STATE_NULL);
+
+  gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
+
+ error_add_output:
+
+  gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
+
+  gst_element_set_locked_state (priv->liveadder, FALSE);
+  gst_element_set_locked_state (priv->audio_output, FALSE);
+
+ error_add_liveadder:
+
+  if (priv->liveadder != NULL)
+    {
+      gst_object_unref (priv->liveadder);
+      priv->liveadder = NULL;
+    }
+
+  return NULL;
+}
+
+static gboolean
+empathy_streamed_media_window_update_timer (gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  gchar *str;
+  gdouble time_;
+
+  time_ = g_timer_elapsed (priv->timer, NULL);
+
+  /* Translators: number of minutes:seconds the caller has been connected */
+  str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time_ / 60,
+    (int) time_ % 60);
+  empathy_streamed_media_window_status_message (self, str);
+  g_free (str);
+
+  return TRUE;
+}
+
+static void
+display_error (EmpathyStreamedMediaWindow *self,
+    EmpathyTpStreamedMedia *call,
+    const gchar *img,
+    const gchar *title,
+    const gchar *desc,
+    const gchar *details)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GtkWidget *info_bar;
+  GtkWidget *content_area;
+  GtkWidget *hbox;
+  GtkWidget *vbox;
+  GtkWidget *image;
+  GtkWidget *label;
+  gchar *txt;
+
+  /* Create info bar */
+  info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+      NULL);
+
+  gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
+
+  content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
+
+  /* hbox containing the image and the messages vbox */
+  hbox = gtk_hbox_new (FALSE, 3);
+  gtk_container_add (GTK_CONTAINER (content_area), hbox);
+
+  /* Add image */
+  image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
+  gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+
+  /* vbox containing the main message and the details expander */
+  vbox = gtk_vbox_new (FALSE, 3);
+  gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
+
+  /* Add text */
+  txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
+
+  label = gtk_label_new (NULL);
+  gtk_label_set_markup (GTK_LABEL (label), txt);
+  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+  g_free (txt);
+
+  gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
+
+  /* Add details */
+  if (details != NULL)
+    {
+      GtkWidget *expander;
+
+      expander = gtk_expander_new (_("Technical Details"));
+
+      txt = g_strdup_printf ("<i>%s</i>", details);
+
+      label = gtk_label_new (NULL);
+      gtk_label_set_markup (GTK_LABEL (label), txt);
+      gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+      gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+      g_free (txt);
+
+      gtk_container_add (GTK_CONTAINER (expander), label);
+      gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
+    }
+
+  g_signal_connect (info_bar, "response",
+      G_CALLBACK (gtk_widget_destroy), NULL);
+
+  gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
+      FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
+  gtk_widget_show_all (info_bar);
+}
+
+static gchar *
+media_stream_error_to_txt (EmpathyStreamedMediaWindow *self,
+    EmpathyTpStreamedMedia *call,
+    gboolean audio,
+    TpMediaStreamError error)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  const gchar *cm;
+  gchar *url;
+  gchar *result;
+
+  switch (error)
+    {
+      case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
+        if (audio)
+          return g_strdup_printf (
+              _("%s's software does not understand any of the audio formats "
+                "supported by your computer"),
+            empathy_contact_get_alias (priv->contact));
+        else
+          return g_strdup_printf (
+              _("%s's software does not understand any of the video formats "
+                "supported by your computer"),
+            empathy_contact_get_alias (priv->contact));
+
+      case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
+        return g_strdup_printf (
+            _("Can't establish a connection to %s. "
+              "One of you might be on a network that does not allow "
+              "direct connections."),
+          empathy_contact_get_alias (priv->contact));
+
+      case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
+          return g_strdup (_("There was a failure on the network"));
+
+      case TP_MEDIA_STREAM_ERROR_NO_CODECS:
+        if (audio)
+          return g_strdup (_("The audio formats necessary for this call "
+                "are not installed on your computer"));
+        else
+          return g_strdup (_("The video formats necessary for this call "
+                "are not installed on your computer"));
+
+      case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
+        cm = empathy_tp_streamed_media_get_connection_manager (call);
+
+        url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
+            "product=Telepathy&amp;component=%s", cm);
+
+        result = g_strdup_printf (
+            _("Something unexpected happened in a Telepathy component. "
+              "Please <a href=\"%s\">report this bug</a> and attach "
+              "logs gathered from the 'Debug' window in the Help menu."), url);
+
+        g_free (url);
+        return result;
+
+      case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
+        return g_strdup (_("There was a failure in the call engine"));
+
+      case TP_MEDIA_STREAM_ERROR_EOS:
+        return g_strdup (_("The end of the stream was reached"));
+
+      case TP_MEDIA_STREAM_ERROR_UNKNOWN:
+      default:
+        return NULL;
+    }
+}
+
+static void
+empathy_streamed_media_window_stream_error (EmpathyStreamedMediaWindow *self,
+    EmpathyTpStreamedMedia *call,
+    gboolean audio,
+    guint code,
+    const gchar *msg,
+    const gchar *icon,
+    const gchar *title)
+{
+  gchar *desc;
+
+  desc = media_stream_error_to_txt (self, call, audio, code);
+  if (desc == NULL)
+    {
+      /* No description, use the error message. That's not great as it's not
+       * localized but it's better than nothing. */
+      display_error (self, call, icon, title, msg, NULL);
+    }
+  else
+    {
+      display_error (self, call, icon, title, desc, msg);
+      g_free (desc);
+    }
+}
+
+static void
+empathy_streamed_media_window_audio_stream_error (EmpathyTpStreamedMedia *call,
+    guint code,
+    const gchar *msg,
+    EmpathyStreamedMediaWindow *self)
+{
+  empathy_streamed_media_window_stream_error (self, call, TRUE, code, msg,
+      "gnome-stock-mic", _("Can't establish audio stream"));
+}
+
+static void
+empathy_streamed_media_window_video_stream_error (EmpathyTpStreamedMedia *call,
+    guint code,
+    const gchar *msg,
+    EmpathyStreamedMediaWindow *self)
+{
+  empathy_streamed_media_window_stream_error (self, call, FALSE, code, msg,
+      "camera-web", _("Can't establish video stream"));
+}
+
+static gboolean
+empathy_streamed_media_window_connected (gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  EmpathyTpStreamedMedia *call;
+  gboolean can_send_video;
+
+  empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
+
+  can_send_video = priv->video_input != NULL && priv->contact != NULL &&
+    empathy_contact_can_voip_video (priv->contact);
+
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+
+  tp_g_signal_connect_object (call, "notify::video-stream",
+    G_CALLBACK (empathy_streamed_media_window_video_stream_changed_cb),
+    self, 0);
+
+  if (empathy_tp_streamed_media_has_dtmf (call))
+    gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
+
+  if (priv->video_input == NULL)
+    empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_OFF);
+
+  priv->sending_video = can_send_video ?
+    empathy_tp_streamed_media_is_sending_video (call) : FALSE;
+
+  gtk_toggle_tool_button_set_active (
+      GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
+      priv->sending_video && priv->video_input != NULL);
+  gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
+  gtk_action_set_sensitive (priv->action_camera_on, can_send_video);
+
+  gtk_action_set_sensitive (priv->redial, FALSE);
+  gtk_widget_set_sensitive (priv->redial_button, FALSE);
+
+  gtk_widget_set_sensitive (priv->mic_button, TRUE);
+
+  empathy_streamed_media_window_update_avatars_visibility (call, self);
+
+  g_object_unref (call);
+
+  g_mutex_lock (priv->lock);
+
+  priv->timer_id = g_timeout_add_seconds (1,
+    empathy_streamed_media_window_update_timer, self);
+
+  g_mutex_unlock (priv->lock);
+
+  empathy_streamed_media_window_update_timer (self);
+
+  gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
+
+  return FALSE;
+}
+
+
+/* Called from the streaming thread */
+static gboolean
+empathy_streamed_media_window_src_added_cb (EmpathyStreamedMediaHandler *handler,
+  GstPad *src, guint media_type, gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  gboolean retval = FALSE;
+
+  GstPad *pad;
+
+  g_mutex_lock (priv->lock);
+
+  if (priv->call_state != CONNECTED)
+    {
+      g_timer_start (priv->timer);
+      priv->timer_id = g_idle_add  (empathy_streamed_media_window_connected, self);
+      priv->call_state = CONNECTED;
+    }
+
+  switch (media_type)
+    {
+      case TP_MEDIA_STREAM_TYPE_AUDIO:
+        pad = empathy_streamed_media_window_get_audio_sink_pad (self);
+        break;
+      case TP_MEDIA_STREAM_TYPE_VIDEO:
+        gtk_widget_hide (priv->remote_user_avatar_widget);
+        gtk_widget_show (priv->video_output);
+        pad = empathy_streamed_media_window_get_video_sink_pad (self);
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  if (pad == NULL)
+    goto out;
+
+  if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
+      g_warning ("Could not link %s sink pad",
+          media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
+  else
+      retval = TRUE;
+
+  gst_object_unref (pad);
+
+ out:
+
+  /* If no sink could be linked, try to add fakesink to prevent the whole call
+   * aborting */
+
+  if (!retval)
+    {
+      GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
+
+      if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
+        {
+          GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
+          if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
+              GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
+            {
+              gst_element_set_locked_state (fakesink, TRUE);
+              gst_element_set_state (fakesink, GST_STATE_NULL);
+              gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
+            }
+          else
+            {
+              g_debug ("Could not link real sink, linked fakesink instead");
+            }
+          gst_object_unref (sinkpad);
+        }
+      else
+        {
+          gst_object_unref (fakesink);
+        }
+    }
+
+
+  g_mutex_unlock (priv->lock);
+
+  return TRUE;
+}
+
+static gboolean
+empathy_streamed_media_window_sink_added_cb (EmpathyStreamedMediaHandler *handler,
+  GstPad *sink, guint media_type, gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GstPad *pad;
+  gboolean retval = FALSE;
+
+  switch (media_type)
+    {
+      case TP_MEDIA_STREAM_TYPE_AUDIO:
+        if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
+          {
+            g_warning ("Could not add audio source to pipeline");
+            break;
+          }
+
+        pad = gst_element_get_static_pad (priv->audio_input, "src");
+        if (!pad)
+          {
+            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
+            g_warning ("Could not get source pad from audio source");
+            break;
+          }
+
+        if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
+          {
+            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
+            g_warning ("Could not link audio source to farsight");
+            break;
+          }
+
+        if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+          {
+            g_warning ("Could not start audio source");
+            gst_element_set_state (priv->audio_input, GST_STATE_NULL);
+            gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
+            break;
+          }
+
+        retval = TRUE;
+        break;
+      case TP_MEDIA_STREAM_TYPE_VIDEO:
+        if (priv->video_input != NULL)
+          {
+            if (priv->video_tee != NULL)
+              {
+                pad = gst_element_get_request_pad (priv->video_tee, "src%d");
+                if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
+                  {
+                    g_warning ("Could not link videp soure input pipeline");
+                    break;
+                  }
+                gst_object_unref (pad);
+              }
+
+            retval = TRUE;
+          }
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  return retval;
+}
+
+static void
+empathy_streamed_media_window_remove_video_input (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GstElement *preview;
+
+  disable_camera (self);
+
+  DEBUG ("remove video input");
+  preview = empathy_video_widget_get_element (
+    EMPATHY_VIDEO_WIDGET (priv->video_preview));
+
+  gst_element_set_state (priv->video_input, GST_STATE_NULL);
+  gst_element_set_state (priv->video_tee, GST_STATE_NULL);
+  gst_element_set_state (preview, GST_STATE_NULL);
+
+  gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
+    priv->video_tee, preview, NULL);
+
+  g_object_unref (priv->video_input);
+  priv->video_input = NULL;
+  g_object_unref (priv->video_tee);
+  priv->video_tee = NULL;
+  gtk_widget_destroy (priv->video_preview);
+  priv->video_preview = NULL;
+
+  gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
+  gtk_action_set_sensitive (priv->action_camera_on, FALSE);
+  gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
+}
+
+static void
+start_call (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  priv->call_started = TRUE;
+  empathy_streamed_media_handler_start_call (priv->handler,
+      gtk_get_current_event_time ());
+
+  if (empathy_streamed_media_handler_has_initial_video (priv->handler))
+    {
+      /* Enable 'send video' buttons and display the preview */
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
+    }
+}
+
+static gboolean
+empathy_streamed_media_window_bus_message (GstBus *bus, GstMessage *message,
+  gpointer user_data)
+{
+  EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  GstState newstate;
+
+  empathy_streamed_media_handler_bus_message (priv->handler, bus, message);
+
+  switch (GST_MESSAGE_TYPE (message))
+    {
+      case GST_MESSAGE_STATE_CHANGED:
+        if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
+          {
+            gst_message_parse_state_changed (message, NULL, &newstate, NULL);
+            if (newstate == GST_STATE_PAUSED)
+                empathy_streamed_media_window_setup_video_input (self);
+          }
+        if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
+            !priv->call_started)
+          {
+            gst_message_parse_state_changed (message, NULL, &newstate, NULL);
+            if (newstate == GST_STATE_PAUSED)
+              {
+                gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+                priv->pipeline_playing = TRUE;
+
+                if (priv->start_call_when_playing)
+                  start_call (self);
+              }
+          }
+        break;
+      case GST_MESSAGE_ERROR:
+        {
+          GError *error = NULL;
+          GstElement *gst_error;
+          gchar *debug;
+
+          gst_message_parse_error (message, &error, &debug);
+          gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
+
+          g_message ("Element error: %s -- %s\n", error->message, debug);
+
+          if (g_str_has_prefix (gst_element_get_name (gst_error),
+                VIDEO_INPUT_ERROR_PREFIX))
+            {
+              /* Remove the video input and continue */
+              if (priv->video_input != NULL)
+                empathy_streamed_media_window_remove_video_input (self);
+              gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+            }
+          else
+            {
+              empathy_streamed_media_window_disconnected (self, TRUE);
+            }
+          g_error_free (error);
+          g_free (debug);
+        }
+      case GST_MESSAGE_UNKNOWN:
+      case GST_MESSAGE_EOS:
+      case GST_MESSAGE_WARNING:
+      case GST_MESSAGE_INFO:
+      case GST_MESSAGE_TAG:
+      case GST_MESSAGE_BUFFERING:
+      case GST_MESSAGE_STATE_DIRTY:
+      case GST_MESSAGE_STEP_DONE:
+      case GST_MESSAGE_CLOCK_PROVIDE:
+      case GST_MESSAGE_CLOCK_LOST:
+      case GST_MESSAGE_NEW_CLOCK:
+      case GST_MESSAGE_STRUCTURE_CHANGE:
+      case GST_MESSAGE_STREAM_STATUS:
+      case GST_MESSAGE_APPLICATION:
+      case GST_MESSAGE_ELEMENT:
+      case GST_MESSAGE_SEGMENT_START:
+      case GST_MESSAGE_SEGMENT_DONE:
+      case GST_MESSAGE_DURATION:
+      case GST_MESSAGE_ANY:
+      default:
+        break;
+    }
+
+  return TRUE;
+}
+
+static void
+empathy_streamed_media_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
+    EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  if (empathy_tp_streamed_media_is_receiving_video (call))
+    {
+      gtk_widget_hide (priv->remote_user_avatar_widget);
+      gtk_widget_show (priv->video_output);
+    }
+  else
+    {
+      gtk_widget_hide (priv->video_output);
+      gtk_widget_show (priv->remote_user_avatar_widget);
+    }
+}
+
+static void
+call_handler_notify_tp_streamed_media_cb (EmpathyStreamedMediaHandler *handler,
+    GParamSpec *spec,
+    EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+  EmpathyTpStreamedMedia *call;
+
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+  if (call == NULL)
+    return;
+
+  tp_g_signal_connect_object (call, "audio-stream-error",
+      G_CALLBACK (empathy_streamed_media_window_audio_stream_error), self, 0);
+  tp_g_signal_connect_object (call, "video-stream-error",
+      G_CALLBACK (empathy_streamed_media_window_video_stream_error), self, 0);
+
+  g_object_unref (call);
+}
+
+static void
+empathy_streamed_media_window_realized_cb (GtkWidget *widget, EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+  EmpathyTpStreamedMedia *call;
+
+  g_signal_connect (priv->handler, "conference-added",
+    G_CALLBACK (empathy_streamed_media_window_conference_added_cb), window);
+  g_signal_connect (priv->handler, "request-resource",
+    G_CALLBACK (empathy_streamed_media_window_request_resource_cb), window);
+  g_signal_connect (priv->handler, "closed",
+    G_CALLBACK (empathy_streamed_media_window_channel_closed_cb), window);
+  g_signal_connect (priv->handler, "src-pad-added",
+    G_CALLBACK (empathy_streamed_media_window_src_added_cb), window);
+  g_signal_connect (priv->handler, "sink-pad-added",
+    G_CALLBACK (empathy_streamed_media_window_sink_added_cb), window);
+  g_signal_connect (priv->handler, "stream-closed",
+    G_CALLBACK (empathy_streamed_media_window_channel_stream_closed_cb), window);
+
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+  if (call != NULL)
+    {
+      tp_g_signal_connect_object (call, "audio-stream-error",
+        G_CALLBACK (empathy_streamed_media_window_audio_stream_error), window,
+        0);
+      tp_g_signal_connect_object (call, "video-stream-error",
+        G_CALLBACK (empathy_streamed_media_window_video_stream_error), window,
+        0);
+
+      g_object_unref (call);
+    }
+  else
+    {
+      /* tp-call doesn't exist yet, we'll connect signals once it has been
+       * set */
+      g_signal_connect (priv->handler, "notify::tp-call",
+        G_CALLBACK (call_handler_notify_tp_streamed_media_cb), window);
+    }
+
+  gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+}
+
+static gboolean
+empathy_streamed_media_window_delete_cb (GtkWidget *widget, GdkEvent*event,
+  EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->pipeline != NULL)
+    {
+      if (priv->bus_message_source_id != 0)
+        {
+          g_source_remove (priv->bus_message_source_id);
+          priv->bus_message_source_id = 0;
+        }
+
+      gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+    }
+
+  if (priv->call_state == CONNECTING)
+    empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
+
+  return FALSE;
+}
+
+static void
+show_controls (EmpathyStreamedMediaWindow *window, gboolean set_fullscreen)
+{
+  GtkWidget *menu;
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  menu = gtk_ui_manager_get_widget (priv->ui_manager,
+            "/menubar1");
+
+  if (set_fullscreen)
+    {
+      gtk_widget_hide (priv->sidebar);
+      gtk_widget_hide (menu);
+      gtk_widget_hide (priv->vbox);
+      gtk_widget_hide (priv->statusbar);
+      gtk_widget_hide (priv->toolbar);
+    }
+  else
+    {
+      if (priv->sidebar_was_visible_before_fs)
+        gtk_widget_show (priv->sidebar);
+
+      gtk_widget_show (menu);
+      gtk_widget_show (priv->vbox);
+      gtk_widget_show (priv->statusbar);
+      gtk_widget_show (priv->toolbar);
+
+      gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
+          priv->original_height_before_fs);
+    }
+}
+
+static void
+show_borders (EmpathyStreamedMediaWindow *window, gboolean set_fullscreen)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
+      set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
+  gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
+      set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
+
+  if (priv->video_output != NULL)
+    {
+      gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
+          priv->video_output, TRUE, TRUE,
+          set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
+          GTK_PACK_START);
+    }
+
+  gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
+      priv->vbox, TRUE, TRUE,
+      set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
+      GTK_PACK_START);
+}
+
+static gboolean
+empathy_streamed_media_window_state_event_cb (GtkWidget *widget,
+  GdkEventWindowState *event, EmpathyStreamedMediaWindow *window)
+{
+  if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
+    {
+      EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+      gboolean set_fullscreen = event->new_window_state &
+        GDK_WINDOW_STATE_FULLSCREEN;
+
+      if (set_fullscreen)
+        {
+          gboolean sidebar_was_visible;
+          GtkAllocation allocation;
+          gint original_width, original_height;
+
+          gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
+          original_width = allocation.width;
+          original_height = allocation.height;
+
+          g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
+
+          priv->sidebar_was_visible_before_fs = sidebar_was_visible;
+          priv->original_width_before_fs = original_width;
+          priv->original_height_before_fs = original_height;
+
+          if (priv->video_output_motion_handler_id == 0 &&
+                priv->video_output != NULL)
+            {
+              priv->video_output_motion_handler_id = g_signal_connect (
+                  G_OBJECT (priv->video_output), "motion-notify-event",
+                  G_CALLBACK (empathy_streamed_media_window_video_output_motion_notify),
+                  window);
+            }
+        }
+      else
+        {
+          disconnect_video_output_motion_handler (window);
+        }
+
+      empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
+          set_fullscreen);
+      show_controls (window, set_fullscreen);
+      show_borders (window, set_fullscreen);
+      gtk_action_set_stock_id (priv->menu_fullscreen,
+          (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
+      priv->is_fullscreen = set_fullscreen;
+  }
+
+  return FALSE;
+}
+
+static void
+empathy_streamed_media_window_sidebar_toggled_cb (GtkToggleButton *toggle,
+  EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+  GtkWidget *arrow;
+  int w, h, handle_size;
+  GtkAllocation allocation, sidebar_allocation;
+
+  gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
+  w = allocation.width;
+  h = allocation.height;
+
+  gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
+
+  gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
+  if (gtk_toggle_button_get_active (toggle))
+    {
+      arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
+      gtk_widget_show (priv->sidebar);
+      w += sidebar_allocation.width + handle_size;
+    }
+  else
+    {
+      arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
+      w -= sidebar_allocation.width + handle_size;
+      gtk_widget_hide (priv->sidebar);
+    }
+
+  gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
+
+  if (w > 0 && h > 0)
+    gtk_window_resize (GTK_WINDOW (window), w, h);
+}
+
+static void
+empathy_streamed_media_window_set_send_video (EmpathyStreamedMediaWindow *window,
+  CameraState state)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+  EmpathyTpStreamedMedia *call;
+
+  priv->sending_video = (state == CAMERA_STATE_ON);
+
+  if (state == CAMERA_STATE_PREVIEW ||
+      state == CAMERA_STATE_ON)
+    {
+      /* When we start sending video, we want to show the video preview by
+         default. */
+      display_video_preview (window, TRUE);
+    }
+  else
+    {
+      display_video_preview (window, FALSE);
+    }
+
+  if (priv->call_state != CONNECTED)
+    return;
+
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+  DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
+  empathy_tp_streamed_media_request_video_stream_direction (call, priv->sending_video);
+  g_object_unref (call);
+}
+
+static void
+empathy_streamed_media_window_mic_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+  gboolean active;
+
+  active = (gtk_toggle_tool_button_get_active (toggle));
+
+  if (active)
+    {
+      empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
+        priv->volume);
+      gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
+    }
+  else
+    {
+      /* TODO, Instead of setting the input volume to 0 we should probably
+       * stop sending but this would cause the audio call to drop if both
+       * sides mute at the same time on certain CMs AFAIK. Need to revisit this
+       * in the future. GNOME #574574
+       */
+      empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
+        0);
+      gtk_adjustment_set_value (priv->audio_input_adj, 0);
+    }
+}
+
+static void
+empathy_streamed_media_window_sidebar_hidden_cb (EvSidebar *sidebar,
+  EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
+    FALSE);
+}
+
+static void
+empathy_streamed_media_window_sidebar_shown_cb (EvSidebar *sidebar,
+  EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
+    TRUE);
+}
+
+static void
+empathy_streamed_media_window_hangup_cb (gpointer object,
+                               EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  empathy_streamed_media_handler_stop_call (priv->handler);
+
+  if (empathy_streamed_media_window_disconnected (window, FALSE))
+    gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+static void
+empathy_streamed_media_window_restart_call (EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  /* Remove error info bars */
+  gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
+      (GtkCallback) gtk_widget_destroy, NULL);
+
+  create_video_output_widget (window);
+
+  g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
+      G_CALLBACK (empathy_streamed_media_window_mic_volume_changed_cb), window);
+
+  /* While the call was disconnected, the input volume might have changed.
+   * However, since the audio_input source was destroyed, its volume has not
+   * been updated during that time. That's why we manually update it here */
+  empathy_streamed_media_window_mic_volume_changed_cb (priv->audio_input_adj, window);
+
+  priv->outgoing = TRUE;
+  empathy_streamed_media_window_set_state_connecting (window);
+
+  if (priv->pipeline_playing)
+    start_call (window);
+  else
+    /* call will be started when the pipeline is ready */
+    priv->start_call_when_playing = TRUE;
+
+
+  empathy_streamed_media_window_setup_avatars (window, priv->handler);
+
+  gtk_action_set_sensitive (priv->redial, FALSE);
+  gtk_widget_set_sensitive (priv->redial_button, FALSE);
+}
+
+static void
+empathy_streamed_media_window_redial_cb (gpointer object,
+    EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->call_state == CONNECTED)
+    priv->call_state = REDIALING;
+
+  empathy_streamed_media_handler_stop_call (priv->handler);
+
+  if (priv->call_state != CONNECTED)
+    empathy_streamed_media_window_restart_call (window);
+}
+
+static void
+empathy_call_window_fullscreen_cb (gpointer object,
+                                   EmpathyStreamedMediaWindow *window)
+{
+  empathy_call_window_fullscreen_toggle (window);
+}
+
+static void
+empathy_call_window_fullscreen_toggle (EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->is_fullscreen)
+    gtk_window_unfullscreen (GTK_WINDOW (window));
+  else
+    gtk_window_fullscreen (GTK_WINDOW (window));
+}
+
+static gboolean
+empathy_streamed_media_window_video_button_press_cb (GtkWidget *video_output,
+  GdkEventButton *event, EmpathyStreamedMediaWindow *window)
+{
+  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
+    {
+      empathy_streamed_media_window_video_menu_popup (window, event->button);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+empathy_streamed_media_window_key_press_cb (GtkWidget *video_output,
+  GdkEventKey *event, EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
+    {
+      /* Since we are in fullscreen mode, toggling will bring us back to
+         normal mode. */
+      empathy_call_window_fullscreen_toggle (window);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+empathy_streamed_media_window_video_output_motion_notify (GtkWidget *widget,
+    GdkEventMotion *event, EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->is_fullscreen)
+    {
+      empathy_call_window_fullscreen_show_popup (priv->fullscreen);
+      return TRUE;
+    }
+  return FALSE;
+}
+
+static void
+empathy_streamed_media_window_video_menu_popup (EmpathyStreamedMediaWindow *window,
+  guint button)
+{
+  GtkWidget *menu;
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  menu = gtk_ui_manager_get_widget (priv->ui_manager,
+            "/video-popup");
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
+      button, gtk_get_current_event_time ());
+  gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+}
+
+static void
+empathy_streamed_media_window_status_message (EmpathyStreamedMediaWindow *window,
+  gchar *message)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->context_id == 0)
+    {
+      priv->context_id = gtk_statusbar_get_context_id (
+        GTK_STATUSBAR (priv->statusbar), "voip call status messages");
+    }
+  else
+    {
+      gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
+    }
+
+  gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
+    message);
+}
+
+static void
+empathy_streamed_media_window_volume_changed_cb (GtkScaleButton *button,
+  gdouble value, EmpathyStreamedMediaWindow *window)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
+
+  if (priv->audio_output == NULL)
+    return;
+
+  empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
+    value);
+}
+
+/* block all the signals related to camera control widgets. This is useful
+ * when we are manually updating the UI and so don't want to fire the
+ * callbacks */
+static void
+block_camera_control_signals (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  g_signal_handlers_block_by_func (priv->tool_button_camera_off,
+      tool_button_camera_off_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
+      tool_button_camera_preview_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->tool_button_camera_on,
+      tool_button_camera_on_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->action_camera_on,
+      action_camera_change_cb, self);
+}
+
+static void
+unblock_camera_control_signals (EmpathyStreamedMediaWindow *self)
+{
+  EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
+
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
+      tool_button_camera_off_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
+      tool_button_camera_preview_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
+      tool_button_camera_on_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->action_camera_on,
+      action_camera_change_cb, self);
+}
diff --git a/src/empathy-streamed-media-window.h b/src/empathy-streamed-media-window.h
new file mode 100644 (file)
index 0000000..3de80eb
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * empathy-streamed-media-window.h - Header for EmpathyStreamedMediaWindow
+ * Copyright (C) 2008 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __EMPATHY_STREAMED_MEDIA_WINDOW_H__
+#define __EMPATHY_STREAMED_MEDIA_WINDOW_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <libempathy/empathy-streamed-media-handler.h>
+
+G_BEGIN_DECLS
+
+typedef struct _EmpathyStreamedMediaWindow EmpathyStreamedMediaWindow;
+typedef struct _EmpathyStreamedMediaWindowClass EmpathyStreamedMediaWindowClass;
+
+struct _EmpathyStreamedMediaWindowClass {
+    GtkWindowClass parent_class;
+};
+
+struct _EmpathyStreamedMediaWindow {
+    GtkWindow parent;
+};
+
+GType empathy_streamed_media_window_get_type (void);
+
+/* TYPE MACROS */
+#define EMPATHY_TYPE_STREAMED_MEDIA_WINDOW \
+  (empathy_streamed_media_window_get_type ())
+#define EMPATHY_STREAMED_MEDIA_WINDOW(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, \
+    EmpathyStreamedMediaWindow))
+#define EMPATHY_STREAMED_MEDIA_WINDOW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, \
+    EmpathyStreamedMediaWindowClass))
+#define EMPATHY_IS_STREAMED_MEDIA_WINDOW(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EMPATHY_TYPE_STREAMED_MEDIA_WINDOW))
+#define EMPATHY_IS_STREAMED_MEDIA_WINDOW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), EMPATHY_TYPE_STREAMED_MEDIA_WINDOW))
+#define EMPATHY_STREAMED_MEDIA_WINDOW_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, \
+    EmpathyStreamedMediaWindowClass))
+
+EmpathyStreamedMediaWindow *empathy_streamed_media_window_new (EmpathyStreamedMediaHandler *handler);
+
+G_END_DECLS
+
+#endif /* #ifndef __EMPATHY_STREAMED_MEDIA_WINDOW_H__*/