X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=src%2Fempathy-call-window.c;h=2a92e947168b3d395cd17c253ed1de264d8d5039;hp=78ff5cb570b29d2875b3033ef22a7d17605ee99a;hb=7b6b8da406493311445f6c2470a005a542972693;hpb=ca2cb730d68620a638ef41d09cd25984e6cb4008 diff --git a/src/empathy-call-window.c b/src/empathy-call-window.c index 78ff5cb5..2a92e947 100644 --- a/src/empathy-call-window.c +++ b/src/empathy-call-window.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -65,13 +66,16 @@ #include "empathy-mic-menu.h" #include "empathy-preferences.h" #include "empathy-rounded-actor.h" +#include "empathy-rounded-rectangle.h" +#include "empathy-rounded-texture.h" +#include "empathy-camera-menu.h" #define CONTENT_HBOX_BORDER_WIDTH 6 #define CONTENT_HBOX_SPACING 3 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3 #define SELF_VIDEO_SECTION_WIDTH 120 -#define SELF_VIDEO_SECTION_HEIGTH 90 +#define SELF_VIDEO_SECTION_HEIGHT 90 #define SELF_VIDEO_SECTION_MARGIN 10 #define FLOATING_TOOLBAR_OPACITY 192 @@ -79,8 +83,6 @@ #define FLOATING_TOOLBAR_HEIGHT 36 #define FLOATING_TOOLBAR_SPACING 20 -#define SELF_VIDEO_MARGIN 10 - /* 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 @@ -102,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 { @@ -140,11 +143,19 @@ struct _EmpathyCallWindowPriv * alive only during call. */ ClutterActor *video_output; ClutterActor *video_preview; + ClutterActor *drag_preview; + ClutterActor *preview_shown_button; ClutterActor *preview_hidden_button; ClutterActor *preview_rectangle1; ClutterActor *preview_rectangle2; ClutterActor *preview_rectangle3; ClutterActor *preview_rectangle4; + ClutterActor *preview_rectangle_box1; + 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; @@ -161,6 +172,7 @@ struct _EmpathyCallWindowPriv ClutterActor *floating_toolbar; GtkWidget *pane; GtkAction *menu_fullscreen; + GtkAction *menu_swap_camera; ClutterState *transitions; @@ -170,18 +182,28 @@ struct _EmpathyCallWindowPriv ClutterLayoutManager *video_layout; /* Coordinates of the preview drag event's start. */ - gfloat event_x; - gfloat event_y; + PreviewPosition preview_pos; /* We keep a reference on the hbox which contains the main content so we can 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 */ @@ -246,6 +268,7 @@ struct _EmpathyCallWindowPriv GSettings *settings; EmpathyMicMenu *mic_menu; + EmpathyCameraMenu *camera_menu; }; #define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv) @@ -285,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); @@ -327,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 @@ -391,6 +453,17 @@ empathy_call_window_prefs_volume_changed_cb (GSettings *settings, empathy_call_window_mic_volume_changed (self); } +static void +empathy_call_window_raise_actors (EmpathyCallWindow *self) +{ + clutter_actor_raise_top (self->priv->floating_toolbar); + + clutter_actor_raise_top (self->priv->preview_rectangle_box1); + clutter_actor_raise_top (self->priv->preview_rectangle_box2); + clutter_actor_raise_top (self->priv->preview_rectangle_box3); + clutter_actor_raise_top (self->priv->preview_rectangle_box4); +} + static void empathy_call_window_show_video_output (EmpathyCallWindow *self, gboolean show) @@ -400,7 +473,7 @@ empathy_call_window_show_video_output (EmpathyCallWindow *self, gtk_widget_set_visible (self->priv->remote_user_avatar_widget, !show); - clutter_actor_raise_top (self->priv->floating_toolbar); + empathy_call_window_raise_actors (self); } static void @@ -514,6 +587,67 @@ empathy_call_window_maximise_camera_cb (GtkAction *action, clutter_actor_hide (self->priv->preview_hidden_button); } +static void +empathy_call_window_swap_camera_cb (GtkAction *action, + EmpathyCallWindow *self) +{ + const GList *cameras, *l; + gchar *current_cam; + + DEBUG ("Swapping the camera"); + + cameras = empathy_camera_monitor_get_cameras (self->priv->camera_monitor); + current_cam = empathy_video_src_dup_device ( + EMPATHY_GST_VIDEO_SRC (self->priv->video_input)); + + for (l = cameras; l != NULL; l = l->next) + { + EmpathyCamera *camera = l->data; + + if (!tp_strdiff (camera->device, current_cam)) + { + EmpathyCamera *next; + + if (l->next != NULL) + next = l->next->data; + else + next = cameras->data; + + /* EmpathyCameraMenu will update itself and do the actual change + * for us */ + g_settings_set_string (self->priv->settings, + EMPATHY_PREFS_CALL_CAMERA_DEVICE, + next->device); + + break; + } + } + + g_free (current_cam); +} + +static void +empathy_call_window_camera_added_cb (EmpathyCameraMonitor *monitor, + EmpathyCamera *camera, + EmpathyCallWindow *self) +{ + const GList *cameras = empathy_camera_monitor_get_cameras (monitor); + + gtk_action_set_visible (self->priv->menu_swap_camera, + g_list_length ((GList *) cameras) >= 2); +} + +static void +empathy_call_window_camera_removed_cb (EmpathyCameraMonitor *monitor, + EmpathyCamera *camera, + EmpathyCallWindow *self) +{ + const GList *cameras = empathy_camera_monitor_get_cameras (monitor); + + gtk_action_set_visible (self->priv->menu_swap_camera, + g_list_length ((GList *) cameras) >= 2); +} + static void empathy_call_window_preview_button_clicked_cb (GtkButton *button, EmpathyCallWindow *self) @@ -542,35 +676,47 @@ empathy_call_window_preview_hidden_button_clicked_cb (GtkButton *button, static ClutterActor * empathy_call_window_create_preview_rectangle (EmpathyCallWindow *self, + ClutterActor **box, ClutterBinAlignment x, ClutterBinAlignment y) { - ClutterLayoutManager *layout; + ClutterLayoutManager *layout1, *layout2; ClutterActor *rectangle; - ClutterActor *box; + ClutterActor *box1, *box2; - layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, - CLUTTER_BIN_ALIGNMENT_CENTER); + layout1 = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_BIN_ALIGNMENT_START); - box = clutter_box_new (layout); + box1 = clutter_box_new (layout1); - rectangle = clutter_rectangle_new_with_color ( - CLUTTER_COLOR_Transparent); + *box = box1; - clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (rectangle), - 1); + rectangle = empathy_rounded_rectangle_new ( + SELF_VIDEO_SECTION_WIDTH + 5, + SELF_VIDEO_SECTION_HEIGHT + 5); - clutter_actor_set_size (box, - SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_MARGIN, - SELF_VIDEO_SECTION_HEIGTH + 2 * SELF_VIDEO_MARGIN); + clutter_actor_set_size (box1, + SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_SECTION_MARGIN, + SELF_VIDEO_SECTION_HEIGHT + 2 * SELF_VIDEO_SECTION_MARGIN + + FLOATING_TOOLBAR_HEIGHT + FLOATING_TOOLBAR_SPACING); - clutter_actor_set_size (rectangle, - SELF_VIDEO_SECTION_WIDTH + 5, SELF_VIDEO_SECTION_HEIGTH + 5); + layout2 = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_BIN_ALIGNMENT_CENTER); - clutter_container_add_actor (CLUTTER_CONTAINER (box), rectangle); + /* 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. */ + box2 = clutter_box_new (layout2); + + clutter_actor_set_size (box2, + SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_SECTION_MARGIN, + SELF_VIDEO_SECTION_HEIGHT + 2 * SELF_VIDEO_SECTION_MARGIN); + + clutter_container_add_actor (CLUTTER_CONTAINER (box1), box2); + clutter_container_add_actor (CLUTTER_CONTAINER (box2), rectangle); clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (self->priv->video_layout), - box, x, y); + box1, x, y); clutter_actor_hide (rectangle); @@ -582,15 +728,19 @@ empathy_call_window_create_preview_rectangles (EmpathyCallWindow *self) { self->priv->preview_rectangle1 = empathy_call_window_create_preview_rectangle (self, + &self->priv->preview_rectangle_box1, CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_START); self->priv->preview_rectangle2 = empathy_call_window_create_preview_rectangle (self, + &self->priv->preview_rectangle_box2, CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_END); self->priv->preview_rectangle3 = empathy_call_window_create_preview_rectangle (self, + &self->priv->preview_rectangle_box3, CLUTTER_BIN_ALIGNMENT_END, CLUTTER_BIN_ALIGNMENT_START); self->priv->preview_rectangle4 = empathy_call_window_create_preview_rectangle (self, + &self->priv->preview_rectangle_box4, CLUTTER_BIN_ALIGNMENT_END, CLUTTER_BIN_ALIGNMENT_END); } @@ -604,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, @@ -612,33 +811,36 @@ empathy_call_window_get_preview_position (EmpathyCallWindow *self, ClutterGeometry box; PreviewPosition pos = PREVIEW_POS_NONE; + if (!clutter_actor_has_allocation (self->priv->video_box)) + return pos; + clutter_actor_get_geometry (self->priv->video_box, &box); - if (0 + SELF_VIDEO_MARGIN <= event_x && - event_x <= (0 + SELF_VIDEO_MARGIN + (gint) SELF_VIDEO_SECTION_WIDTH) && - 0 + SELF_VIDEO_MARGIN <= event_y && - event_y <= (0 + SELF_VIDEO_MARGIN + (gint) SELF_VIDEO_SECTION_HEIGTH)) + if (0 + SELF_VIDEO_SECTION_MARGIN <= event_x && + event_x <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_WIDTH) && + 0 + SELF_VIDEO_SECTION_MARGIN <= event_y && + event_y <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_HEIGHT)) { pos = PREVIEW_POS_TOP_LEFT; } - else if (box.width - SELF_VIDEO_MARGIN >= event_x && - event_x >= (box.width - SELF_VIDEO_MARGIN - (gint) SELF_VIDEO_SECTION_WIDTH) && - 0 + SELF_VIDEO_MARGIN <= event_y && - event_y <= (0 + SELF_VIDEO_MARGIN + (gint) SELF_VIDEO_SECTION_HEIGTH)) + else if (box.width - SELF_VIDEO_SECTION_MARGIN >= event_x && + event_x >= (box.width - SELF_VIDEO_SECTION_MARGIN - (gint) SELF_VIDEO_SECTION_WIDTH) && + 0 + SELF_VIDEO_SECTION_MARGIN <= event_y && + event_y <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_HEIGHT)) { pos = PREVIEW_POS_TOP_RIGHT; } - else if (0 + SELF_VIDEO_MARGIN <= event_x && - event_x <= (0 + SELF_VIDEO_MARGIN + (gint) SELF_VIDEO_SECTION_WIDTH) && - box.height - SELF_VIDEO_MARGIN >= event_y && - event_y >= (box.height - SELF_VIDEO_MARGIN - (gint) SELF_VIDEO_SECTION_HEIGTH)) + else if (0 + SELF_VIDEO_SECTION_MARGIN <= event_x && + event_x <= (0 + SELF_VIDEO_SECTION_MARGIN + (gint) SELF_VIDEO_SECTION_WIDTH) && + box.height - SELF_VIDEO_SECTION_MARGIN - FLOATING_TOOLBAR_HEIGHT - FLOATING_TOOLBAR_SPACING >= event_y && + event_y >= (box.height - SELF_VIDEO_SECTION_MARGIN - FLOATING_TOOLBAR_HEIGHT - FLOATING_TOOLBAR_SPACING - (gint) SELF_VIDEO_SECTION_HEIGHT)) { pos = PREVIEW_POS_BOTTOM_LEFT; } - else if (box.width - SELF_VIDEO_MARGIN >= event_x && - event_x >= (box.width - SELF_VIDEO_MARGIN - (gint) SELF_VIDEO_SECTION_WIDTH) && - box.height - SELF_VIDEO_MARGIN >= event_y && - event_y >= (box.height - SELF_VIDEO_MARGIN - (gint) SELF_VIDEO_SECTION_HEIGTH)) + else if (box.width - SELF_VIDEO_SECTION_MARGIN >= event_x && + event_x >= (box.width - SELF_VIDEO_SECTION_MARGIN - (gint) SELF_VIDEO_SECTION_WIDTH) && + box.height - SELF_VIDEO_SECTION_MARGIN - SELF_VIDEO_SECTION_MARGIN - FLOATING_TOOLBAR_HEIGHT - FLOATING_TOOLBAR_SPACING >= event_y && + event_y >= (box.height - SELF_VIDEO_SECTION_MARGIN - FLOATING_TOOLBAR_HEIGHT - FLOATING_TOOLBAR_SPACING - (gint) SELF_VIDEO_SECTION_HEIGHT)) { pos = PREVIEW_POS_BOTTOM_RIGHT; } @@ -681,6 +883,8 @@ empathy_call_window_move_video_preview (EmpathyCallWindow *self, DEBUG ("moving the video preview to %d", pos); + self->priv->preview_pos = pos; + switch (pos) { case PREVIEW_POS_TOP_LEFT: @@ -710,6 +914,8 @@ empathy_call_window_move_video_preview (EmpathyCallWindow *self, default: g_warn_if_reached (); } + + g_settings_set_enum (self->priv->settings, "camera-position", pos); } static void @@ -720,34 +926,48 @@ empathy_call_window_highlight_preview_rectangle (EmpathyCallWindow *self, rectangle = empathy_call_window_get_preview_rectangle (self, pos); - clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (rectangle), 3); - clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (rectangle), - CLUTTER_COLOR_Red); + empathy_rounded_rectangle_set_border_width ( + EMPATHY_ROUNDED_RECTANGLE (rectangle), 5); + empathy_rounded_rectangle_set_border_color ( + EMPATHY_ROUNDED_RECTANGLE (rectangle), CLUTTER_COLOR_Red); } static void empathy_call_window_darken_preview_rectangle (EmpathyCallWindow *self, ClutterActor *rectangle) { - clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (rectangle), 1); - clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (rectangle), - CLUTTER_COLOR_Black); + empathy_rounded_rectangle_set_border_width ( + EMPATHY_ROUNDED_RECTANGLE (rectangle), 1); + empathy_rounded_rectangle_set_border_color ( + EMPATHY_ROUNDED_RECTANGLE (rectangle), CLUTTER_COLOR_Black); } static void empathy_call_window_darken_preview_rectangles (EmpathyCallWindow *self) { - empathy_call_window_darken_preview_rectangle (self, - self->priv->preview_rectangle1); + ClutterActor *rectangle; + + rectangle = empathy_call_window_get_preview_rectangle (self, + self->priv->preview_pos); + + /* We don't want to darken the rectangle where the preview + * currently is. */ - empathy_call_window_darken_preview_rectangle (self, - self->priv->preview_rectangle2); + if (self->priv->preview_rectangle1 != rectangle) + empathy_call_window_darken_preview_rectangle (self, + self->priv->preview_rectangle1); - empathy_call_window_darken_preview_rectangle (self, - self->priv->preview_rectangle3); + if (self->priv->preview_rectangle2 != rectangle) + empathy_call_window_darken_preview_rectangle (self, + self->priv->preview_rectangle2); - empathy_call_window_darken_preview_rectangle (self, - self->priv->preview_rectangle4); + if (self->priv->preview_rectangle3 != rectangle) + empathy_call_window_darken_preview_rectangle (self, + self->priv->preview_rectangle3); + + if (self->priv->preview_rectangle4 != rectangle) + empathy_call_window_darken_preview_rectangle (self, + self->priv->preview_rectangle4); } static void @@ -758,10 +978,35 @@ empathy_call_window_preview_on_drag_begin_cb (ClutterDragAction *action, ClutterModifierType modifiers, EmpathyCallWindow *self) { + ClutterActor *stage = clutter_actor_get_stage (actor); + gfloat rel_x, rel_y; + + self->priv->drag_preview = clutter_clone_new (actor); + + clutter_container_add_actor (CLUTTER_CONTAINER (stage), + self->priv->drag_preview); + + clutter_actor_transform_stage_point (actor, event_x, event_y, + &rel_x, &rel_y); + + clutter_actor_set_position (self->priv->drag_preview, + event_x - rel_x, event_y - rel_y); + + clutter_drag_action_set_drag_handle (action, + self->priv->drag_preview); + + clutter_actor_set_opacity (actor, 0); + clutter_actor_hide (self->priv->preview_shown_button); + empathy_call_window_show_preview_rectangles (self, TRUE); + empathy_call_window_darken_preview_rectangles (self); +} - self->priv->event_x = event_x; - self->priv->event_y = event_y; +static void +empathy_call_window_on_animation_completed_cb (ClutterAnimation *animation, + ClutterActor *actor) +{ + clutter_actor_set_opacity (actor, 255); } static void @@ -773,9 +1018,31 @@ 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); + 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); + + self->priv->drag_preview = NULL; + if (pos != PREVIEW_POS_NONE) empathy_call_window_move_video_preview (self, pos); @@ -790,9 +1057,11 @@ empathy_call_window_preview_on_drag_motion_cb (ClutterDragAction *action, EmpathyCallWindow *self) { PreviewPosition pos; + gfloat event_x, event_y; - pos = empathy_call_window_get_preview_position (self, - self->priv->event_x - delta_x, self->priv->event_y + delta_y); + clutter_drag_action_get_motion_coords (action, &event_x, &event_y); + + pos = empathy_call_window_get_preview_position (self, event_x, event_y); if (pos != PREVIEW_POS_NONE) empathy_call_window_highlight_preview_rectangle (self, pos); @@ -806,12 +1075,12 @@ empathy_call_window_preview_enter_event_cb (ClutterActor *actor, EmpathyCallWindow *self) { ClutterActor *rectangle; - PreviewPosition pos; - pos = empathy_call_window_get_preview_position (self, event->x, event->y); - rectangle = empathy_call_window_get_preview_rectangle (self, pos); + rectangle = empathy_call_window_get_preview_rectangle (self, + self->priv->preview_pos); - empathy_call_window_highlight_preview_rectangle (self, pos); + empathy_call_window_highlight_preview_rectangle (self, + self->priv->preview_pos); clutter_actor_show (rectangle); @@ -824,10 +1093,9 @@ empathy_call_window_preview_leave_event_cb (ClutterActor *actor, EmpathyCallWindow *self) { ClutterActor *rectangle; - PreviewPosition pos; - pos = empathy_call_window_get_preview_position (self, event->x, event->y); - rectangle = empathy_call_window_get_preview_rectangle (self, pos); + rectangle = empathy_call_window_get_preview_rectangle (self, + self->priv->preview_pos); empathy_call_window_darken_preview_rectangle (self, rectangle); @@ -840,45 +1108,69 @@ static void create_video_preview (EmpathyCallWindow *self) { EmpathyCallWindowPriv *priv = GET_PRIV (self); - ClutterLayoutManager *layout, *layout_center; + ClutterLayoutManager *layout, *layout_center, *layout_end; ClutterActor *preview; ClutterActor *box; ClutterActor *b; ClutterAction *action; GtkWidget *button; + PreviewPosition pos; + GdkRGBA transparent = { 0., 0., 0., 0. }; g_assert (priv->video_preview == NULL); - preview = clutter_texture_new (); + pos = g_settings_get_enum (priv->settings, "camera-position"); + + preview = empathy_rounded_texture_new (); clutter_actor_set_size (preview, - SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH); + SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGHT); priv->video_preview_sink = clutter_gst_video_sink_new ( CLUTTER_TEXTURE (preview)); - /* Flip the video preview */ - clutter_actor_set_rotation (preview, - CLUTTER_Y_AXIS, - 180, - SELF_VIDEO_SECTION_WIDTH * 0.5, - 0.0, - 0.0); - /* Add a little offset to the video preview */ layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, - CLUTTER_BIN_ALIGNMENT_CENTER); + CLUTTER_BIN_ALIGNMENT_START); priv->video_preview = clutter_box_new (layout); clutter_actor_set_size (priv->video_preview, SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_SECTION_MARGIN, - SELF_VIDEO_SECTION_HEIGTH + 2 * SELF_VIDEO_SECTION_MARGIN + + 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. */ layout_center = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, CLUTTER_BIN_ALIGNMENT_CENTER); box = clutter_box_new (layout_center); clutter_actor_set_size (box, - SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH); + SELF_VIDEO_SECTION_WIDTH + 2 * SELF_VIDEO_SECTION_MARGIN, + 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, @@ -889,12 +1181,21 @@ create_video_preview (EmpathyCallWindow *self) /* Translators: this is an "Info" label. It should be as short * as possible. */ button = gtk_button_new_with_label (_("i")); - b = gtk_clutter_actor_new_with_contents (button); + priv->preview_shown_button = b = empathy_rounded_actor_new (); + gtk_container_add ( + GTK_CONTAINER (gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (b))), + button); + clutter_actor_set_size (b, 24, 24); - clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout_center), - b, - CLUTTER_BIN_ALIGNMENT_END, + layout_end = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_END, CLUTTER_BIN_ALIGNMENT_END); + box = clutter_box_new (layout_end); + clutter_actor_set_size (box, + SELF_VIDEO_SECTION_WIDTH, + SELF_VIDEO_SECTION_HEIGHT + SELF_VIDEO_SECTION_MARGIN); + + clutter_container_add_actor (CLUTTER_CONTAINER (box), b); + clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview), box); g_signal_connect (button, "clicked", G_CALLBACK (empathy_call_window_preview_button_clicked_cb), @@ -903,14 +1204,20 @@ create_video_preview (EmpathyCallWindow *self) /* Translators: this is an "Info" label. It should be as short * as possible. */ button = gtk_button_new_with_label (_("i")); - priv->preview_hidden_button = - gtk_clutter_actor_new_with_contents (button); + b = empathy_rounded_actor_new (); + gtk_container_add ( + GTK_CONTAINER (gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (b))), + button); + clutter_actor_set_size (b, 24, 24); + priv->preview_hidden_button = b; clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout), priv->preview_hidden_button, CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_END); + self->priv->preview_pos = PREVIEW_POS_BOTTOM_LEFT; + clutter_actor_hide (priv->preview_hidden_button); g_signal_connect (button, "clicked", @@ -922,6 +1229,8 @@ create_video_preview (EmpathyCallWindow *self) CLUTTER_BIN_ALIGNMENT_START, CLUTTER_BIN_ALIGNMENT_END); + empathy_call_window_move_video_preview (self, pos); + action = clutter_drag_action_new (); g_signal_connect (action, "drag-begin", G_CALLBACK (empathy_call_window_preview_on_drag_begin_cb), self); @@ -937,26 +1246,46 @@ create_video_preview (EmpathyCallWindow *self) clutter_actor_add_action (preview, action); clutter_actor_set_reactive (preview, TRUE); + 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 -play_camera (EmpathyCallWindow *window, +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 *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; @@ -976,7 +1305,7 @@ display_video_preview (EmpathyCallWindow *self, /* Display the video preview */ DEBUG ("Show video preview"); - play_camera (self, TRUE); + empathy_call_window_play_camera (self, TRUE); clutter_actor_show (priv->video_preview); clutter_actor_raise_top (priv->floating_toolbar); } @@ -988,7 +1317,7 @@ display_video_preview (EmpathyCallWindow *self, if (priv->video_preview != NULL) { clutter_actor_hide (priv->video_preview); - play_camera (self, FALSE); + empathy_call_window_play_camera (self, FALSE); } } } @@ -1001,6 +1330,9 @@ empathy_call_window_set_state_connecting (EmpathyCallWindow *window) empathy_call_window_status_message (window, _("Connecting…")); priv->call_state = CONNECTING; + /* Show the toolbar */ + clutter_state_set_state (priv->transitions, "fade-in"); + if (priv->outgoing) empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window), EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING); @@ -1117,7 +1449,11 @@ empathy_call_window_toolbar_timeout (gpointer data) { EmpathyCallWindow *self = data; - clutter_state_set_state (self->priv->transitions, "fade-out"); + /* We don't want to hide the toolbar if we're not in a call, as + * to show the call status all the time. */ + if (self->priv->call_state != CONNECTING && + self->priv->call_state != DISCONNECTED) + clutter_state_set_state (self->priv->transitions, "fade-out"); return TRUE; } @@ -1187,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) { @@ -1203,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, @@ -1221,6 +1658,7 @@ empathy_call_window_init (EmpathyCallWindow *self) "bottom_toolbar", &priv->bottom_toolbar, "ui_manager", &priv->ui_manager, "menufullscreen", &priv->menu_fullscreen, + "menupreviewswap", &priv->menu_swap_camera, "details_vbox", &priv->details_vbox, "vcodec_encoding_label", &priv->vcodec_encoding_label, "acodec_encoding_label", &priv->acodec_encoding_label, @@ -1253,6 +1691,7 @@ empathy_call_window_init (EmpathyCallWindow *self) "menupreviewdisable", "activate", empathy_call_window_disable_camera_cb, "menupreviewminimise", "activate", empathy_call_window_minimise_camera_cb, "menupreviewmaximise", "activate", empathy_call_window_maximise_camera_cb, + "menupreviewswap", "activate", empathy_call_window_swap_camera_cb, NULL); gtk_action_set_sensitive (priv->menu_fullscreen, FALSE); @@ -1263,6 +1702,11 @@ empathy_call_window_init (EmpathyCallWindow *self) priv->camera_button, "sensitive", G_BINDING_SYNC_CREATE); + g_signal_connect (priv->camera_monitor, "added", + G_CALLBACK (empathy_call_window_camera_added_cb), self); + g_signal_connect (priv->camera_monitor, "removed", + G_CALLBACK (empathy_call_window_camera_removed_cb), self); + priv->lock = g_mutex_new (); gtk_container_add (GTK_CONTAINER (self), top_vbox); @@ -1279,8 +1723,6 @@ empathy_call_window_init (EmpathyCallWindow *self) priv->video_box = clutter_box_new (priv->video_layout); - empathy_call_window_create_preview_rectangles (self); - priv->video_container = gtk_clutter_embed_new (); /* Set the background color to that of the rest of the window */ @@ -1314,6 +1756,8 @@ empathy_call_window_init (EmpathyCallWindow *self) clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box), remote_avatar); + empathy_call_window_create_preview_rectangles (self); + gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->video_container, TRUE, TRUE, CONTENT_HBOX_CHILDREN_PACKING_PADDING); @@ -1379,8 +1823,10 @@ 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, 0); + FALSE, FALSE, 6); gtk_box_pack_start (GTK_BOX (priv->pane), priv->details_vbox, FALSE, FALSE, 0); @@ -1427,11 +1873,10 @@ empathy_call_window_init (EmpathyCallWindow *self) priv->sound_mgr = empathy_sound_manager_dup_singleton (); priv->mic_menu = empathy_mic_menu_new (self); + priv->camera_menu = empathy_camera_menu_new (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; @@ -1815,17 +2260,24 @@ empathy_call_window_constructed (GObject *object) 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); + if (!empathy_contact_can_voip_video (priv->contact)) + { + gtk_widget_set_sensitive (priv->video_call_button, FALSE); + gtk_widget_set_sensitive (priv->camera_button, FALSE); + } + empathy_call_window_setup_avatars (self, priv->handler); empathy_call_window_set_state_connecting (self); @@ -1958,7 +2410,9 @@ empathy_call_window_dispose (GObject *object) tp_clear_object (&priv->fullscreen); tp_clear_object (&priv->camera_monitor); tp_clear_object (&priv->settings); - tp_clear_object (&priv->transitions); + tp_clear_object (&priv->sound_mgr); + tp_clear_object (&priv->mic_menu); + tp_clear_object (&priv->camera_menu); g_list_free_full (priv->notifiers, g_object_unref); @@ -1973,11 +2427,6 @@ empathy_call_window_dispose (GObject *object) priv->contact = NULL; } - - tp_clear_object (&priv->sound_mgr); - - tp_clear_object (&priv->mic_menu); - G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object); } @@ -2007,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); } @@ -2018,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) @@ -2091,6 +2556,15 @@ empathy_call_window_reset_pipeline (EmpathyCallWindow *self) clutter_actor_destroy (priv->video_preview); priv->video_preview = NULL; + /* If we destroy the preview while it's being dragged, we won't + * get the ::drag-end signal, so manually destroy the clone */ + if (priv->drag_preview != NULL) + { + clutter_actor_destroy (priv->drag_preview); + empathy_call_window_show_preview_rectangles (self, FALSE); + priv->drag_preview = NULL; + } + priv->funnel = NULL; create_pipeline (self); @@ -2134,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) @@ -2142,6 +2619,9 @@ empathy_call_window_disconnected (EmpathyCallWindow *self, if (priv->call_state != REDIALING) priv->call_state = DISCONNECTED; + /* Show the toolbar */ + clutter_state_set_state (priv->transitions, "fade-in"); + if (could_reset_pipeline) { g_mutex_lock (priv->lock); @@ -2682,7 +3162,7 @@ empathy_call_window_show_video_output_cb (gpointer user_data) { gtk_widget_hide (self->priv->remote_user_avatar_widget); clutter_actor_show (self->priv->video_output); - clutter_actor_raise_top (self->priv->floating_toolbar); + empathy_call_window_raise_actors (self); } return FALSE; @@ -2947,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); @@ -2971,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: { @@ -3075,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); - gst_element_set_state (priv->pipeline, GST_STATE_PAUSED); + empathy_call_window_connect_handler (self); + + gst_element_set_state (self->priv->pipeline, GST_STATE_PAUSED); } static gboolean @@ -3503,3 +4005,9 @@ empathy_call_window_get_audio_src (EmpathyCallWindow *window) return (EmpathyGstAudioSrc *) priv->audio_input; } + +EmpathyGstVideoSrc * +empathy_call_window_get_video_src (EmpathyCallWindow *self) +{ + return EMPATHY_GST_VIDEO_SRC (self->priv->video_input); +}