]> git.0d.be Git - empathy.git/blobdiff - src/empathy-call-window.c
Updated Polish translation
[empathy.git] / src / empathy-call-window.c
index 4ad1b260930faba66dcb3f1ad3be3c4a29c7fed1..8b7af2dcf0a0715158b8dfed25e539e99ef56ff5 100644 (file)
@@ -31,6 +31,8 @@
 
 #include <telepathy-farsight/channel.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-audio-sink.h>
 #include <libempathy-gtk/empathy-video-src.h>
 #include <libempathy-gtk/empathy-ui-utils.h>
+#include <libempathy-gtk/empathy-sound.h>
+#include <libempathy-gtk/empathy-geometry.h>
 
-#include "empathy-call-window.h"
+#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
+#include <libempathy/empathy-debug.h>
 
+#include "empathy-call-window.h"
 #include "empathy-call-window-fullscreen.h"
 #include "empathy-sidebar.h"
 
 /* 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
-
-#define CONNECTING_STATUS_TEXT _("Connecting...")
+#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 */
@@ -81,6 +89,19 @@ enum {
   PROP_CALL_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;
 
@@ -90,9 +111,11 @@ struct _EmpathyCallWindowPriv
   EmpathyCallHandler *handler;
   EmpathyContact *contact;
 
-  gboolean connected;
+  guint call_state;
+  gboolean outgoing;
 
   GtkUIManager *ui_manager;
+  GtkWidget *errors_vbox;
   GtkWidget *video_output;
   GtkWidget *video_preview;
   GtkWidget *remote_user_avatar_widget;
@@ -103,13 +126,15 @@ struct _EmpathyCallWindowPriv
   GtkWidget *volume_button;
   GtkWidget *redial_button;
   GtkWidget *mic_button;
-  GtkWidget *camera_button;
   GtkWidget *toolbar;
   GtkWidget *pane;
-  GtkAction *show_preview;
-  GtkAction *send_video;
   GtkAction *redial;
   GtkAction *menu_fullscreen;
+  GtkAction *action_camera;
+  GtkAction *action_camera_preview;
+  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 */
@@ -128,8 +153,10 @@ struct _EmpathyCallWindowPriv
   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;
 
@@ -144,6 +171,8 @@ struct _EmpathyCallWindowPriv
   GstElement *funnel;
   GstElement *liveadder;
 
+  FsElementAddedNotifier *fsnotifier;
+
   guint context_id;
 
   GTimer *timer;
@@ -156,6 +185,7 @@ struct _EmpathyCallWindowPriv
   GMutex *lock;
   gboolean call_started;
   gboolean sending_video;
+  CameraState camera_state;
 
   EmpathyCallWindowFullscreen *fullscreen;
   gboolean is_fullscreen;
@@ -165,10 +195,6 @@ struct _EmpathyCallWindowPriv
   gboolean sidebar_was_visible_before_fs;
   gint original_width_before_fs;
   gint original_height_before_fs;
-
-  /* Used to indicate if we are currently redialing. If we are, as soon as the
-     channel is closed, the call is automatically re-initiated.*/
-  gboolean redialing;
 };
 
 #define GET_PRIV(o) \
@@ -187,18 +213,9 @@ static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
   EmpathyCallWindow *window);
 
-static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
-  EmpathyCallWindow *window);
-
 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
   gboolean send);
 
