Merge remote-tracking branch 'pochu/misc-fixes'
authorDanielle Madeley <danielle.madeley@collabora.co.uk>
Fri, 14 Oct 2011 04:27:12 +0000 (15:27 +1100)
committerDanielle Madeley <danielle.madeley@collabora.co.uk>
Fri, 14 Oct 2011 04:28:58 +0000 (15:28 +1100)
1  2 
libempathy-gtk/empathy-call-utils.c
libempathy-gtk/empathy-ui-utils.c
src/empathy-call-window.c

index 399647a623a8b56fd74db6c75b36b88f7ee452e7,0926230ec742e7f45975ccabc9aa7d1c897ed385..adf4987bad0e6b8197c38a97b0eadaf7b5e5b6b1
@@@ -55,6 -55,8 +55,8 @@@ get_error_display_message (GError *erro
          return _("The specified contact is not valid");
        case TP_ERROR_EMERGENCY_CALLS_NOT_SUPPORTED:
          return _("Emergency calls are not supported on this protocol");
+       case TP_ERROR_INSUFFICIENT_BALANCE:
+         return _("You don't have enough credit in order to place this call");
      }
  
    return _("There was an error starting the call");
@@@ -132,7 -134,6 +134,7 @@@ create_streamed_media_channel_cb (GObje
      }
  }
  
 +#ifdef HAVE_CALL
  static void
  create_call_channel_cb (GObject *source,
      GAsyncResult *result,
        NULL);
  }
  
 -void
 -empathy_call_new_with_streams (const gchar *contact,
 +/* Try to request a Call channel and fallback to StreamedMedia if that fails */
 +static void
 +call_new_with_streams (const gchar *contact,
      TpAccount *account,
      gboolean initial_audio,
      gboolean initial_video,
    GHashTable *call_request, *streamed_media_request;
    TpAccountChannelRequest *call_req, *streamed_media_req;
  
 +  /* Call */
    call_request = empathy_call_create_call_request (contact,
        initial_audio,
        initial_video);
  
 +  call_req = tp_account_channel_request_new (account, call_request, timestamp);
 +
 +  g_hash_table_unref (call_request);
 +
 +  /* StreamedMedia */
    streamed_media_request = empathy_call_create_streamed_media_request (
        contact, initial_audio, initial_video);
  
 -  call_req = tp_account_channel_request_new (account, call_request, timestamp);
    streamed_media_req = tp_account_channel_request_new (account,
        streamed_media_request,
        timestamp);
  
 +  g_hash_table_unref (streamed_media_request);
 +
    tp_account_channel_request_create_channel_async (call_req,
        EMPATHY_CALL_BUS_NAME, NULL,
        create_call_channel_cb,
        streamed_media_req);
  
 -  g_hash_table_unref (call_request);
 -  g_hash_table_unref (streamed_media_request);
    g_object_unref (call_req);
  }
  
 +#else /* HAVE_CALL */
 +
 +static void
 +sm_new_with_streams (const gchar *contact,
 +    TpAccount *account,
 +    gboolean initial_audio,
 +    gboolean initial_video,
 +    gint64 timestamp)
 +{
 +  GHashTable *streamed_media_request;
 +  TpAccountChannelRequest *streamed_media_req;
 +
 +  /* StreamedMedia */
 +  streamed_media_request = empathy_call_create_streamed_media_request (
 +      contact, initial_audio, initial_video);
 +
 +  streamed_media_req = tp_account_channel_request_new (account,
 +      streamed_media_request,
 +      timestamp);
 +
 +  g_hash_table_unref (streamed_media_request);
 +
 +  tp_account_channel_request_create_channel_async (streamed_media_req,
 +      EMPATHY_AV_BUS_NAME, NULL, create_streamed_media_channel_cb, NULL);
 +
 +  g_object_unref (streamed_media_req);
 +}
 +#endif /* HAVE_CALL */
 +
 +void
 +empathy_call_new_with_streams (const gchar *contact,
 +    TpAccount *account,
 +    gboolean initial_audio,
 +    gboolean initial_video,
 +    gint64 timestamp)
 +{
 +#ifdef HAVE_CALL
 +  call_new_with_streams (contact, account, initial_audio, initial_video,
 +      timestamp);
 +#else
 +  sm_new_with_streams (contact, account, initial_audio, initial_video,
 +      timestamp);
 +#endif
 +}
 +
  void
  empathy_call_set_stream_properties (GstElement *element)
  {
index 3fde9e3e15d48407987470199c74da24b5680816,8822ccdf69011d58cc6cdec5219f212ce9cedafe..32ad451a12115918492780ae78e8ec7854580036
@@@ -947,6 -947,9 +947,9 @@@ empathy_filename_from_icon_name (const 
        }
  
        icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0);
+       if (icon_info == NULL)
+               return NULL;
        ret = g_strdup (gtk_icon_info_get_filename (icon_info));
        gtk_icon_info_free (icon_info);
  
@@@ -1759,8 -1762,7 +1762,8 @@@ empathy_send_file (EmpathyContact *cont
  
        factory = empathy_ft_factory_dup_singleton ();
  
 -      empathy_ft_factory_new_transfer_outgoing (factory, contact, file);
 +      empathy_ft_factory_new_transfer_outgoing (factory, contact, file,
 +              empathy_get_current_action_time ());
  
        uri = g_file_get_uri (file);
        manager = gtk_recent_manager_get_default ();
index 0a130c4f85f146ec0ad9c9e27e6f9bfc970d79fe,05f63e2f6813197bd3fa569e4b3fb96cbf21c03d..2a92e947168b3d395cd17c253ed1de264d8d5039
@@@ -43,7 -43,6 +43,7 @@@
  #include <libempathy/empathy-camera-monitor.h>
  #include <libempathy/empathy-gsettings.h>
  #include <libempathy/empathy-tp-contact-factory.h>
 +#include <libempathy/empathy-request-util.h>
  #include <libempathy/empathy-utils.h>
  
  #include <libempathy-gtk/empathy-avatar-image.h>
@@@ -104,12 -103,11 +104,12 @@@ enum 
  };
  
  typedef enum {
 -  CONNECTING,
 -  CONNECTED,
 -  HELD,
 -  DISCONNECTED,
 -  REDIALING
 +  RINGING,       /* Incoming call */
 +  CONNECTING,    /* Outgoing call */
 +  CONNECTED,     /* Connected */
 +  HELD,          /* Connected, but on hold */
 +  DISCONNECTED,  /* Disconnected */
 +  REDIALING      /* Redialing (special case of CONNECTING) */
  } CallState;
  
  typedef enum {
@@@ -154,8 -152,6 +154,8 @@@ struct _EmpathyCallWindowPri
    ClutterActor *preview_rectangle_box2;
    ClutterActor *preview_rectangle_box3;
    ClutterActor *preview_rectangle_box4;
 +  ClutterActor *preview_spinner_actor;
 +  GtkWidget *preview_spinner_widget;
    GtkWidget *video_container;
    GtkWidget *remote_user_avatar_widget;
    GtkWidget *remote_user_avatar_toolbar;
       easilly repack everything when toggling fullscreen */
    GtkWidget *content_hbox;
  
 +  /* These are used to accept or reject an incoming call when the status
 +     is RINGING. */
 +  GtkWidget *incoming_call_dialog;
 +  TpyCallChannel *pending_channel;
 +  TpChannelDispatchOperation *pending_cdo;
 +  TpAddDispatchOperationContext *pending_context;
 +
    gulong video_output_motion_handler_id;
    guint bus_message_source_id;
  
    gdouble volume;
  
 +  /* String that contains the queued tones to send after the current ones
 +     are sent */
 +  GString *tones;
 +  gboolean sending_tones;
    GtkWidget *dtmf_panel;
  
    /* Details vbox */
@@@ -308,8 -293,6 +308,8 @@@ static gboolean empathy_call_window_vid
  static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
    guint button);
  
 +static void empathy_call_window_connect_handler (EmpathyCallWindow *self);
 +
  static void empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
    EmpathyCallWindow *window);
  
