X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=src%2Fempathy-call-window.c;h=2a92e947168b3d395cd17c253ed1de264d8d5039;hp=05f63e2f6813197bd3fa569e4b3fb96cbf21c03d;hb=7b6b8da406493311445f6c2470a005a542972693;hpb=2f9a520747ceacc89e5d6d923ed0cda8da22b95c diff --git a/src/empathy-call-window.c b/src/empathy-call-window.c index 05f63e2f..2a92e947 100644 --- a/src/empathy-call-window.c +++ b/src/empathy-call-window.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -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 { @@ -152,6 +154,8 @@ struct _EmpathyCallWindowPriv 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; @@ -184,11 +188,22 @@ struct _EmpathyCallWindowPriv 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 */ @@ -293,6 +308,8 @@ static gboolean empathy_call_window_video_output_motion_notify ( 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); @@ -335,35 +352,72 @@ empathy_call_window_video_call_cb (GtkToggleToolButton *button, } 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 @@ -700,6 +754,55 @@ empathy_call_window_show_preview_rectangles (EmpathyCallWindow *self, 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, @@ -899,6 +1002,13 @@ empathy_call_window_preview_on_drag_begin_cb (ClutterDragAction *action, 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, @@ -908,18 +1018,30 @@ empathy_call_window_preview_on_drag_end_cb (ClutterDragAction *action, 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); @@ -993,6 +1115,7 @@ create_video_preview (EmpathyCallWindow *self) ClutterAction *action; GtkWidget *button; PreviewPosition pos; + GdkRGBA transparent = { 0., 0., 0., 0. }; g_assert (priv->video_preview == NULL); @@ -1013,6 +1136,28 @@ create_video_preview (EmpathyCallWindow *self) 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. */ @@ -1024,6 +1169,8 @@ create_video_preview (EmpathyCallWindow *self) 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, @@ -1102,24 +1249,43 @@ create_video_preview (EmpathyCallWindow *self) 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; @@ -1357,6 +1523,105 @@ empathy_call_window_stage_allocation_changed_cb (ClutterActor *stage, 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) { @@ -1373,6 +1638,8 @@ 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, @@ -1556,6 +1823,8 @@ empathy_call_window_init (EmpathyCallWindow *self) 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); @@ -1608,8 +1877,6 @@ empathy_call_window_init (EmpathyCallWindow *self) 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; @@ -2189,6 +2456,8 @@ empathy_call_window_finalize (GObject *object) g_timer_destroy (priv->timer); + g_string_free (priv->tones, TRUE); + G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object); } @@ -2200,6 +2469,20 @@ empathy_call_window_new (EmpathyCallHandler *handler) 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) @@ -2325,6 +2608,9 @@ empathy_call_window_disconnected (EmpathyCallWindow *self, 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) @@ -3141,7 +3427,7 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message, { 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); @@ -3165,6 +3451,15 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *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: { @@ -3269,50 +3564,63 @@ call_handler_notify_call_cb (EmpathyCallHandler *handler, 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