-static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
-  EmpathyCallWindow *window);
-
-static void empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
-  EmpathyCallWindow *window);
-
 static void empathy_call_window_mic_toggled_cb (
   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
 
@@ -216,14 +233,14 @@ static void empathy_call_window_fullscreen_cb (gpointer object,
 
 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_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 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);
@@ -246,11 +263,32 @@ 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. */
@@ -480,7 +518,10 @@ empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   gdouble volume;
 
-  volume =  gtk_adjustment_get_value (adj)/100.0;
+  if (priv->audio_input == NULL)
+    return;
+
+  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 (
@@ -488,7 +529,7 @@ empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
     priv->volume = volume;
 
   /* Ensure that the toggle button is active if the volume is > 0 and inactive
-   * if it's smaller then 0 */
+   * 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 (
@@ -506,39 +547,41 @@ empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
   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);
+  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, *scale, *label;
-  GtkAdjustment *adj;
+  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);
 
-  scale = gtk_vscale_new_with_range (0, 150, 100);
-  gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
+  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 = adj = gtk_range_get_adjustment (GTK_RANGE (scale));
+  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 (adj, priv->volume * 100);
+  gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
 
-  g_signal_connect (G_OBJECT (adj), "value-changed",
+  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), scale, TRUE, TRUE, 3);
+  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_progress_bar_set_orientation (
-      GTK_PROGRESS_BAR (priv->volume_progress_bar), GTK_PROGRESS_BOTTOM_TO_TOP);
+      GTK_PROGRESS_BAR (priv->volume_progress_bar),
+      GTK_PROGRESS_BOTTOM_TO_TOP);
   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
       0);
 
@@ -603,8 +646,9 @@ empathy_call_window_setup_self_frame (GstBus *bus, EmpathyCallWindow *self)
   gst_object_ref (priv->audio_input);
   gst_object_sink (priv->audio_input);
 
-  g_signal_connect (priv->audio_input, "peak-level-changed",
-    G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), self);
+  empathy_signal_connect_weak (priv->audio_input, "peak-level-changed",
+    G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
+    G_OBJECT (self));
 }
 
 static void
@@ -623,6 +667,7 @@ empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
       return;
     }
 
+  DEBUG ("Create video preview");
   g_assert (priv->video_tee == NULL);
 
   priv->video_tee = gst_element_factory_make ("tee", NULL);
@@ -649,6 +694,228 @@ empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
   gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
 }
 
+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");
+
+      if (priv->video_preview == NULL)
+        empathy_call_window_setup_video_preview (self);
+      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);
+      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_start_playing (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, FALSE);
+
+  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),
+      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, FALSE);
+
+  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),
+      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;
+
+  DEBUG ("Enable camera");
+
+  empathy_call_window_set_send_video (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_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),
+      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
 empathy_call_window_init (EmpathyCallWindow *self)
 {
@@ -660,22 +927,28 @@ empathy_call_window_init (EmpathyCallWindow *self)
   GtkWidget *page;
   GstBus *bus;
   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,
-    "camera", &priv->camera_button,
     "toolbar", &priv->toolbar,
-    "send_video", &priv->send_video,
     "menuredial", &priv->redial,
-    "show_preview", &priv->show_preview,
     "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_off",  &priv->action_camera,
+    "action_camera_preview",  &priv->action_camera_preview,
     NULL);
+  g_free (filename);
 
   empathy_builder_connect (gui, self,
     "menuhangup", "activate", empathy_call_window_hangup_cb,
@@ -683,18 +956,17 @@ empathy_call_window_init (EmpathyCallWindow *self)
     "menuredial", "activate", empathy_call_window_redial_cb,
     "redial", "clicked", empathy_call_window_redial_cb,
     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
-    "camera", "toggled", empathy_call_window_camera_toggled_cb,
-    "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
-    "show_preview", "toggled", empathy_call_window_show_preview_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_off", "changed", action_camera_change_cb,
     NULL);
 
   priv->lock = g_mutex_new ();
 
   gtk_container_add (GTK_CONTAINER (self), top_vbox);
 
-  empathy_call_window_setup_toolbar (self);
-
   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
                                   CONTENT_HBOX_BORDER_WIDTH);
@@ -702,7 +974,27 @@ empathy_call_window_init (EmpathyCallWindow *self)
 
   priv->pipeline = gst_pipeline_new (NULL);
   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