@@@ -352,72 -335,35 +352,72 @@@ empathy_call_window_video_call_cb (GtkT
  }
  
  static void
 -dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
 +empathy_call_window_emit_tones (EmpathyCallWindow *self)
  {
 -  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 -  TpyCallChannel *call;
 +  TpChannel *channel;
 +
 +  if (tp_str_empty (self->priv->tones->str))
 +    return;
 +
 +  g_object_get (self->priv->handler, "call-channel", &channel, NULL);
 +
 +  DEBUG ("Emitting multiple tones: %s", self->priv->tones->str);
 +
 +  tp_cli_channel_interface_dtmf_call_multiple_tones (channel, -1,
 +      self->priv->tones->str,
 +      NULL, NULL, NULL, NULL);
 +
 +  self->priv->sending_tones = TRUE;
 +
 +  g_string_set_size (self->priv->tones, 0);
 +
 +  g_object_unref (channel);
 +}
 +
 +static void
 +empathy_call_window_maybe_emit_tones (EmpathyCallWindow *self)
 +{
 +  if (self->priv->sending_tones)
 +    return;
 +
 +  empathy_call_window_emit_tones (self);
 +}
 +
 +static void
 +empathy_call_window_tones_stopped_cb (TpChannel *proxy,
 +    gboolean arg_cancelled,
 +    gpointer user_data,
 +    GObject *weak_object)
 +{
 +  EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
 +
 +  self->priv->sending_tones = FALSE;
 +
 +  empathy_call_window_emit_tones (self);
 +}
 +
 +static void
 +dtmf_button_pressed_cb (GtkButton *button,
 +    EmpathyCallWindow *self)
 +{
 +  EmpathyCallWindowPriv *priv = GET_PRIV (self);
    GQuark button_quark;
    TpDTMFEvent event;
  
 -  g_object_get (priv->handler, "call-channel", &call, NULL);
 -
    button_quark = g_quark_from_static_string (EMPATHY_DTMF_BUTTON_ID);
    event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
      button_quark));
  
 -  tpy_call_channel_dtmf_start_tone (call, event);
 +  g_string_append_c (priv->tones, tp_dtmf_event_to_char (event));
  
 -  g_object_unref (call);
 +  empathy_call_window_maybe_emit_tones (self);
  }
  
 +/* empathy_create_dtmf_dialpad() requires a callback, even if empty */
  static void
 -dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
 +dtmf_button_released_cb (GtkButton *button,
 +    EmpathyCallWindow *self)
  {
 -  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 -  TpyCallChannel *call;
 -
 -  g_object_get (priv->handler, "call-channel", &call, NULL);
 -
 -  tpy_call_channel_dtmf_stop_tone (call);
 -
 -  g_object_unref (call);
  }
  
  static void
