#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>
#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
};
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 {
CAMERA_STATE_ON,
} CameraState;
+typedef enum {
+ PREVIEW_POS_NONE,
+ PREVIEW_POS_TOP_LEFT,
+ PREVIEW_POS_TOP_RIGHT,
+ PREVIEW_POS_BOTTOM_LEFT,
+ PREVIEW_POS_BOTTOM_RIGHT,
+} PreviewPosition;
+
struct _EmpathyCallWindowPriv
{
gboolean dispose_has_run;
* 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;
ClutterActor *floating_toolbar;
GtkWidget *pane;
GtkAction *menu_fullscreen;
+ GtkAction *menu_swap_camera;
ClutterState *transitions;
ClutterActor *video_box;
ClutterLayoutManager *video_layout;
+ /* Coordinates of the preview drag event's start. */
+ 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 */
GSettings *settings;
EmpathyMicMenu *mic_menu;
+ EmpathyCameraMenu *camera_menu;
};
#define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv)
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);
}
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
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)
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
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)
gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
}
+static ClutterActor *
+empathy_call_window_create_preview_rectangle (EmpathyCallWindow *self,
+ ClutterActor **box,
+ ClutterBinAlignment x,
+ ClutterBinAlignment y)
+{
+ ClutterLayoutManager *layout1, *layout2;
+ ClutterActor *rectangle;
+ ClutterActor *box1, *box2;
+
+ layout1 = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
+ CLUTTER_BIN_ALIGNMENT_START);
+
+ box1 = clutter_box_new (layout1);
+
+ *box = box1;
+
+ rectangle = empathy_rounded_rectangle_new (
+ SELF_VIDEO_SECTION_WIDTH + 5,
+ SELF_VIDEO_SECTION_HEIGHT + 5);
+
+ 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);
+
+ layout2 = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
+ CLUTTER_BIN_ALIGNMENT_CENTER);
+
+ /* 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),
+ box1, x, y);
+
+ clutter_actor_hide (rectangle);
+
+ return rectangle;
+}
+
+static void
+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);
+}
+
+static void
+empathy_call_window_show_preview_rectangles (EmpathyCallWindow *self,
+ gboolean show)
+{
+ g_object_set (self->priv->preview_rectangle1, "visible", show, NULL);
+ g_object_set (self->priv->preview_rectangle2, "visible", show, NULL);
+ g_object_set (self->priv->preview_rectangle3, "visible", show, NULL);
+ 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,
+ gfloat event_y)
+{
+ 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_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_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_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_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;
+ }
+
+ return pos;
+}
+
+static ClutterActor *
+empathy_call_window_get_preview_rectangle (EmpathyCallWindow *self,
+ PreviewPosition pos)
+{
+ ClutterActor *rectangle;
+
+ switch (pos)
+ {
+ case PREVIEW_POS_TOP_LEFT:
+ rectangle = self->priv->preview_rectangle1;
+ break;
+ case PREVIEW_POS_TOP_RIGHT:
+ rectangle = self->priv->preview_rectangle3;
+ break;
+ case PREVIEW_POS_BOTTOM_LEFT:
+ rectangle = self->priv->preview_rectangle2;
+ break;
+ case PREVIEW_POS_BOTTOM_RIGHT:
+ rectangle = self->priv->preview_rectangle4;
+ break;
+ default:
+ rectangle = NULL;
+ }
+
+ return rectangle;
+}
+
+static void
+empathy_call_window_move_video_preview (EmpathyCallWindow *self,
+ PreviewPosition pos)
+{
+ ClutterBinLayout *layout = CLUTTER_BIN_LAYOUT (self->priv->video_layout);
+
+ DEBUG ("moving the video preview to %d", pos);
+
+ self->priv->preview_pos = pos;
+
+ switch (pos)
+ {
+ case PREVIEW_POS_TOP_LEFT:
+ clutter_bin_layout_set_alignment (layout,
+ self->priv->video_preview,
+ CLUTTER_BIN_ALIGNMENT_START,
+ CLUTTER_BIN_ALIGNMENT_START);
+ break;
+ case PREVIEW_POS_TOP_RIGHT:
+ clutter_bin_layout_set_alignment (layout,
+ self->priv->video_preview,
+ CLUTTER_BIN_ALIGNMENT_END,
+ CLUTTER_BIN_ALIGNMENT_START);
+ break;
+ case PREVIEW_POS_BOTTOM_LEFT:
+ clutter_bin_layout_set_alignment (layout,
+ self->priv->video_preview,
+ CLUTTER_BIN_ALIGNMENT_START,
+ CLUTTER_BIN_ALIGNMENT_END);
+ break;
+ case PREVIEW_POS_BOTTOM_RIGHT:
+ clutter_bin_layout_set_alignment (layout,
+ self->priv->video_preview,
+ CLUTTER_BIN_ALIGNMENT_END,
+ CLUTTER_BIN_ALIGNMENT_END);
+ break;
+ default:
+ g_warn_if_reached ();
+ }
+
+ g_settings_set_enum (self->priv->settings, "camera-position", pos);
+}
+
+static void
+empathy_call_window_highlight_preview_rectangle (EmpathyCallWindow *self,
+ PreviewPosition pos)
+{
+ ClutterActor *rectangle;
+
+ rectangle = empathy_call_window_get_preview_rectangle (self, pos);
+
+ 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)
+{
+ 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)
+{
+ 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. */
+
+ if (self->priv->preview_rectangle1 != rectangle)
+ empathy_call_window_darken_preview_rectangle (self,
+ self->priv->preview_rectangle1);
+
+ if (self->priv->preview_rectangle2 != rectangle)
+ empathy_call_window_darken_preview_rectangle (self,
+ self->priv->preview_rectangle2);
+
+ 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
+empathy_call_window_preview_on_drag_begin_cb (ClutterDragAction *action,
+ ClutterActor *actor,
+ gfloat event_x,
+ gfloat event_y,
+ 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);
+}
+
+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,
+ gfloat event_x,
+ gfloat event_y,
+ ClutterModifierType modifiers,
+ 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);
+
+ empathy_call_window_show_preview_rectangles (self, FALSE);
+}
+
+static void
+empathy_call_window_preview_on_drag_motion_cb (ClutterDragAction *action,
+ ClutterActor *actor,
+ gfloat delta_x,
+ gfloat delta_y,
+ EmpathyCallWindow *self)
+{
+ PreviewPosition pos;
+ gfloat event_x, event_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);
+ else
+ empathy_call_window_darken_preview_rectangles (self);
+}
+
+static gboolean
+empathy_call_window_preview_enter_event_cb (ClutterActor *actor,
+ ClutterCrossingEvent *event,
+ EmpathyCallWindow *self)
+{
+ ClutterActor *rectangle;
+
+ rectangle = empathy_call_window_get_preview_rectangle (self,
+ self->priv->preview_pos);
+
+ empathy_call_window_highlight_preview_rectangle (self,
+ self->priv->preview_pos);
+
+ clutter_actor_show (rectangle);
+
+ return FALSE;
+}
+
+static gboolean
+empathy_call_window_preview_leave_event_cb (ClutterActor *actor,
+ ClutterCrossingEvent *event,
+ EmpathyCallWindow *self)
+{
+ ClutterActor *rectangle;
+
+ rectangle = empathy_call_window_get_preview_rectangle (self,
+ self->priv->preview_pos);
+
+ empathy_call_window_darken_preview_rectangle (self, rectangle);
+
+ clutter_actor_hide (rectangle);
+
+ return FALSE;
+}
+
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_END,
+ layout = clutter_bin_layout_new (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 + SELF_VIDEO_SECTION_MARGIN,
- SELF_VIDEO_SECTION_HEIGTH + SELF_VIDEO_SECTION_MARGIN +
+ SELF_VIDEO_SECTION_WIDTH + 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,
/* 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),
/* 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",
priv->video_preview,
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);
+ g_signal_connect (action, "drag-end",
+ G_CALLBACK (empathy_call_window_preview_on_drag_end_cb), self);
+ g_signal_connect (action, "drag-motion",
+ G_CALLBACK (empathy_call_window_preview_on_drag_motion_cb), self);
+
+ g_signal_connect (preview, "enter-event",
+ G_CALLBACK (empathy_call_window_preview_enter_event_cb), self);
+ g_signal_connect (preview, "leave-event",
+ G_CALLBACK (empathy_call_window_preview_leave_event_cb), self);
+
+ clutter_actor_add_action (preview, action);
+ clutter_actor_set_reactive (preview, TRUE);
+ clutter_actor_set_reactive (priv->preview_shown_button, TRUE);
}
static void
-play_camera (EmpathyCallWindow *window,
+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 *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;
/* 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);
}
else
{
if (priv->video_preview != NULL)
{
clutter_actor_hide (priv->video_preview);
- play_camera (self, FALSE);
+ empathy_call_window_play_camera (self, FALSE);
}
}
}
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);
{
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;
}
}
static void
-empathy_call_window_video_box_allocation_changed_cb (ClutterActor *video_box,
+empathy_call_window_stage_allocation_changed_cb (ClutterActor *stage,
GParamSpec *pspec,
ClutterBindConstraint *constraint)
{
ClutterActorBox allocation;
- clutter_actor_get_allocation_box (video_box, &allocation);
+ clutter_actor_get_allocation_box (stage, &allocation);
clutter_bind_constraint_set_offset (constraint,
allocation.y2 - allocation.y1 -
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,
"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,
"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);
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);
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);
gtk_widget_reparent (priv->bottom_toolbar,
gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->floating_toolbar)));
- constraint = clutter_bind_constraint_new (priv->video_box,
+ constraint = clutter_bind_constraint_new (
+ gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)),
CLUTTER_BIND_Y, 0);
clutter_actor_add_constraint (priv->floating_toolbar, constraint);
- g_signal_connect (priv->video_box, "notify::allocation",
- G_CALLBACK (empathy_call_window_video_box_allocation_changed_cb),
+ g_signal_connect (
+ gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)),
+ "notify::allocation",
+ G_CALLBACK (empathy_call_window_stage_allocation_changed_cb),
constraint);
clutter_actor_set_size (priv->floating_toolbar,
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);
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;
+
g_signal_connect (priv->settings, "changed::"EMPATHY_PREFS_CALL_SOUND_VOLUME,
G_CALLBACK (empathy_call_window_prefs_volume_changed_cb), self);
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);
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);
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);
}
g_timer_destroy (priv->timer);
+ g_string_free (priv->tones, TRUE);
+
G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
}
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)
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);
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)
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);
{
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;
{
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:
{
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);
- }
+}
- gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+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 (self->priv->pipeline, GST_STATE_PAUSED);
}
static gboolean
static void
empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
- EmpathyCallWindow *window)
+ EmpathyCallWindow *self)
{
- EmpathyCallWindowPriv *priv = GET_PRIV (window);
+ EmpathyCallWindowPriv *priv = GET_PRIV (self);
gboolean active;
active = (gtk_toggle_tool_button_get_active (toggle));
+ /* We don't want the settings callback to react to this change to avoid
+ * a loop. */
+ g_signal_handlers_block_by_func (priv->settings,
+ empathy_call_window_prefs_volume_changed_cb, self);
+
if (active)
{
g_settings_set_double (priv->settings, EMPATHY_PREFS_CALL_SOUND_VOLUME,
g_settings_set_double (priv->settings, EMPATHY_PREFS_CALL_SOUND_VOLUME,
0);
}
+
+ g_signal_handlers_unblock_by_func (priv->settings,
+ empathy_call_window_prefs_volume_changed_cb, self);
}
static void
return (EmpathyGstAudioSrc *) priv->audio_input;
}
+
+EmpathyGstVideoSrc *
+empathy_call_window_get_video_src (EmpathyCallWindow *self)
+{
+ return EMPATHY_GST_VIDEO_SRC (self->priv->video_input);
+}