-  gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
+  priv->bus_message_source_id = gst_bus_add_watch (bus,
+      empathy_call_window_bus_message, self);
+
+  priv->fsnotifier = fs_element_added_notifier_new ();
+  fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
+
+  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->remote_user_output_frame = gtk_frame_new (NULL);
   gtk_widget_set_size_request (priv->remote_user_output_frame,
@@ -719,10 +1011,12 @@ empathy_call_window_init (EmpathyCallWindow *self)
   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);
+  gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
+      FALSE, FALSE, 0);
   empathy_call_window_setup_self_frame (bus, self);
 
+  empathy_call_window_setup_toolbar (self);
+
   g_object_unref (bus);
 
   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
@@ -762,7 +1056,8 @@ empathy_call_window_init (EmpathyCallWindow *self)
   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);
+  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);
 
@@ -778,22 +1073,21 @@ empathy_call_window_init (EmpathyCallWindow *self)
   g_signal_connect (G_OBJECT (self), "key-press-event",
       G_CALLBACK (empathy_call_window_key_press_cb), self);
 
-  empathy_call_window_status_message (self, CONNECTING_STATUS_TEXT);
-
   priv->timer = g_timer_new ();
 
   g_object_ref (priv->ui_manager);
   g_object_unref (gui);
-  g_free (filename);
+
+  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,
+init_contact_avatar_with_size (EmpathyContact *contact,
+    GtkWidget *image_widget,
     gint size)
 {
-
   GdkPixbuf *pixbuf_avatar = NULL;
 
   if (contact != NULL)
@@ -817,6 +1111,8 @@ 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_name (priv->contact));
   gtk_window_set_title (GTK_WINDOW (self), tmp);
@@ -834,8 +1130,18 @@ static void
 contact_avatar_changed_cb (EmpathyContact *contact,
     GParamSpec *pspec, GtkWidget *avatar_widget)
 {
-  init_contact_avatar_with_size (contact, avatar_widget,
-      avatar_widget->allocation.height);
+  int size;
+
+  size = avatar_widget->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
@@ -879,13 +1185,15 @@ empathy_call_window_setup_avatars (EmpathyCallWindow *self,
       factory = empathy_tp_contact_factory_dup_singleton (connection);
       empathy_tp_contact_factory_get_from_handle (factory,
           tp_connection_get_self_handle (connection),
-          empathy_call_window_got_self_contact_cb, self, NULL, NULL);
+          empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
 
       g_object_unref (factory);
     }
   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
@@ -896,8 +1204,9 @@ empathy_call_window_setup_avatars (EmpathyCallWindow *self,
     }
 
   init_contact_avatar_with_size (priv->contact,
-      priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
-      REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
+      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. */
@@ -905,26 +1214,30 @@ empathy_call_window_setup_avatars (EmpathyCallWindow *self,
   gtk_widget_show (priv->remote_user_avatar_widget);
 }
 
-static void
-empathy_call_window_setup_video_preview_visibility (EmpathyCallWindow *self,
-    EmpathyCallHandler *handler)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  gboolean initial_video = empathy_call_handler_has_initial_video (priv->handler);
-
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
-      initial_video);
-}
-
 static void
 empathy_call_window_constructed (GObject *object)
 {
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  EmpathyTpCall *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_setup_video_preview_visibility (self, priv->handler);
+  empathy_call_window_set_state_connecting (self);
+
+  if (!empathy_call_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()). */
 }
 
 static void empathy_call_window_dispose (GObject *object);
@@ -985,13 +1298,13 @@ empathy_call_window_class_init (
     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
   g_object_class_install_property (object_class,
     PROP_CALL_HANDLER, param_spec);
-
 }
 
 static void
 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
     GParamSpec *property, EmpathyCallWindow *self)
 {
+  DEBUG ("video stream changed");
   empathy_call_window_update_avatars_visibility (call, self);
 }
 