@@@ -754,55 -700,6 +754,55 @@@ empathy_call_window_show_preview_rectan
    g_object_set (self->priv->preview_rectangle4, "visible", show, NULL);
  }
  
 +static void
 +empathy_call_window_get_preview_coordinates (EmpathyCallWindow *self,
 +    PreviewPosition pos,
 +    guint *x,
 +    guint *y)
 +{
 +  guint ret_x = 0, ret_y = 0;
 +  ClutterGeometry box;
 +
 +  if (!clutter_actor_has_allocation (self->priv->video_box))
 +    goto out;
 +
 +  clutter_actor_get_geometry (self->priv->video_box, &box);
 +
 +  switch (pos)
 +    {
 +      case PREVIEW_POS_TOP_LEFT:
 +        ret_x = ret_y = SELF_VIDEO_SECTION_MARGIN;
 +        break;
 +      case PREVIEW_POS_TOP_RIGHT:
 +        ret_x = box.width - SELF_VIDEO_SECTION_MARGIN
 +            - SELF_VIDEO_SECTION_WIDTH;
 +        ret_y = SELF_VIDEO_SECTION_MARGIN;
 +        break;
 +      case PREVIEW_POS_BOTTOM_LEFT:
 +        ret_x = SELF_VIDEO_SECTION_MARGIN;
 +        ret_y = box.height - SELF_VIDEO_SECTION_MARGIN
 +            - SELF_VIDEO_SECTION_HEIGHT
 +            - FLOATING_TOOLBAR_HEIGHT - FLOATING_TOOLBAR_SPACING;
 +        break;
 +      case PREVIEW_POS_BOTTOM_RIGHT:
 +        ret_x = box.width - SELF_VIDEO_SECTION_MARGIN
 +            - SELF_VIDEO_SECTION_WIDTH;
 +        ret_y = box.height - SELF_VIDEO_SECTION_MARGIN
 +            - SELF_VIDEO_SECTION_HEIGHT - FLOATING_TOOLBAR_HEIGHT
 +            - FLOATING_TOOLBAR_SPACING;
 +        break;
 +      default:
 +        g_warn_if_reached ();
 +    }
 +
 +out:
 +  if (x != NULL)
 +    *x = ret_x;
 +
 +  if (y != NULL)
 +    *y = ret_y;
 +}
 +
  static PreviewPosition
  empathy_call_window_get_preview_position (EmpathyCallWindow *self,
      gfloat event_x,
@@@ -1002,13 -899,6 +1002,13 @@@ empathy_call_window_preview_on_drag_beg
    empathy_call_window_darken_preview_rectangles (self);
  }
  
 +static void
 +empathy_call_window_on_animation_completed_cb (ClutterAnimation *animation,
 +    ClutterActor *actor)
 +{
 +  clutter_actor_set_opacity (actor, 255);
 +}
 +
  static void
  empathy_call_window_preview_on_drag_end_cb (ClutterDragAction *action,
      ClutterActor *actor,
      EmpathyCallWindow *self)
  {
    PreviewPosition pos;
 +  guint x, y;
  
    /* Get the position before destroying the drag actor, otherwise the
     * preview_box allocation won't be valid and we won't be able to
     * calculate the position. */
    pos = empathy_call_window_get_preview_position (self, event_x, event_y);
  
 -  /* Destroy the video preview copy that we were dragging */
 -  clutter_actor_destroy (self->priv->drag_preview);
 -  self->priv->drag_preview = NULL;
 +  empathy_call_window_get_preview_coordinates (self,
 +      pos != PREVIEW_POS_NONE ? pos : self->priv->preview_pos,
 +      &x, &y);
 +
 +  /* Move the preview to the destination and destroy it afterwards */
 +  clutter_actor_animate (self->priv->drag_preview, CLUTTER_LINEAR, 500,
 +      "x", (gfloat) x,
 +      "y", (gfloat) y,
 +      "signal-swapped-after::completed",
 +        clutter_actor_destroy, self->priv->drag_preview,
 +      "signal-swapped-after::completed",
 +        clutter_actor_show, self->priv->preview_shown_button,
 +      "signal::completed",
 +        empathy_call_window_on_animation_completed_cb, actor,
 +      NULL);
  
 -  clutter_actor_set_opacity (actor, 255);
 -  clutter_actor_show (self->priv->preview_shown_button);
 +  self->priv->drag_preview = NULL;
  
    if (pos != PREVIEW_POS_NONE)
      empathy_call_window_move_video_preview (self, pos);
@@@ -1115,7 -993,6 +1115,7 @@@ create_video_preview (EmpathyCallWindo
    ClutterAction *action;
    GtkWidget *button;
    PreviewPosition pos;
 +  GdkRGBA transparent = { 0., 0., 0., 0. };
  
    g_assert (priv->video_preview == NULL);
  
        SELF_VIDEO_SECTION_HEIGHT + 2 * SELF_VIDEO_SECTION_MARGIN +
        FLOATING_TOOLBAR_HEIGHT + FLOATING_TOOLBAR_SPACING);
  
 +  /* Spinner for when changing the camera device */
 +  priv->preview_spinner_widget = gtk_spinner_new ();
 +  priv->preview_spinner_actor = empathy_rounded_actor_new ();
 +  empathy_rounded_actor_set_round_factor (
 +      EMPATHY_ROUNDED_ACTOR (priv->preview_spinner_actor), 16);
 +
 +  g_object_set (priv->preview_spinner_widget, "expand", TRUE, NULL);
 +  gtk_widget_override_background_color (
 +      gtk_clutter_actor_get_widget (
 +          GTK_CLUTTER_ACTOR (priv->preview_spinner_actor)),
 +      GTK_STATE_FLAG_NORMAL, &transparent);
 +  gtk_widget_show (priv->preview_spinner_widget);
 +
 +  gtk_container_add (
 +      GTK_CONTAINER (gtk_clutter_actor_get_widget (
 +          GTK_CLUTTER_ACTOR (priv->preview_spinner_actor))),
 +      priv->preview_spinner_widget);
 +  clutter_actor_set_size (priv->preview_spinner_actor,
 +      SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGHT);
 +  clutter_actor_set_opacity (priv->preview_spinner_actor, 128);
 +  clutter_actor_hide (priv->preview_spinner_actor);
 +
    /* We have a box with the margins and the video in the middle inside
     * a bigger box with an extra bottom margin so we're not on top of
     * the floating toolbar. */
        SELF_VIDEO_SECTION_HEIGHT + 2 * SELF_VIDEO_SECTION_MARGIN);
  
    clutter_container_add_actor (CLUTTER_CONTAINER (box), preview);
 +  clutter_container_add_actor (CLUTTER_CONTAINER (box),
 +      priv->preview_spinner_actor);
    clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview), box);
  
    g_object_set (priv->video_preview_sink,
    clutter_actor_set_reactive (priv->preview_shown_button, TRUE);
  }
  
 +static void
 +empathy_call_window_start_camera_spinning (EmpathyCallWindow *self)
 +{
 +  clutter_actor_show (self->priv->preview_spinner_actor);
 +  gtk_spinner_start (GTK_SPINNER (self->priv->preview_spinner_widget));
 +}
 +
 +static void
 +empathy_call_window_stop_camera_spinning (EmpathyCallWindow *self)
 +{
 +  clutter_actor_hide (self->priv->preview_spinner_actor);
 +  gtk_spinner_stop (GTK_SPINNER (self->priv->preview_spinner_widget));
 +}
 +
  void
 -empathy_call_window_play_camera (EmpathyCallWindow *window,
 +empathy_call_window_play_camera (EmpathyCallWindow *self,
      gboolean play)
  {
 -  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 +  EmpathyCallWindowPriv *priv = GET_PRIV (self);
    GstElement *preview;
    GstState state;
  
    if (priv->video_preview == NULL)
      {
 -      create_video_preview (window);
 -      add_video_preview_to_pipeline (window);
 +      create_video_preview (self);
 +      add_video_preview_to_pipeline (self);
      }
  
    if (play)
 -    state = GST_STATE_PLAYING;
 +    {
 +      state = GST_STATE_PLAYING;
 +    }
    else
 -    state = GST_STATE_NULL;
 +    {
 +      empathy_call_window_start_camera_spinning (self);
 +      state = GST_STATE_NULL;
 +    }
  
    preview = priv->video_preview_sink;
  
@@@ -1523,105 -1357,6 +1523,105 @@@ empathy_call_window_stage_allocation_ch
        FLOATING_TOOLBAR_SPACING - FLOATING_TOOLBAR_HEIGHT);
  }
  
 +static void
 +empathy_call_window_incoming_call_response_cb (GtkDialog *dialog,
 +    gint response_id,
 +    EmpathyCallWindow *self)
 +{
 +  switch (response_id)
 +    {
 +      case GTK_RESPONSE_ACCEPT:
 +        tp_channel_dispatch_operation_handle_with_async (
 +            self->priv->pending_cdo, EMPATHY_CALL_BUS_NAME, NULL, NULL);
 +
 +        tp_clear_object (&self->priv->pending_cdo);
 +        tp_clear_object (&self->priv->pending_channel);
 +        tp_clear_object (&self->priv->pending_context);
 +
 +        break;
 +      case GTK_RESPONSE_CANCEL:
 +        tp_channel_dispatch_operation_close_channels_async (
 +            self->priv->pending_cdo, NULL, NULL);
 +
 +        empathy_call_window_status_message (self, _("Disconnected"));
 +        self->priv->call_state = DISCONNECTED;
 +        break;
 +      default:
 +        g_warn_if_reached ();
 +    }
 +}
 +
 +static void
 +empathy_call_window_set_state_ringing (EmpathyCallWindow *self)
 +{
 +  gboolean video;
 +
 +  g_assert (self->priv->call_state != CONNECTED);
 +
 +  video = tpy_call_channel_has_initial_video (self->priv->pending_channel);
 +
 +  empathy_call_window_status_message (self, _("Incoming call"));
 +  self->priv->call_state = RINGING;
 +
 +  self->priv->incoming_call_dialog = gtk_message_dialog_new (
 +      GTK_WINDOW (self), GTK_DIALOG_MODAL,
 +      GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
 +      video ? _("Incoming video call from %s") : _("Incoming call from %s"),
 +      empathy_contact_get_alias (self->priv->contact));
 +
 +  gtk_dialog_add_buttons (GTK_DIALOG (self->priv->incoming_call_dialog),
 +      _("Reject"), GTK_RESPONSE_CANCEL,
 +      _("Answer"), GTK_RESPONSE_ACCEPT,
 +      NULL);
 +
 +  g_signal_connect (self->priv->incoming_call_dialog, "response",
 +      G_CALLBACK (empathy_call_window_incoming_call_response_cb), self);
 +  gtk_widget_show (self->priv->incoming_call_dialog);
 +}
 +
 +static void
 +empathy_call_window_cdo_invalidated_cb (TpProxy *channel,
 +    guint domain,
 +    gint code,
 +    gchar *message,
 +    EmpathyCallWindow *self)
 +{
 +  tp_clear_object (&self->priv->pending_cdo);
 +  tp_clear_object (&self->priv->pending_channel);
 +  tp_clear_object (&self->priv->pending_context);
 +
 +  /* We don't know if the incoming call has been accepted or not, so we
 +   * assume it hasn't and if it has, we'll set the proper status when
 +   * we get the new handler. */
 +  empathy_call_window_status_message (self, _("Disconnected"));
 +  self->priv->call_state = DISCONNECTED;
 +
 +  gtk_widget_destroy (self->priv->incoming_call_dialog);
 +  self->priv->incoming_call_dialog = NULL;
 +}
 +
 +void
 +empathy_call_window_start_ringing (EmpathyCallWindow *self,
 +    TpyCallChannel *channel,
 +    TpChannelDispatchOperation *dispatch_operation,
 +    TpAddDispatchOperationContext *context)
 +{
 +  g_assert (self->priv->pending_channel == NULL);
 +  g_assert (self->priv->pending_context == NULL);
 +  g_assert (self->priv->pending_cdo == NULL);
 +
 +  /* Start ringing and delay until the user answers or hangs. */
 +  self->priv->pending_channel = g_object_ref (channel);
 +  self->priv->pending_context = g_object_ref (context);
 +  self->priv->pending_cdo = g_object_ref (dispatch_operation);
 +
 +  g_signal_connect (self->priv->pending_cdo, "invalidated",
 +      G_CALLBACK (empathy_call_window_cdo_invalidated_cb), self);
 +
 +  empathy_call_window_set_state_ringing (self);
 +  tp_add_dispatch_operation_context_accept (context);
 +}
 +
  static void
  empathy_call_window_init (EmpathyCallWindow *self)
  {
    priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
      EMPATHY_TYPE_CALL_WINDOW, EmpathyCallWindowPriv);
  
 +  priv->settings = g_settings_new (EMPATHY_PREFS_CALL_SCHEMA);
 +
    filename = empathy_file_lookup ("empathy-call-window.ui", "src");
    gui = empathy_builder_get_file (filename,
      "call_window_vbox", &top_vbox,
        G_CALLBACK (dtmf_button_pressed_cb),
        G_CALLBACK (dtmf_button_released_cb));
  
 +  priv->tones = g_string_new ("");
 +
    gtk_box_pack_start (GTK_BOX (priv->pane), priv->dtmf_panel,
        FALSE, FALSE, 6);
  
  
    empathy_call_window_show_hangup_button (self, TRUE);
  
 -  priv->settings = g_settings_new (EMPATHY_PREFS_CALL_SCHEMA);
 -
    /* Retrieve initial volume */
    priv->volume = g_settings_get_double (priv->settings,
        EMPATHY_PREFS_CALL_SOUND_VOLUME) / 100.0;