@@ -1013,13 +1326,11 @@ empathy_call_window_dispose (GObject *object)
     {
       g_signal_handlers_disconnect_by_func (call,
         empathy_call_window_video_stream_changed_cb, object);
+      g_object_unref (call);
     }
 
-  g_object_unref (call);
-
   if (priv->handler != NULL)
     g_object_unref (priv->handler);
-
   priv->handler = NULL;
 
   if (priv->pipeline != NULL)
@@ -1042,6 +1353,10 @@ empathy_call_window_dispose (GObject *object)
     g_object_unref (priv->video_tee);
   priv->video_tee = 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;
@@ -1076,6 +1391,12 @@ empathy_call_window_finalize (GObject *object)
       priv->video_output_motion_handler_id = 0;
     }
 
+  if (priv->bus_message_source_id != 0)
+    {
+      g_source_remove (priv->bus_message_source_id);
+      priv->bus_message_source_id = 0;
+    }
+
   /* free any data held directly by the object here */
   g_mutex_free (priv->lock);
 
@@ -1111,7 +1432,7 @@ empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
-  if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
+  if (type != FS_MEDIA_TYPE_VIDEO)
     return TRUE;
 
   if (direction == FS_DIRECTION_RECV)
@@ -1130,6 +1451,12 @@ empathy_call_window_reset_pipeline (EmpathyCallWindow *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 ||
@@ -1147,6 +1474,9 @@ empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
         g_object_unref (priv->audio_input);
       priv->audio_input = NULL;
 
+      g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
+          empathy_call_window_mic_volume_changed_cb, self);
+
       if (priv->audio_output != NULL)
         g_object_unref (priv->audio_output);
       priv->audio_output = NULL;
@@ -1180,12 +1510,14 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
 
-  priv->connected = FALSE;
+  if (priv->call_state == CONNECTING)
+      empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
+
+  if (priv->call_state != REDIALING)
+    priv->call_state = DISCONNECTED;
 
   if (could_reset_pipeline)
     {
-      gboolean initial_video = empathy_call_handler_has_initial_video (
-          priv->handler);
       g_mutex_lock (priv->lock);
 
       g_timer_stop (priv->timer);
@@ -1201,17 +1533,24 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
       gtk_action_set_sensitive (priv->redial, TRUE);
       gtk_widget_set_sensitive (priv->redial_button, TRUE);
 
-      /* Reseting the send_video and camera_buton to their initial state */
-      gtk_widget_set_sensitive (priv->camera_button, FALSE);
-      gtk_action_set_sensitive (priv->send_video, FALSE);
-      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
-          initial_video);
+      /* Reseting the send_video, camera_buton and mic_button to their
+         initial state */
+      gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
+      gtk_widget_set_sensitive (priv->mic_button, FALSE);
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
       gtk_toggle_tool_button_set_active (
-          GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
+          GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
+
+      /* FIXME: This is to workaround the fact that the pipeline has been
+       * destroyed and so we can't display preview until a new call (and so a
+       * new pipeline) is created. We should fix this properly by refactoring
+       * the code managing the pipeline. This is bug #602937 */
+      gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
+      gtk_action_set_sensitive (priv->action_camera_preview, FALSE);
 
-      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
-          FALSE);
-      gtk_action_set_sensitive (priv->show_preview, FALSE);
+      gtk_progress_bar_set_fraction (
+          GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
 
       gtk_widget_hide (priv->video_output);
       gtk_widget_show (priv->remote_user_avatar_widget);
@@ -1220,6 +1559,9 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
       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;
@@ -1227,15 +1569,59 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
 
 
 static void
-empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
+empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
+    gpointer user_data)
 {
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
-  if (empathy_call_window_disconnected (self) && priv->redialing)
-    {
+  if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
       empathy_call_window_restart_call (self);
-      priv->redialing = FALSE;
+}
+
+
+static void
+empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *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;
+        }
     }
 }
 