@@@ -2260,13 -1993,14 +2260,14 @@@ empathy_call_window_constructed (GObjec
    EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
    EmpathyCallWindowPriv *priv = GET_PRIV (self);
    TpyCallChannel *call;
+   TpyCallState state;
  
    g_assert (priv->handler != NULL);
  
    g_object_get (priv->handler, "call-channel", &call, NULL);
-   priv->outgoing = (call == NULL);
-   if (call != NULL)
-     g_object_unref (call);
+   state = tpy_call_channel_get_state (call, NULL, NULL);
+   priv->outgoing = (state == TPY_CALL_STATE_PENDING_INITIATOR);
+   tp_clear_object (&call);
  
    g_object_get (priv->handler, "target-contact", &priv->contact, NULL);
    g_assert (priv->contact != NULL);
@@@ -2455,8 -2189,6 +2456,8 @@@ empathy_call_window_finalize (GObject *
  
    g_timer_destroy (priv->timer);
  
 +  g_string_free (priv->tones, TRUE);
 +
    G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
  }
  
@@@ -2468,20 -2200,6 +2469,20 @@@ empathy_call_window_new (EmpathyCallHan
      g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
  }
  
 +void
 +empathy_call_window_present (EmpathyCallWindow *self,
 +    EmpathyCallHandler *handler)
 +{
 +  g_return_if_fail (EMPATHY_IS_CALL_HANDLER (handler));
 +
 +  tp_clear_object (&self->priv->handler);
 +  self->priv->handler = g_object_ref (handler);
 +  empathy_call_window_connect_handler (self);
 +
 +  empathy_window_present (GTK_WINDOW (self));
 +  empathy_call_window_restart_call (self);
 +}
 +
  static void
  empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
    GstElement *conference, gpointer user_data)
@@@ -2607,9 -2325,6 +2608,9 @@@ empathy_call_window_disconnected (Empat
    gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
    gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
  
 +  priv->sending_tones = FALSE;
 +  g_string_set_size (priv->tones, 0);
 +
    could_reset_pipeline = empathy_call_window_reset_pipeline (self);
  
    if (priv->call_state == CONNECTING)
@@@ -3426,7 -3141,7 +3427,7 @@@ empathy_call_window_bus_message (GstBu
  {
    EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
    EmpathyCallWindowPriv *priv = GET_PRIV (self);
 -  GstState newstate;
 +  GstState newstate, pending;
  
    empathy_call_handler_bus_message (priv->handler, bus, message);
  
                    start_call (self);
                }
            }
 +        if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_preview_sink))
 +          {
 +            gst_message_parse_state_changed (message, NULL, &newstate,
 +                &pending);
 +
 +            if (newstate == GST_STATE_PLAYING &&
 +                pending == GST_STATE_VOID_PENDING)
 +              empathy_call_window_stop_camera_spinning (self);
 +          }
          break;
        case GST_MESSAGE_ERROR:
          {
@@@ -3563,63 -3269,50 +3564,63 @@@ call_handler_notify_call_cb (EmpathyCal
    tp_g_signal_connect_object (call, "members-changed",
        G_CALLBACK (empathy_call_window_members_changed_cb), self, 0);
  
 +  tp_cli_channel_interface_dtmf_connect_to_stopped_tones (TP_CHANNEL (call),
 +      empathy_call_window_tones_stopped_cb, self, NULL,
 +      G_OBJECT (call), NULL);
 +
    g_object_unref (call);
  }
  
  static void
 -empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
 +empathy_call_window_connect_handler (EmpathyCallWindow *self)
  {
 -  EmpathyCallWindowPriv *priv = GET_PRIV (window);
 +  EmpathyCallWindowPriv *priv = GET_PRIV (self);
    TpyCallChannel *call;
 -  gint width;
 -
 -  /* Make the hangup button twice as wide */
 -  width = gtk_widget_get_allocated_width (priv->hangup_button);
 -  gtk_widget_set_size_request (priv->hangup_button, width * 2, -1);
  
    g_signal_connect (priv->handler, "state-changed",
 -    G_CALLBACK (empathy_call_window_state_changed_cb), window);
 +    G_CALLBACK (empathy_call_window_state_changed_cb), self);
    g_signal_connect (priv->handler, "conference-added",
 -    G_CALLBACK (empathy_call_window_conference_added_cb), window);
 +    G_CALLBACK (empathy_call_window_conference_added_cb), self);
    g_signal_connect (priv->handler, "conference-removed",
 -    G_CALLBACK (empathy_call_window_conference_removed_cb), window);
 +    G_CALLBACK (empathy_call_window_conference_removed_cb), self);
    g_signal_connect (priv->handler, "closed",
 -    G_CALLBACK (empathy_call_window_channel_closed_cb), window);
 +    G_CALLBACK (empathy_call_window_channel_closed_cb), self);
    g_signal_connect (priv->handler, "src-pad-added",
 -    G_CALLBACK (empathy_call_window_src_added_cb), window);
 +    G_CALLBACK (empathy_call_window_src_added_cb), self);
    g_signal_connect (priv->handler, "sink-pad-added",
 -    G_CALLBACK (empathy_call_window_sink_added_cb), window);
 +    G_CALLBACK (empathy_call_window_sink_added_cb), self);
    g_signal_connect (priv->handler, "sink-pad-removed",
 -    G_CALLBACK (empathy_call_window_sink_removed_cb), window);
 +    G_CALLBACK (empathy_call_window_sink_removed_cb), self);
 +
 +  /* We connect to ::call-channel unconditionally since we'll
 +   * get new channels if we hangup and redial or if we reuse the
 +   * call window. */
 +  g_signal_connect (priv->handler, "notify::call-channel",
 +    G_CALLBACK (call_handler_notify_call_cb), self);
  
    g_object_get (priv->handler, "call-channel", &call, NULL);
    if (call != NULL)
      {
 -      call_handler_notify_call_cb (priv->handler, NULL, window);
 +      /* We won't get notify::call-channel for this channel, so
 +       * directly call the callback. */
 +      call_handler_notify_call_cb (priv->handler, NULL, self);
        g_object_unref (call);
      }
 -  else
 -    {
 -      /* call-channel doesn't exist yet, we'll connect signals once it has been
 -       * set */
 -      g_signal_connect (priv->handler, "notify::call-channel",
 -        G_CALLBACK (call_handler_notify_call_cb), window);
 -    }
 +}
 +
 +static void
 +empathy_call_window_realized_cb (GtkWidget *widget,
 +    EmpathyCallWindow *self)
 +{
 +  gint width;
 +
 +  /* Make the hangup button twice as wide */
 +  width = gtk_widget_get_allocated_width (self->priv->hangup_button);
 +  gtk_widget_set_size_request (self->priv->hangup_button, width * 2, -1);
 +
 +  empathy_call_window_connect_handler (self);
  
 -  gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
 +  gst_element_set_state (self->priv->pipeline, GST_STATE_PAUSED);
  }
  
  static gboolean