@@ -1300,25 +1686,216 @@ empathy_call_window_update_timer (gpointer user_data)
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   gchar *str;
-  gdouble time;
+  gdouble time_;
 
-  time = g_timer_elapsed (priv->timer, NULL);
+  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);
+  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,
+    EmpathyTpCall *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,
+    EmpathyTpCall *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_name (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_name (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_name (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_call_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"));
+
+      default:
+        return NULL;
+    }
+}
+
+static void
+empathy_call_window_stream_error (EmpathyCallWindow *self,
+    EmpathyTpCall *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 (EmpathyTpCall *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 (EmpathyTpCall *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);
   EmpathyTpCall *call;
+  gboolean can_send_video;
+
+  empathy_sound_stop (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);
 
@@ -1329,25 +1906,26 @@ empathy_call_window_connected (gpointer user_data)
     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
 
   if (priv->video_input == NULL)
-      empathy_call_window_set_send_video (self, FALSE);
+    empathy_call_window_set_send_video (self, FALSE);
 
-  priv->sending_video = empathy_tp_call_is_sending_video (call);
+  priv->sending_video = can_send_video ?
+    empathy_tp_call_is_sending_video (call) : FALSE;
 
-  gtk_action_set_sensitive (priv->show_preview, TRUE);
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
-      priv->sending_video
-      || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
-      priv->sending_video && priv->video_input != NULL);
   gtk_toggle_tool_button_set_active (
-      GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
+      GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
       priv->sending_video && priv->video_input != NULL);
-  gtk_widget_set_sensitive (priv->camera_button, priv->video_input != NULL);
-  gtk_action_set_sensitive (priv->send_video, priv->video_input != NULL);
+  gtk_widget_set_sensitive (priv->tool_button_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);
+
+  /* FIXME: this should won't be needed once bug #602937 is fixed
+   * (see empathy_call_window_disconnected for details) */
+  gtk_widget_set_sensitive (priv->tool_button_camera_preview, TRUE);
+  gtk_action_set_sensitive (priv->action_camera_preview, TRUE);
+
   empathy_call_window_update_avatars_visibility (call, self);
 
   g_object_unref (call);
@@ -1377,11 +1955,11 @@ empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
 
   g_mutex_lock (priv->lock);
 
-  if (priv->connected == FALSE)
+  if (priv->call_state != CONNECTED)
     {
       g_timer_start (priv->timer);
       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
-      priv->connected = TRUE;
+      priv->call_state = CONNECTED;
     }
 
   switch (media_type)
@@ -1426,23 +2004,6 @@ empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
       case TP_MEDIA_STREAM_TYPE_VIDEO:
         if (priv->video_input != NULL)
           {
-            EmpathyTpCall *call;
-            g_object_get (priv->handler, "tp-call", &call, NULL);
-
-            if (empathy_tp_call_is_sending_video (call))
-              {
-                empathy_call_window_setup_video_preview (self);
-
-                gtk_toggle_action_set_active (
-                    GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
-
-                if (priv->video_preview != NULL)
-                  gtk_widget_show (priv->video_preview);
-                gtk_widget_hide (priv->self_user_avatar_widget);
-              }
-
-            g_object_unref (call);
-
             if (priv->video_tee != NULL)
               {
                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
@@ -1462,6 +2023,7 @@ empathy_call_window_remove_video_input (EmpathyCallWindow *self)
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GstElement *preview;
 
+  DEBUG ("remove video input");
   preview = empathy_video_widget_get_element (
     EMPATHY_VIDEO_WIDGET (priv->video_preview));
 
@@ -1479,15 +2041,29 @@ empathy_call_window_remove_video_input (EmpathyCallWindow *self)
   gtk_widget_destroy (priv->video_preview);
   priv->video_preview = NULL;
 
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
   gtk_toggle_tool_button_set_active (
-      GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
-  gtk_widget_set_sensitive (priv->camera_button, FALSE);
-  gtk_action_set_sensitive (priv->send_video, FALSE);
+      GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
+  gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
 
   gtk_widget_show (priv->self_user_avatar_widget);
 }
 
+static void
+start_call (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  priv->call_started = TRUE;
+  empathy_call_handler_start_call (priv->handler);
+  gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+
+  if (empathy_call_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,
@@ -1514,9 +2090,7 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
             if (newstate == GST_STATE_PAUSED)
               {
-                priv->call_started = TRUE;
-                empathy_call_handler_start_call (priv->handler);
-                gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+                start_call (self);
               }
           }
         break;
@@ -1553,28 +2127,6 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
   return TRUE;
 }
 
-static void
-empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
-    {
-      if (priv->video_preview != NULL)
-        {
-          gtk_widget_hide (priv->self_user_avatar_widget);
-          gtk_widget_show (priv->video_preview);
-        }
-      else
-        {
-          if (priv->video_preview != NULL)
-            gtk_widget_hide (priv->video_preview);
-
-          gtk_widget_show (priv->self_user_avatar_widget);
-        }
-    }
-}
-
 static void
 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
     EmpathyCallWindow *window)
@@ -1591,14 +2143,33 @@ empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
       gtk_widget_hide (priv->video_output);
       gtk_widget_show (priv->remote_user_avatar_widget);
     }
+}
+
+static void
+call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
+    GParamSpec *spec,
+    EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  EmpathyTpCall *call;
 
-  empathy_call_window_update_self_avatar_visibility (window);
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+  if (call == NULL)
+    return;
+
+  empathy_signal_connect_weak (call, "audio-stream-error",
+      G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
+  empathy_signal_connect_weak (call, "video-stream-error",
+      G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
+
+  g_object_unref (call);
 }
 
 static void
 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
+  EmpathyTpCall *call;
 
   g_signal_connect (priv->handler, "conference-added",
     G_CALLBACK (empathy_call_window_conference_added_cb), window);
@@ -1610,6 +2181,26 @@ empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
     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)
+    {
+      empathy_signal_connect_weak (call, "audio-stream-error",
+        G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
+      empathy_signal_connect_weak (call, "video-stream-error",
+        G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
+
+      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_call_cb), window);
+    }
 
   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
 }
@@ -1621,7 +2212,18 @@ empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
   if (priv->pipeline != NULL)
-    gst_element_set_state (priv->pipeline, GST_STATE_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_stop (EMPATHY_SOUND_PHONE_OUTGOING);
 
   return FALSE;
 }
@@ -1684,13 +2286,18 @@ empathy_call_window_state_event_cb (GtkWidget *widget,
   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
     {
       EmpathyCallWindowPriv *priv = GET_PRIV (window);
-      gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
+      gboolean set_fullscreen = event->new_window_state &
+        GDK_WINDOW_STATE_FULLSCREEN;
 
       if (set_fullscreen)
         {
           gboolean sidebar_was_visible;
-          gint original_width = GTK_WIDGET (window)->allocation.width;
-          gint original_height = GTK_WIDGET (window)->allocation.height;
+          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);
 
@@ -1703,7 +2310,8 @@ empathy_call_window_state_event_cb (GtkWidget *widget,
             {
               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);
+                  G_CALLBACK (empathy_call_window_video_output_motion_notify),
+                  window);
             }
         }
       else
@@ -1734,23 +2342,26 @@ empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
   GtkWidget *arrow;
-  int w,h, handle_size;
+  int w, h, handle_size;
+  GtkAllocation allocation, sidebar_allocation;
 
-  w = GTK_WIDGET (window)->allocation.width;
-  h = GTK_WIDGET (window)->allocation.height;
+  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 += priv->sidebar->allocation.width + handle_size;
+      w += sidebar_allocation.width + handle_size;
     }
   else
     {
       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
-      w -= priv->sidebar->allocation.width + handle_size;
+      w -= sidebar_allocation.width + handle_size;
       gtk_widget_hide (priv->sidebar);
     }
 
@@ -1771,85 +2382,27 @@ empathy_call_window_set_send_video (EmpathyCallWindow *window,
 
   /* When we start sending video, we want to show the video preview by
      default. */
-  if (send)
-    {
-      empathy_call_window_setup_video_preview (window);
-      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
-          TRUE);
-    }
+  display_video_preview (window, send);
+
+  if (priv->call_state != CONNECTED)
+    return;
 
   g_object_get (priv->handler, "tp-call", &call, NULL);
+  DEBUG ("%s sending video", send ? "start": "stop");
   empathy_tp_call_request_video_stream_direction (call, send);
   g_object_unref (call);
 }
 
 static void
-empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  gboolean active;
-
-  if (!priv->connected)
-    return;
-
-  active = (gtk_toggle_tool_button_get_active (toggle));
-
-  if (priv->sending_video == active)
-    return;
-
-  empathy_call_window_set_send_video (window, active);
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
-}
-
-static void
-empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
+empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
   EmpathyCallWindow *window)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
   gboolean active;
 
-  if (!priv->connected)
-    return;
-
-  active = (gtk_toggle_action_get_active (toggle));
-
-  if (priv->sending_video == active)
+  if (priv->audio_input == NULL)
     return;
 
-  empathy_call_window_set_send_video (window, active);
-  gtk_toggle_tool_button_set_active (
-      GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
-}
-
-static void
-empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
-  EmpathyCallWindow *window)
-{
-  gboolean show_preview_toggled;
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  show_preview_toggled = gtk_toggle_action_get_active (toggle);
-
-  if (show_preview_toggled)
-    {
-      empathy_call_window_setup_video_preview (window);
-      gtk_widget_show (priv->self_user_output_frame);
-      empathy_call_window_update_self_avatar_visibility (window);
-    }
-  else
-    {
-      gtk_widget_hide (priv->self_user_output_frame);
-    }
-}
-
-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)
@@ -1910,23 +2463,29 @@ empathy_call_window_restart_call (EmpathyCallWindow *window)
 
   priv->pipeline = gst_pipeline_new (NULL);
   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
-  gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
+  priv->bus_message_source_id = gst_bus_add_watch (bus,
+      empathy_call_window_bus_message, window);
 
   empathy_call_window_setup_remote_frame (bus, window);
   empathy_call_window_setup_self_frame (bus, 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);
+
   g_object_unref (bus);
 
   gtk_widget_show_all (priv->content_hbox);
 
-  if (!empathy_call_handler_has_initial_video (priv->handler))
-    gtk_widget_hide (priv->self_user_output_frame);
+  priv->outgoing = TRUE;
+  empathy_call_window_set_state_connecting (window);
 
-  empathy_call_window_status_message (window, CONNECTING_STATUS_TEXT);
-  priv->call_started = TRUE;
-  empathy_call_handler_start_call (priv->handler);
+  start_call (window);
   empathy_call_window_setup_avatars (window, priv->handler);
-  gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
 
   gtk_action_set_sensitive (priv->redial, FALSE);
   gtk_widget_set_sensitive (priv->redial_button, FALSE);
@@ -1938,12 +2497,12 @@ empathy_call_window_redial_cb (gpointer object,
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
-  if (priv->connected)
-    priv->redialing = TRUE;
+  if (priv->call_state == CONNECTED)
+    priv->call_state = REDIALING;
 
   empathy_call_handler_stop_call (priv->handler);
 
-  if (!priv->connected)
+  if (priv->call_state != CONNECTED)
     empathy_call_window_restart_call (window);
 }
 
@@ -2049,6 +2608,42 @@ empathy_call_window_volume_changed_cb (GtkScaleButton *button,
 {
   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,
+      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,
+      action_camera_change_cb, self);
+}