2 * empathy-call-window.c - Source for EmpathyCallWindow
3 * Copyright (C) 2008-2011 Collabora Ltd.
4 * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "empathy-call-window.h"
24 #include <glib/gi18n.h>
25 #include <clutter-gst/clutter-gst.h>
26 #include <telepathy-farstream/telepathy-farstream.h>
27 #include <farstream/fs-element-added-notifier.h>
28 #include <farstream/fs-utils.h>
29 #include <tp-account-widgets/tpaw-builder.h>
30 #include <tp-account-widgets/tpaw-camera-monitor.h>
31 #include <tp-account-widgets/tpaw-images.h>
32 #include <tp-account-widgets/tpaw-pixbuf-utils.h>
33 #include <tp-account-widgets/tpaw-utils.h>
34 #include <telepathy-glib/telepathy-glib-dbus.h>
36 #include "empathy-about-dialog.h"
37 #include "empathy-audio-sink.h"
38 #include "empathy-call-utils.h"
39 #include "empathy-call-window-fullscreen.h"
40 #include "empathy-camera-menu.h"
41 #include "empathy-dialpad-widget.h"
42 #include "empathy-geometry.h"
43 #include "empathy-gsettings.h"
44 #include "empathy-images.h"
45 #include "empathy-mic-menu.h"
46 #include "empathy-preferences.h"
47 #include "empathy-request-util.h"
48 #include "empathy-rounded-actor.h"
49 #include "empathy-rounded-rectangle.h"
50 #include "empathy-rounded-effect.h"
51 #include "empathy-sound-manager.h"
52 #include "empathy-ui-utils.h"
53 #include "empathy-utils.h"
55 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
56 #include "empathy-debug.h"
58 #define CONTENT_HBOX_SPACING 3
59 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 0
60 #define OVERLAY_MARGIN 6
62 #define REMOTE_VIDEO_DEFAULT_WIDTH 320
63 #define REMOTE_VIDEO_DEFAULT_HEIGHT 240
65 #define SELF_VIDEO_SECTION_WIDTH 120
66 #define SELF_VIDEO_SECTION_HEIGHT 90
67 #define SELF_VIDEO_SECTION_MARGIN 2
68 #define SELF_VIDEO_SECTION_BORDER SELF_VIDEO_SECTION_MARGIN*2
70 /* The avatar's default width and height are set to the same value because we
71 want a square icon. */
72 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT REMOTE_VIDEO_DEFAULT_HEIGHT
73 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH REMOTE_VIDEO_DEFAULT_HEIGHT
75 #define SMALL_TOOLBAR_SIZE 36
77 /* If an video input error occurs, the error message will start with "v4l" */
78 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
80 /* The time interval in milliseconds between 2 outgoing rings */
81 #define MS_BETWEEN_RING 500
83 /* The roundedness of preview box and placeholders */
84 #define PREVIEW_ROUND_FACTOR 16
86 #define PREVIEW_BUTTON_OPACITY 180
88 G_DEFINE_TYPE(EmpathyCallWindow
, empathy_call_window
, GTK_TYPE_WINDOW
)
91 PROP_CALL_HANDLER
= 1,
99 static guint signals
[LAST_SIGNAL
];
102 RINGING
, /* Incoming call */
103 CONNECTING
, /* Outgoing call */
104 CONNECTED
, /* Connected */
105 HELD
, /* Connected, but on hold */
106 DISCONNECTED
, /* Disconnected */
107 REDIALING
/* Redialing (special case of CONNECTING) */
111 CAMERA_STATE_OFF
= 0,
117 PREVIEW_POS_TOP_LEFT
,
118 PREVIEW_POS_TOP_RIGHT
,
119 PREVIEW_POS_BOTTOM_LEFT
,
120 PREVIEW_POS_BOTTOM_RIGHT
,
123 struct _EmpathyCallWindowPriv
125 gboolean dispose_has_run
;
126 EmpathyCallHandler
*handler
;
128 EmpathyContact
*contact
;
130 TpawCameraMonitor
*camera_monitor
;
132 CallState call_state
;
135 GtkUIManager
*ui_manager
;
136 GtkWidget
*errors_vbox
;
137 /* widget displays the video received from the remote user. This widget is
138 * alive only during call. */
139 ClutterActor
*video_output
;
140 ClutterActor
*video_preview
;
141 ClutterActor
*drag_preview
;
142 ClutterActor
*preview_shown_button
;
143 ClutterActor
*preview_hidden_button
;
144 ClutterActor
*preview_rectangle1
;
145 ClutterActor
*preview_rectangle2
;
146 ClutterActor
*preview_rectangle3
;
147 ClutterActor
*preview_rectangle4
;
148 ClutterActor
*preview_spinner_actor
;
149 ClutterContent
*preview_content
;
150 GtkWidget
*preview_spinner_widget
;
151 GtkWidget
*video_container
;
152 GtkWidget
*remote_user_avatar_widget
;
153 GtkWidget
*remote_user_avatar_toolbar
;
154 GtkWidget
*remote_user_name_toolbar
;
155 GtkWidget
*remote_user_status_toolbar
;
156 GtkWidget
*status_label
;
157 GtkWidget
*hangup_button
;
158 GtkWidget
*audio_call_button
;
159 GtkWidget
*video_call_button
;
160 GtkWidget
*mic_button
;
161 GtkWidget
*microphone_icon
;
162 GtkWidget
*volume_button
;
163 GtkWidget
*camera_button
;
164 GtkWidget
*dialpad_button
;
166 GtkWidget
*bottom_toolbar
;
167 ClutterActor
*floating_toolbar
;
169 GtkAction
*menu_fullscreen
;
170 GtkAction
*menu_swap_camera
;
172 ClutterState
*transitions
;
174 /* The main box covering all the stage, contaning remote avatar/video */
175 ClutterActor
*video_box
;
176 ClutterLayoutManager
*video_layout
;
178 /* A bin layout manager containing a bin for previews
179 * and the floating toolbar */
180 ClutterActor
*overlay_bin
;
181 ClutterLayoutManager
*overlay_layout
;
183 /* Bin layout for the previews */
184 ClutterActor
*preview_box
;
185 ClutterLayoutManager
*preview_layout
;
187 /* Coordinates of the preview drag event's start. */
188 PreviewPosition preview_pos
;
190 /* We keep a reference on the hbox which contains the main content so we can
191 easilly repack everything when toggling fullscreen */
192 GtkWidget
*content_hbox
;
194 /* These are used to accept or reject an incoming call when the status
196 GtkWidget
*incoming_call_dialog
;
197 TpCallChannel
*pending_channel
;
198 TpChannelDispatchOperation
*pending_cdo
;
199 TpAddDispatchOperationContext
*pending_context
;
201 gulong video_output_motion_handler_id
;
202 guint bus_message_source_id
;
204 GtkWidget
*dtmf_panel
;
207 GtkWidget
*details_vbox
;
208 GtkWidget
*vcodec_encoding_label
;
209 GtkWidget
*acodec_encoding_label
;
210 GtkWidget
*vcodec_decoding_label
;
211 GtkWidget
*acodec_decoding_label
;
213 GtkWidget
*audio_remote_candidate_label
;
214 GtkWidget
*audio_local_candidate_label
;
215 GtkWidget
*video_remote_candidate_label
;
216 GtkWidget
*video_local_candidate_label
;
217 GtkWidget
*video_remote_candidate_info_img
;
218 GtkWidget
*video_local_candidate_info_img
;
219 GtkWidget
*audio_remote_candidate_info_img
;
220 GtkWidget
*audio_local_candidate_info_img
;
222 GstElement
*video_input
;
223 GstElement
*video_preview_sink
;
224 GstElement
*video_output_sink
;
225 GstElement
*audio_input
;
226 GstElement
*audio_output
;
227 gboolean audio_output_added
;
228 GstElement
*pipeline
;
229 GstElement
*video_tee
;
239 gboolean call_started
;
240 gboolean sending_video
;
241 CameraState camera_state
;
243 EmpathyCallWindowFullscreen
*fullscreen
;
244 gboolean is_fullscreen
;
249 guint inactivity_src
;
251 /* Those fields represent the state of the window before it actually was in
253 gboolean dialpad_was_visible_before_fs
;
254 gint original_width_before_fs
;
255 gint original_height_before_fs
;
257 gint x
, y
, w
, h
, dialpad_width
;
260 /* TRUE if the call should be started when the pipeline is playing */
261 gboolean start_call_when_playing
;
262 /* TRUE if we requested to set the pipeline in the playing state */
263 gboolean pipeline_playing
;
265 EmpathySoundManager
*sound_mgr
;
268 EmpathyMicMenu
*mic_menu
;
269 EmpathyCameraMenu
*camera_menu
;
274 #define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv)
276 static void empathy_call_window_realized_cb (GtkWidget
*widget
,
277 EmpathyCallWindow
*window
);
279 static gboolean
empathy_call_window_delete_cb (GtkWidget
*widget
,
280 GdkEvent
*event
, EmpathyCallWindow
*window
);
282 static gboolean
empathy_call_window_state_event_cb (GtkWidget
*widget
,
283 GdkEventWindowState
*event
, EmpathyCallWindow
*window
);
285 static void empathy_call_window_set_send_video (EmpathyCallWindow
*window
,
288 static void empathy_call_window_hangup_cb (gpointer object
,
289 EmpathyCallWindow
*window
);
291 static void empathy_call_window_fullscreen_cb (gpointer object
,
292 EmpathyCallWindow
*window
);
294 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow
*window
);
296 static gboolean
empathy_call_window_video_button_press_cb (
297 GtkWidget
*video_output
, GdkEventButton
*event
, EmpathyCallWindow
*window
);
299 static gboolean
empathy_call_window_key_press_cb (GtkWidget
*video_output
,
300 GdkEventKey
*event
, EmpathyCallWindow
*window
);
302 static gboolean
empathy_call_window_video_output_motion_notify (
303 GtkWidget
*widget
, GdkEventMotion
*event
, EmpathyCallWindow
*window
);
305 static void empathy_call_window_video_menu_popup (EmpathyCallWindow
*window
,
308 static void empathy_call_window_connect_handler (EmpathyCallWindow
*self
);
310 static void empathy_call_window_dialpad_cb (GtkToggleToolButton
*button
,
311 EmpathyCallWindow
*window
);
313 static void empathy_call_window_restart_call (EmpathyCallWindow
*window
);
315 static void empathy_call_window_status_message (EmpathyCallWindow
*window
,
318 static gboolean
empathy_call_window_bus_message (GstBus
*bus
,
319 GstMessage
*message
, gpointer user_data
);
321 static gboolean
empathy_call_window_update_timer (gpointer user_data
);
324 make_background_transparent (GtkClutterActor
*actor
)
326 GdkRGBA transparent
= { 0., 0., 0., 0. };
329 widget
= gtk_clutter_actor_get_widget (actor
);
330 gtk_widget_override_background_color (widget
, GTK_STATE_FLAG_NORMAL
, &transparent
);
334 empathy_call_window_show_hangup_button (EmpathyCallWindow
*self
,
337 gtk_widget_set_visible (self
->priv
->hangup_button
, show
);
338 gtk_widget_set_visible (self
->priv
->audio_call_button
, !show
);
339 gtk_widget_set_visible (self
->priv
->video_call_button
, !show
);
343 empathy_call_window_audio_call_cb (GtkToggleToolButton
*button
,
344 EmpathyCallWindow
*self
)
346 g_object_set (self
->priv
->handler
, "initial-video", FALSE
, NULL
);
347 empathy_call_window_restart_call (self
);
351 empathy_call_window_video_call_cb (GtkToggleToolButton
*button
,
352 EmpathyCallWindow
*self
)
354 empathy_call_window_set_send_video (self
, CAMERA_STATE_ON
);
355 g_object_set (self
->priv
->handler
, "initial-video", TRUE
, NULL
);
356 empathy_call_window_restart_call (self
);
360 dtmf_start_tone_cb (EmpathyDialpadWidget
*dialpad
,
362 EmpathyCallWindow
*self
)
367 g_object_get (self
->priv
->handler
, "call-channel", &call
, NULL
);
369 tones
[0] = tp_dtmf_event_to_char (event
);
371 tp_call_channel_send_tones_async (call
, tones
, NULL
, NULL
, NULL
);
373 g_object_unref (call
);
377 empathy_call_window_show_video_output (EmpathyCallWindow
*self
,
380 if (self
->priv
->video_output
!= NULL
)
381 g_object_set (self
->priv
->video_output
, "visible", show
, NULL
);
383 gtk_widget_set_visible (self
->priv
->remote_user_avatar_widget
, !show
);
385 clutter_actor_raise_top (self
->priv
->overlay_bin
);
389 create_video_output_widget (EmpathyCallWindow
*self
)
391 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
393 g_assert (priv
->video_output
== NULL
);
394 g_assert (priv
->pipeline
!= NULL
);
396 priv
->video_output_sink
= GST_ELEMENT (clutter_gst_video_sink_new ());
397 priv
->video_output
= g_object_new (CLUTTER_TYPE_ACTOR
,
398 "content", g_object_new (CLUTTER_GST_TYPE_ASPECTRATIO
,
399 "sink", priv
->video_output_sink
,
403 clutter_container_add_actor (CLUTTER_CONTAINER (priv
->video_box
),
405 clutter_actor_add_constraint (priv
->video_output
,
406 clutter_bind_constraint_new (priv
->video_box
, CLUTTER_BIND_SIZE
, 0));
408 gtk_widget_add_events (priv
->video_container
,
409 GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK
);
410 g_signal_connect (G_OBJECT (priv
->video_container
), "button-press-event",
411 G_CALLBACK (empathy_call_window_video_button_press_cb
), self
);
415 create_video_input (EmpathyCallWindow
*self
)
417 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
419 g_assert (priv
->video_input
== NULL
);
420 priv
->video_input
= empathy_video_src_new ();
421 gst_object_ref_sink (priv
->video_input
);
425 audio_control_volume_to_element (GBinding
*binding
,
426 const GValue
*source_value
,
427 GValue
*target_value
,
430 /* AudioControl volume is 0-255, with -1 for unknown */
433 hv
= g_value_get_int (source_value
);
438 g_value_set_double (target_value
, hv
/255.0);
444 element_volume_to_audio_control (GBinding
*binding
,
445 const GValue
*source_value
,
446 GValue
*target_value
,
451 ev
= g_value_get_double (source_value
);
452 ev
= CLAMP (ev
, 0.0, 1.0);
454 g_value_set_int (target_value
, ev
* 255);
459 audio_input_mute_notify_cb (GObject
*obj
, GParamSpec
*spec
,
460 EmpathyCallWindow
*self
)
463 g_object_get (obj
, "mute", &muted
, NULL
);
465 self
->priv
->muted
= muted
;
466 if (muted
&& self
->priv
->transitions
)
467 clutter_state_set_state (self
->priv
->transitions
, "fade-in");
471 gtk_image_set_from_icon_name (GTK_IMAGE (self
->priv
->microphone_icon
),
472 EMPATHY_IMAGE_MIC_MUTED
, GTK_ICON_SIZE_MENU
);
476 gtk_image_set_from_icon_name (GTK_IMAGE (self
->priv
->microphone_icon
),
477 EMPATHY_IMAGE_MIC
, GTK_ICON_SIZE_MENU
);
480 empathy_call_window_update_timer (self
);
484 create_audio_input (EmpathyCallWindow
*self
)
486 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
488 g_assert (priv
->audio_input
== NULL
);
489 priv
->audio_input
= empathy_audio_src_new ();
490 gst_object_ref_sink (priv
->audio_input
);
492 g_signal_connect (priv
->audio_input
, "notify::mute",
493 G_CALLBACK (audio_input_mute_notify_cb
), self
);
497 add_video_preview_to_pipeline (EmpathyCallWindow
*self
)
499 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
502 g_assert (priv
->video_preview
!= NULL
);
503 g_assert (priv
->pipeline
!= NULL
);
504 g_assert (priv
->video_input
!= NULL
);
505 g_assert (priv
->video_tee
!= NULL
);
507 preview
= priv
->video_preview_sink
;
509 if (!gst_bin_add (GST_BIN (priv
->pipeline
), priv
->video_input
))
511 g_warning ("Could not add video input to pipeline");
515 if (!gst_bin_add (GST_BIN (priv
->pipeline
), preview
))
517 g_warning ("Could not add video preview to pipeline");
521 if (!gst_element_link (priv
->video_input
, priv
->video_tee
))
523 g_warning ("Could not link video input to video tee");
527 if (!gst_element_link (priv
->video_tee
, preview
))
529 g_warning ("Could not link video tee to video preview");
535 empathy_call_window_disable_camera_cb (GtkAction
*action
,
536 EmpathyCallWindow
*self
)
538 clutter_actor_destroy (self
->priv
->preview_hidden_button
);
540 gtk_toggle_button_set_active (
541 GTK_TOGGLE_BUTTON (self
->priv
->camera_button
), FALSE
);
545 empathy_call_window_minimise_camera_cb (GtkAction
*action
,
546 EmpathyCallWindow
*self
)
548 clutter_actor_hide (self
->priv
->video_preview
);
549 clutter_actor_show (self
->priv
->preview_hidden_button
);
553 empathy_call_window_maximise_camera_cb (GtkAction
*action
,
554 EmpathyCallWindow
*self
)
556 clutter_actor_show (self
->priv
->video_preview
);
557 clutter_actor_hide (self
->priv
->preview_hidden_button
);
561 empathy_call_window_swap_camera_cb (GtkAction
*action
,
562 EmpathyCallWindow
*self
)
564 const GList
*cameras
, *l
;
567 DEBUG ("Swapping the camera");
569 cameras
= tpaw_camera_monitor_get_cameras (self
->priv
->camera_monitor
);
570 current_cam
= empathy_video_src_dup_device (
571 EMPATHY_GST_VIDEO_SRC (self
->priv
->video_input
));
573 for (l
= cameras
; l
!= NULL
; l
= l
->next
)
575 TpawCamera
*camera
= l
->data
;
577 if (!tp_strdiff (camera
->device
, current_cam
))
582 next
= l
->next
->data
;
584 next
= cameras
->data
;
586 /* EmpathyCameraMenu will update itself and do the actual change
588 g_settings_set_string (self
->priv
->settings
,
589 EMPATHY_PREFS_CALL_CAMERA_DEVICE
,
596 g_free (current_cam
);
600 empathy_call_window_update_swap_camera (EmpathyCallWindow
*self
)
602 const GList
*cameras
= tpaw_camera_monitor_get_cameras (
603 self
->priv
->camera_monitor
);
605 gtk_action_set_visible (self
->priv
->menu_swap_camera
,
606 g_list_length ((GList
*) cameras
) >= 2);
610 empathy_call_window_camera_added_cb (TpawCameraMonitor
*monitor
,
612 EmpathyCallWindow
*self
)
614 empathy_call_window_update_swap_camera (self
);
618 empathy_call_window_camera_removed_cb (TpawCameraMonitor
*monitor
,
620 EmpathyCallWindow
*self
)
622 empathy_call_window_update_swap_camera (self
);
626 empathy_call_window_preview_button_clicked_cb (ClutterClickAction
*action
,
628 EmpathyCallWindow
*self
)
632 menu
= gtk_ui_manager_get_widget (self
->priv
->ui_manager
,
634 gtk_menu_popup (GTK_MENU (menu
), NULL
, NULL
, NULL
, NULL
,
635 0, gtk_get_current_event_time ());
636 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu
), FALSE
);
640 empathy_call_window_preview_hidden_button_clicked_cb (
641 ClutterClickAction
*action
,
643 EmpathyCallWindow
*self
)
647 menu
= gtk_ui_manager_get_widget (self
->priv
->ui_manager
,
648 "/preview-hidden-menu");
649 gtk_menu_popup (GTK_MENU (menu
), NULL
, NULL
, NULL
, NULL
,
650 0, gtk_get_current_event_time ());
651 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu
), FALSE
);
654 static ClutterActor
*
655 empathy_call_window_create_preview_rectangle (EmpathyCallWindow
*self
,
656 ClutterBinAlignment x
,
657 ClutterBinAlignment y
)
659 ClutterActor
*rectangle
;
661 rectangle
= CLUTTER_ACTOR (empathy_rounded_rectangle_new (
662 SELF_VIDEO_SECTION_WIDTH
, SELF_VIDEO_SECTION_HEIGHT
,
663 PREVIEW_ROUND_FACTOR
));
665 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (self
->priv
->preview_layout
),
667 clutter_actor_hide (rectangle
);
673 empathy_call_window_create_preview_rectangles (EmpathyCallWindow
*self
)
677 self
->priv
->preview_layout
= clutter_bin_layout_new (
678 CLUTTER_BIN_ALIGNMENT_CENTER
, CLUTTER_BIN_ALIGNMENT_CENTER
);
679 self
->priv
->preview_box
= box
= clutter_box_new (self
->priv
->preview_layout
);
681 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (self
->priv
->overlay_layout
),
683 CLUTTER_BIN_ALIGNMENT_FILL
, CLUTTER_BIN_ALIGNMENT_FILL
);
685 self
->priv
->preview_rectangle1
=
686 empathy_call_window_create_preview_rectangle (self
,
687 CLUTTER_BIN_ALIGNMENT_START
, CLUTTER_BIN_ALIGNMENT_START
);
688 self
->priv
->preview_rectangle2
=
689 empathy_call_window_create_preview_rectangle (self
,
690 CLUTTER_BIN_ALIGNMENT_START
, CLUTTER_BIN_ALIGNMENT_END
);
691 self
->priv
->preview_rectangle3
=
692 empathy_call_window_create_preview_rectangle (self
,
693 CLUTTER_BIN_ALIGNMENT_END
, CLUTTER_BIN_ALIGNMENT_START
);
694 self
->priv
->preview_rectangle4
=
695 empathy_call_window_create_preview_rectangle (self
,
696 CLUTTER_BIN_ALIGNMENT_END
, CLUTTER_BIN_ALIGNMENT_END
);
700 empathy_call_window_show_preview_rectangles (EmpathyCallWindow
*self
,
703 g_object_set (self
->priv
->preview_rectangle1
, "visible", show
, NULL
);
704 g_object_set (self
->priv
->preview_rectangle2
, "visible", show
, NULL
);
705 g_object_set (self
->priv
->preview_rectangle3
, "visible", show
, NULL
);
706 g_object_set (self
->priv
->preview_rectangle4
, "visible", show
, NULL
);
710 empathy_call_window_get_preview_coordinates (EmpathyCallWindow
*self
,
715 guint ret_x
= 0, ret_y
= 0;
718 if (!clutter_actor_has_allocation (self
->priv
->preview_box
))
721 clutter_actor_get_geometry (self
->priv
->preview_box
, &box
);
725 case PREVIEW_POS_TOP_LEFT
:
726 ret_x
= ret_y
= SELF_VIDEO_SECTION_MARGIN
;
728 case PREVIEW_POS_TOP_RIGHT
:
729 ret_x
= box
.width
- SELF_VIDEO_SECTION_MARGIN
730 - SELF_VIDEO_SECTION_WIDTH
;
731 ret_y
= SELF_VIDEO_SECTION_MARGIN
;
733 case PREVIEW_POS_BOTTOM_LEFT
:
734 ret_x
= SELF_VIDEO_SECTION_MARGIN
;
735 ret_y
= box
.height
- SELF_VIDEO_SECTION_MARGIN
736 - SELF_VIDEO_SECTION_HEIGHT
;
738 case PREVIEW_POS_BOTTOM_RIGHT
:
739 ret_x
= box
.width
- SELF_VIDEO_SECTION_MARGIN
740 - SELF_VIDEO_SECTION_WIDTH
;
741 ret_y
= box
.height
- SELF_VIDEO_SECTION_MARGIN
742 - SELF_VIDEO_SECTION_HEIGHT
;
745 g_warn_if_reached ();
756 static PreviewPosition
757 empathy_call_window_get_preview_position (EmpathyCallWindow
*self
,
762 PreviewPosition pos
= PREVIEW_POS_NONE
;
764 if (!clutter_actor_has_allocation (self
->priv
->preview_box
))
767 clutter_actor_get_geometry (self
->priv
->preview_box
, &box
);
769 if (0 + SELF_VIDEO_SECTION_MARGIN
<= event_x
&&
770 event_x
<= (0 + SELF_VIDEO_SECTION_MARGIN
+ (gint
) SELF_VIDEO_SECTION_WIDTH
) &&
771 0 + SELF_VIDEO_SECTION_MARGIN
<= event_y
&&
772 event_y
<= (0 + SELF_VIDEO_SECTION_MARGIN
+ (gint
) SELF_VIDEO_SECTION_HEIGHT
))
774 pos
= PREVIEW_POS_TOP_LEFT
;
776 else if (box
.width
- SELF_VIDEO_SECTION_MARGIN
>= event_x
&&
777 event_x
>= (box
.width
- SELF_VIDEO_SECTION_MARGIN
- (gint
) SELF_VIDEO_SECTION_WIDTH
) &&
778 0 + SELF_VIDEO_SECTION_MARGIN
<= event_y
&&
779 event_y
<= (0 + SELF_VIDEO_SECTION_MARGIN
+ (gint
) SELF_VIDEO_SECTION_HEIGHT
))
781 pos
= PREVIEW_POS_TOP_RIGHT
;
783 else if (0 + SELF_VIDEO_SECTION_MARGIN
<= event_x
&&
784 event_x
<= (0 + SELF_VIDEO_SECTION_MARGIN
+ (gint
) SELF_VIDEO_SECTION_WIDTH
) &&
785 box
.height
- SELF_VIDEO_SECTION_MARGIN
>= event_y
&&
786 event_y
>= (box
.height
- SELF_VIDEO_SECTION_MARGIN
- (gint
) SELF_VIDEO_SECTION_HEIGHT
))
788 pos
= PREVIEW_POS_BOTTOM_LEFT
;
790 else if (box
.width
- SELF_VIDEO_SECTION_MARGIN
>= event_x
&&
791 event_x
>= (box
.width
- SELF_VIDEO_SECTION_MARGIN
- (gint
) SELF_VIDEO_SECTION_WIDTH
) &&
792 box
.height
- 2 * SELF_VIDEO_SECTION_MARGIN
>= event_y
&&
793 event_y
>= (box
.height
- SELF_VIDEO_SECTION_MARGIN
- (gint
) SELF_VIDEO_SECTION_HEIGHT
))
795 pos
= PREVIEW_POS_BOTTOM_RIGHT
;
801 static ClutterActor
*
802 empathy_call_window_get_preview_rectangle (EmpathyCallWindow
*self
,
805 ClutterActor
*rectangle
;
809 case PREVIEW_POS_TOP_LEFT
:
810 rectangle
= self
->priv
->preview_rectangle1
;
812 case PREVIEW_POS_TOP_RIGHT
:
813 rectangle
= self
->priv
->preview_rectangle3
;
815 case PREVIEW_POS_BOTTOM_LEFT
:
816 rectangle
= self
->priv
->preview_rectangle2
;
818 case PREVIEW_POS_BOTTOM_RIGHT
:
819 rectangle
= self
->priv
->preview_rectangle4
;
829 empathy_call_window_move_video_preview (EmpathyCallWindow
*self
,
832 ClutterBinLayout
*layout
= CLUTTER_BIN_LAYOUT (self
->priv
->preview_layout
);
834 DEBUG ("moving the video preview to %d", pos
);
836 self
->priv
->preview_pos
= pos
;
840 case PREVIEW_POS_TOP_LEFT
:
841 clutter_bin_layout_set_alignment (layout
,
842 self
->priv
->video_preview
,
843 CLUTTER_BIN_ALIGNMENT_START
,
844 CLUTTER_BIN_ALIGNMENT_START
);
846 case PREVIEW_POS_TOP_RIGHT
:
847 clutter_bin_layout_set_alignment (layout
,
848 self
->priv
->video_preview
,
849 CLUTTER_BIN_ALIGNMENT_END
,
850 CLUTTER_BIN_ALIGNMENT_START
);
852 case PREVIEW_POS_BOTTOM_LEFT
:
853 clutter_bin_layout_set_alignment (layout
,
854 self
->priv
->video_preview
,
855 CLUTTER_BIN_ALIGNMENT_START
,
856 CLUTTER_BIN_ALIGNMENT_END
);
858 case PREVIEW_POS_BOTTOM_RIGHT
:
859 clutter_bin_layout_set_alignment (layout
,
860 self
->priv
->video_preview
,
861 CLUTTER_BIN_ALIGNMENT_END
,
862 CLUTTER_BIN_ALIGNMENT_END
);
865 g_warn_if_reached ();
868 g_settings_set_enum (self
->priv
->settings
, "camera-position", pos
);
872 empathy_call_window_highlight_preview_rectangle (EmpathyCallWindow
*self
,
875 ClutterActor
*rectangle
;
876 ClutterColor white
= { 0xff, 0xff, 0xff, 0xff};
878 rectangle
= empathy_call_window_get_preview_rectangle (self
, pos
);
880 empathy_rounded_rectangle_set_border_width (
881 EMPATHY_ROUNDED_RECTANGLE (rectangle
), 2 * SELF_VIDEO_SECTION_MARGIN
);
882 empathy_rounded_rectangle_set_border_color (
883 EMPATHY_ROUNDED_RECTANGLE (rectangle
), &white
);
887 empathy_call_window_darken_preview_rectangle (EmpathyCallWindow
*self
,
888 ClutterActor
*rectangle
)
890 ClutterColor white
= { 0xff, 0xff, 0xff, 0xff}, darker
;
892 clutter_color_shade (&white
, 0.55, &darker
);
894 empathy_rounded_rectangle_set_border_width (
895 EMPATHY_ROUNDED_RECTANGLE (rectangle
), 1);
896 empathy_rounded_rectangle_set_border_color (
897 EMPATHY_ROUNDED_RECTANGLE (rectangle
), &darker
);
901 empathy_call_window_darken_preview_rectangles (EmpathyCallWindow
*self
)
903 ClutterActor
*rectangle
;
905 rectangle
= empathy_call_window_get_preview_rectangle (self
,
906 self
->priv
->preview_pos
);
908 /* We don't want to darken the rectangle where the preview
911 if (self
->priv
->preview_rectangle1
!= rectangle
)
912 empathy_call_window_darken_preview_rectangle (self
,
913 self
->priv
->preview_rectangle1
);
915 if (self
->priv
->preview_rectangle2
!= rectangle
)
916 empathy_call_window_darken_preview_rectangle (self
,
917 self
->priv
->preview_rectangle2
);
919 if (self
->priv
->preview_rectangle3
!= rectangle
)
920 empathy_call_window_darken_preview_rectangle (self
,
921 self
->priv
->preview_rectangle3
);
923 if (self
->priv
->preview_rectangle4
!= rectangle
)
924 empathy_call_window_darken_preview_rectangle (self
,
925 self
->priv
->preview_rectangle4
);
929 empathy_call_window_preview_on_drag_begin_cb (ClutterDragAction
*action
,
933 ClutterModifierType modifiers
,
934 EmpathyCallWindow
*self
)
936 ClutterActor
*stage
= clutter_actor_get_stage (actor
);
939 self
->priv
->drag_preview
= clutter_clone_new (actor
);
941 clutter_container_add_actor (CLUTTER_CONTAINER (stage
),
942 self
->priv
->drag_preview
);
944 clutter_actor_transform_stage_point (actor
, event_x
, event_y
,
947 clutter_actor_set_position (self
->priv
->drag_preview
,
948 event_x
- rel_x
, event_y
- rel_y
);
950 clutter_drag_action_set_drag_handle (action
,
951 self
->priv
->drag_preview
);
953 clutter_actor_set_opacity (actor
, 0);
954 clutter_actor_hide (self
->priv
->preview_shown_button
);
956 empathy_call_window_show_preview_rectangles (self
, TRUE
);
957 empathy_call_window_darken_preview_rectangles (self
);
961 empathy_call_window_on_animation_completed_cb (ClutterAnimation
*animation
,
964 clutter_actor_set_opacity (actor
, 255);
968 empathy_call_window_preview_on_drag_end_cb (ClutterDragAction
*action
,
972 ClutterModifierType modifiers
,
973 EmpathyCallWindow
*self
)
978 /* Get the position before destroying the drag actor, otherwise the
979 * preview_box allocation won't be valid and we won't be able to
980 * calculate the position. */
981 pos
= empathy_call_window_get_preview_position (self
, event_x
, event_y
);
983 empathy_call_window_get_preview_coordinates (self
,
984 pos
!= PREVIEW_POS_NONE
? pos
: self
->priv
->preview_pos
,
987 /* Move the preview to the destination and destroy it afterwards */
988 clutter_actor_animate (self
->priv
->drag_preview
, CLUTTER_LINEAR
, 500,
991 "signal-swapped-after::completed",
992 clutter_actor_destroy
, self
->priv
->drag_preview
,
993 "signal-swapped-after::completed",
994 clutter_actor_show
, self
->priv
->preview_shown_button
,
996 empathy_call_window_on_animation_completed_cb
, actor
,
999 self
->priv
->drag_preview
= NULL
;
1001 if (pos
!= PREVIEW_POS_NONE
)
1002 empathy_call_window_move_video_preview (self
, pos
);
1004 empathy_call_window_show_preview_rectangles (self
, FALSE
);
1008 empathy_call_window_preview_on_drag_motion_cb (ClutterDragAction
*action
,
1009 ClutterActor
*actor
,
1012 EmpathyCallWindow
*self
)
1014 PreviewPosition pos
;
1015 gfloat event_x
, event_y
;
1017 clutter_drag_action_get_motion_coords (action
, &event_x
, &event_y
);
1019 pos
= empathy_call_window_get_preview_position (self
, event_x
, event_y
);
1021 if (pos
!= PREVIEW_POS_NONE
)
1022 empathy_call_window_highlight_preview_rectangle (self
, pos
);
1024 empathy_call_window_darken_preview_rectangles (self
);
1028 empathy_call_window_preview_enter_event_cb (ClutterActor
*actor
,
1029 ClutterCrossingEvent
*event
,
1030 EmpathyCallWindow
*self
)
1032 ClutterActor
*rectangle
;
1034 rectangle
= empathy_call_window_get_preview_rectangle (self
,
1035 self
->priv
->preview_pos
);
1037 empathy_call_window_highlight_preview_rectangle (self
,
1038 self
->priv
->preview_pos
);
1040 clutter_actor_show (rectangle
);
1046 empathy_call_window_preview_leave_event_cb (ClutterActor
*actor
,
1047 ClutterCrossingEvent
*event
,
1048 EmpathyCallWindow
*self
)
1050 ClutterActor
*rectangle
;
1052 rectangle
= empathy_call_window_get_preview_rectangle (self
,
1053 self
->priv
->preview_pos
);
1055 empathy_call_window_darken_preview_rectangle (self
, rectangle
);
1057 clutter_actor_hide (rectangle
);
1063 create_video_preview (EmpathyCallWindow
*self
)
1065 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
1066 ClutterLayoutManager
*layout
;
1067 ClutterActor
*preview
;
1069 ClutterAction
*action
;
1070 PreviewPosition pos
;
1072 g_assert (priv
->video_preview
== NULL
);
1074 pos
= g_settings_get_enum (priv
->settings
, "camera-position");
1076 preview
= clutter_actor_new ();
1077 clutter_actor_set_size (preview
,
1078 SELF_VIDEO_SECTION_WIDTH
, SELF_VIDEO_SECTION_HEIGHT
);
1079 clutter_actor_add_effect (preview
, empathy_rounded_effect_new ());
1081 priv
->video_preview_sink
= GST_ELEMENT (clutter_gst_video_sink_new ());
1082 g_object_add_weak_pointer (G_OBJECT (priv
->video_preview_sink
), (gpointer
) &priv
->video_preview_sink
);
1084 /* Add a little offset to the video preview */
1085 layout
= clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER
,
1086 CLUTTER_BIN_ALIGNMENT_CENTER
);
1087 priv
->video_preview
= clutter_box_new (layout
);
1088 clutter_actor_set_size (priv
->video_preview
,
1089 SELF_VIDEO_SECTION_WIDTH
+ 2 * SELF_VIDEO_SECTION_MARGIN
,
1090 SELF_VIDEO_SECTION_HEIGHT
+ 2 * SELF_VIDEO_SECTION_MARGIN
);
1091 priv
->preview_content
= clutter_gst_content_new_with_sink (
1092 CLUTTER_GST_VIDEO_SINK (priv
->video_preview_sink
));
1093 clutter_actor_set_content (preview
, priv
->preview_content
);
1095 /* Spinner for when changing the camera device */
1096 priv
->preview_spinner_widget
= gtk_spinner_new ();
1097 priv
->preview_spinner_actor
= empathy_rounded_actor_new (PREVIEW_ROUND_FACTOR
);
1099 g_object_set (priv
->preview_spinner_widget
, "expand", TRUE
, NULL
);
1100 make_background_transparent (GTK_CLUTTER_ACTOR (priv
->preview_spinner_actor
));
1101 gtk_widget_show (priv
->preview_spinner_widget
);
1104 GTK_CONTAINER (gtk_clutter_actor_get_widget (
1105 GTK_CLUTTER_ACTOR (priv
->preview_spinner_actor
))),
1106 priv
->preview_spinner_widget
);
1107 clutter_actor_set_size (priv
->preview_spinner_actor
,
1108 SELF_VIDEO_SECTION_WIDTH
, SELF_VIDEO_SECTION_HEIGHT
);
1109 clutter_actor_set_opacity (priv
->preview_spinner_actor
, 128);
1110 clutter_actor_hide (priv
->preview_spinner_actor
);
1112 clutter_container_add_actor (CLUTTER_CONTAINER (priv
->video_preview
),
1114 clutter_container_add_actor (CLUTTER_CONTAINER (priv
->video_preview
),
1115 priv
->preview_spinner_actor
);
1117 g_object_set (priv
->video_preview_sink
,
1123 priv
->preview_shown_button
= b
= gtk_clutter_actor_new_with_contents (
1124 gtk_image_new_from_icon_name ("emblem-system-symbolic",
1125 GTK_ICON_SIZE_MENU
));
1126 clutter_actor_set_margin_right (b
, 4);
1127 clutter_actor_set_margin_bottom (b
, 2);
1128 clutter_actor_set_opacity (b
, PREVIEW_BUTTON_OPACITY
);
1129 make_background_transparent (GTK_CLUTTER_ACTOR (b
));
1131 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout
), b
,
1132 CLUTTER_BIN_ALIGNMENT_END
, CLUTTER_BIN_ALIGNMENT_END
);
1134 action
= clutter_click_action_new ();
1135 clutter_actor_add_action (b
, action
);
1136 g_signal_connect (action
, "clicked",
1137 G_CALLBACK (empathy_call_window_preview_button_clicked_cb
), self
);
1139 /* Preview hidden */
1140 priv
->preview_hidden_button
= b
= gtk_clutter_actor_new_with_contents (
1141 gtk_image_new_from_icon_name ("emblem-system-symbolic",
1142 GTK_ICON_SIZE_MENU
));
1143 make_background_transparent (GTK_CLUTTER_ACTOR (b
));
1145 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv
->preview_layout
),
1146 priv
->preview_hidden_button
,
1147 CLUTTER_BIN_ALIGNMENT_START
,
1148 CLUTTER_BIN_ALIGNMENT_END
);
1150 self
->priv
->preview_pos
= PREVIEW_POS_BOTTOM_LEFT
;
1152 clutter_actor_hide (priv
->preview_hidden_button
);
1154 action
= clutter_click_action_new ();
1155 clutter_actor_add_action (b
, action
);
1156 g_signal_connect (action
, "clicked",
1157 G_CALLBACK (empathy_call_window_preview_hidden_button_clicked_cb
), self
);
1159 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv
->preview_layout
),
1160 priv
->video_preview
,
1161 CLUTTER_BIN_ALIGNMENT_START
,
1162 CLUTTER_BIN_ALIGNMENT_END
);
1164 empathy_call_window_move_video_preview (self
, pos
);
1166 action
= clutter_drag_action_new ();
1167 g_signal_connect (action
, "drag-begin",
1168 G_CALLBACK (empathy_call_window_preview_on_drag_begin_cb
), self
);
1169 g_signal_connect (action
, "drag-end",
1170 G_CALLBACK (empathy_call_window_preview_on_drag_end_cb
), self
);
1171 g_signal_connect (action
, "drag-motion",
1172 G_CALLBACK (empathy_call_window_preview_on_drag_motion_cb
), self
);
1174 g_signal_connect (preview
, "enter-event",
1175 G_CALLBACK (empathy_call_window_preview_enter_event_cb
), self
);
1176 g_signal_connect (preview
, "leave-event",
1177 G_CALLBACK (empathy_call_window_preview_leave_event_cb
), self
);
1179 clutter_actor_add_action (preview
, action
);
1180 clutter_actor_set_reactive (preview
, TRUE
);
1181 clutter_actor_set_reactive (priv
->preview_shown_button
, TRUE
);
1185 empathy_call_window_start_camera_spinning (EmpathyCallWindow
*self
)
1187 clutter_actor_show (self
->priv
->preview_spinner_actor
);
1188 gtk_spinner_start (GTK_SPINNER (self
->priv
->preview_spinner_widget
));
1192 empathy_call_window_stop_camera_spinning (EmpathyCallWindow
*self
)
1194 clutter_actor_hide (self
->priv
->preview_spinner_actor
);
1195 gtk_spinner_stop (GTK_SPINNER (self
->priv
->preview_spinner_widget
));
1199 empathy_call_window_play_camera (EmpathyCallWindow
*self
,
1202 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
1203 GstElement
*preview
;
1206 if (priv
->video_preview
== NULL
)
1208 create_video_preview (self
);
1209 add_video_preview_to_pipeline (self
);
1214 state
= GST_STATE_PLAYING
;
1218 empathy_call_window_start_camera_spinning (self
);
1219 state
= GST_STATE_NULL
;
1222 preview
= priv
->video_preview_sink
;
1224 gst_element_set_state (preview
, state
);
1225 gst_element_set_state (priv
->video_tee
, state
);
1226 gst_element_set_state (priv
->video_input
, state
);
1230 display_video_preview (EmpathyCallWindow
*self
,
1233 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
1235 if (priv
->video_preview
== NULL
)
1237 create_video_preview (self
);
1238 add_video_preview_to_pipeline (self
);
1243 /* Display the video preview */
1244 DEBUG ("Show video preview");
1246 empathy_call_window_play_camera (self
, TRUE
);
1247 clutter_actor_show (priv
->video_preview
);
1248 clutter_actor_raise_top (priv
->floating_toolbar
);
1252 /* Hide the video preview */
1253 DEBUG ("Hide video preview");
1255 if (priv
->video_preview
!= NULL
)
1257 clutter_actor_hide (priv
->video_preview
);
1258 empathy_call_window_play_camera (self
, FALSE
);
1264 empathy_call_window_set_state_connecting (EmpathyCallWindow
*window
)
1266 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
1268 empathy_call_window_status_message (window
, _("Connecting…"));
1269 priv
->call_state
= CONNECTING
;
1271 /* Show the toolbar */
1272 clutter_state_set_state (priv
->transitions
, "fade-in");
1275 empathy_sound_manager_start_playing (priv
->sound_mgr
, GTK_WIDGET (window
),
1276 EMPATHY_SOUND_PHONE_OUTGOING
, MS_BETWEEN_RING
);
1280 disable_camera (EmpathyCallWindow
*self
)
1282 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
1284 if (priv
->camera_state
== CAMERA_STATE_OFF
)
1287 DEBUG ("Disable camera");
1289 empathy_call_window_set_send_video (self
, CAMERA_STATE_OFF
);
1291 priv
->camera_state
= CAMERA_STATE_OFF
;
1295 enable_camera (EmpathyCallWindow
*self
)
1297 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
1299 if (priv
->camera_state
== CAMERA_STATE_ON
)
1302 if (priv
->video_input
== NULL
)
1304 DEBUG ("Can't enable camera, no input");
1308 DEBUG ("Enable camera");
1310 empathy_call_window_set_send_video (self
, CAMERA_STATE_ON
);
1312 priv
->camera_state
= CAMERA_STATE_ON
;
1316 empathy_call_window_camera_toggled_cb (GtkToggleButton
*toggle
,
1317 EmpathyCallWindow
*self
)
1319 if (gtk_toggle_button_get_active (toggle
))
1320 enable_camera (self
);
1322 disable_camera (self
);
1326 create_pipeline (EmpathyCallWindow
*self
)
1328 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
1331 g_assert (priv
->pipeline
== NULL
);
1333 priv
->pipeline
= gst_pipeline_new (NULL
);
1334 priv
->pipeline_playing
= FALSE
;
1336 priv
->video_tee
= gst_element_factory_make ("tee", NULL
);
1337 gst_object_ref_sink (priv
->video_tee
);
1339 gst_bin_add (GST_BIN (priv
->pipeline
), priv
->video_tee
);
1341 bus
= gst_pipeline_get_bus (GST_PIPELINE (priv
->pipeline
));
1342 priv
->bus_message_source_id
= gst_bus_add_watch (bus
,
1343 empathy_call_window_bus_message
, self
);
1345 g_object_unref (bus
);
1349 empathy_call_window_settings_cb (GtkAction
*action
,
1350 EmpathyCallWindow
*self
)
1352 gchar
*args
= g_strdup_printf ("-p %s",
1353 empathy_preferences_tab_to_string (EMPATHY_PREFERENCES_TAB_CALLS
));
1355 empathy_launch_program (BIN_DIR
, "empathy", args
);
1361 empathy_call_window_contents_cb (GtkAction
*action
,
1362 EmpathyCallWindow
*self
)
1364 empathy_url_show (GTK_WIDGET (self
), "help:empathy/audio-video");
1368 show_png (GPid pid
, gint status
, gpointer user_data
)
1370 gtk_show_uri (NULL
, (gchar
*) user_data
, GDK_CURRENT_TIME
, NULL
);
1371 g_spawn_close_pid (pid
);
1376 empathy_call_window_debug_gst_cb (GtkAction
*action
,
1377 EmpathyCallWindow
*self
)
1379 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
1380 GDateTime
*date_time
;
1382 const gchar
*dot_dir
;
1383 const gchar
*prgname
;
1389 if (priv
->pipeline
== NULL
)
1390 DEBUG ("No pipeline");
1392 date_time
= g_date_time_new_now_utc ();
1393 prgname
= g_get_prgname ();
1394 filename
= g_strdup_printf ("%s-%" G_GINT64_FORMAT
, prgname
,
1395 g_date_time_to_unix (date_time
));
1397 GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (priv
->pipeline
),
1398 GST_DEBUG_GRAPH_SHOW_ALL
, filename
);
1400 dot_dir
= g_getenv ("GST_DEBUG_DUMP_DOT_DIR");
1401 dot_cmd
= g_strdup_printf ("dot -Tpng -o %s.png %s.dot",
1404 g_shell_parse_argv (dot_cmd
, &argc
, &argv
, NULL
);
1406 if (g_spawn_async (dot_dir
,
1409 G_SPAWN_DO_NOT_REAP_CHILD
| G_SPAWN_SEARCH_PATH
,
1415 gchar
*uri
= g_strdup_printf ("file://%s/%s.png", dot_dir
, filename
);
1416 g_child_watch_add (dot_pid
, show_png
, uri
);
1422 g_date_time_unref (date_time
);
1426 empathy_call_window_debug_tp_cb (GtkAction
*action
,
1427 EmpathyCallWindow
*self
)
1429 empathy_launch_program (BIN_DIR
, "empathy-debugger", "-s Empathy.Call");
1433 empathy_call_window_about_cb (GtkAction
*action
,
1434 EmpathyCallWindow
*self
)
1436 empathy_about_dialog_new (GTK_WINDOW (self
));
1440 empathy_call_window_toolbar_timeout (gpointer data
)
1442 EmpathyCallWindow
*self
= data
;
1444 /* We don't want to hide the toolbar if we're not in a call, as
1445 * to show the call status all the time. Also don't hide if we're muted
1446 * to prevent the awkward, talking when muted situation */
1447 if (self
->priv
->call_state
!= CONNECTING
&&
1448 self
->priv
->call_state
!= DISCONNECTED
&&
1450 clutter_state_set_state (self
->priv
->transitions
, "fade-out");
1456 empathy_call_window_motion_notify_cb (GtkWidget
*widget
,
1458 EmpathyCallWindow
*self
)
1460 clutter_state_set_state (self
->priv
->transitions
, "fade-in");
1462 if (self
->priv
->inactivity_src
> 0)
1463 g_source_remove (self
->priv
->inactivity_src
);
1465 self
->priv
->inactivity_src
= g_timeout_add_seconds (3,
1466 empathy_call_window_toolbar_timeout
, self
);
1472 empathy_call_window_configure_event_cb (GtkWidget
*widget
,
1474 EmpathyCallWindow
*self
)
1476 GdkWindow
*gdk_window
;
1477 GdkWindowState window_state
;
1479 gtk_window_get_position (GTK_WINDOW (self
), &self
->priv
->x
, &self
->priv
->y
);
1480 gtk_window_get_size (GTK_WINDOW (self
), &self
->priv
->w
, &self
->priv
->h
);
1482 gtk_widget_get_preferred_width (self
->priv
->dtmf_panel
,
1483 &self
->priv
->dialpad_width
, NULL
);
1485 gdk_window
= gtk_widget_get_window (widget
);
1486 window_state
= gdk_window_get_state (gdk_window
);
1487 self
->priv
->maximized
= (window_state
& GDK_WINDOW_STATE_MAXIMIZED
);
1493 empathy_call_window_destroyed_cb (GtkWidget
*object
,
1494 EmpathyCallWindow
*self
)
1496 if (gtk_widget_get_visible (self
->priv
->dtmf_panel
))
1498 /* Save the geometry as if the dialpad was hidden. */
1499 empathy_geometry_save_values (GTK_WINDOW (self
),
1500 self
->priv
->x
, self
->priv
->y
,
1501 self
->priv
->w
- self
->priv
->dialpad_width
, self
->priv
->h
,
1502 self
->priv
->maximized
);
1507 empathy_call_window_incoming_call_response_cb (GtkDialog
*dialog
,
1509 EmpathyCallWindow
*self
)
1511 switch (response_id
)
1513 case GTK_RESPONSE_ACCEPT
:
1514 tp_channel_dispatch_operation_handle_with_time_async (
1515 self
->priv
->pending_cdo
, EMPATHY_CALL_TP_BUS_NAME
,
1516 empathy_get_current_action_time (), NULL
, NULL
);
1518 tp_clear_object (&self
->priv
->pending_cdo
);
1519 tp_clear_object (&self
->priv
->pending_channel
);
1520 tp_clear_object (&self
->priv
->pending_context
);
1523 case GTK_RESPONSE_CANCEL
:
1524 tp_channel_dispatch_operation_close_channels_async (
1525 self
->priv
->pending_cdo
, NULL
, NULL
);
1527 empathy_call_window_status_message (self
, _("Disconnected"));
1528 self
->priv
->call_state
= DISCONNECTED
;
1531 g_warn_if_reached ();
1536 empathy_call_window_set_state_ringing (EmpathyCallWindow
*self
)
1540 g_assert (self
->priv
->call_state
!= CONNECTED
);
1542 video
= tp_call_channel_has_initial_video (self
->priv
->pending_channel
, NULL
);
1544 empathy_call_window_status_message (self
, _("Incoming call"));
1545 self
->priv
->call_state
= RINGING
;
1547 self
->priv
->incoming_call_dialog
= gtk_message_dialog_new (
1548 GTK_WINDOW (self
), GTK_DIALOG_MODAL
,
1549 GTK_MESSAGE_QUESTION
, GTK_BUTTONS_NONE
,
1550 video
? _("Incoming video call from %s") : _("Incoming call from %s"),
1551 empathy_contact_get_alias (self
->priv
->contact
));
1553 gtk_dialog_add_buttons (GTK_DIALOG (self
->priv
->incoming_call_dialog
),
1554 _("Reject"), GTK_RESPONSE_CANCEL
,
1555 _("Answer"), GTK_RESPONSE_ACCEPT
,
1558 g_signal_connect (self
->priv
->incoming_call_dialog
, "response",
1559 G_CALLBACK (empathy_call_window_incoming_call_response_cb
), self
);
1560 gtk_widget_show (self
->priv
->incoming_call_dialog
);
1564 empathy_call_window_cdo_invalidated_cb (TpProxy
*channel
,
1568 EmpathyCallWindow
*self
)
1570 tp_clear_object (&self
->priv
->pending_cdo
);
1571 tp_clear_object (&self
->priv
->pending_channel
);
1572 tp_clear_object (&self
->priv
->pending_context
);
1574 /* We don't know if the incoming call has been accepted or not, so we
1575 * assume it hasn't and if it has, we'll set the proper status when
1576 * we get the new handler. */
1577 empathy_call_window_status_message (self
, _("Disconnected"));
1578 self
->priv
->call_state
= DISCONNECTED
;
1580 gtk_widget_destroy (self
->priv
->incoming_call_dialog
);
1581 self
->priv
->incoming_call_dialog
= NULL
;
1585 empathy_call_window_start_ringing (EmpathyCallWindow
*self
,
1586 TpCallChannel
*channel
,
1587 TpChannelDispatchOperation
*dispatch_operation
,
1588 TpAddDispatchOperationContext
*context
)
1590 g_assert (self
->priv
->pending_channel
== NULL
);
1591 g_assert (self
->priv
->pending_context
== NULL
);
1592 g_assert (self
->priv
->pending_cdo
== NULL
);
1594 /* Start ringing and delay until the user answers or hangs. */
1595 self
->priv
->pending_channel
= g_object_ref (channel
);
1596 self
->priv
->pending_context
= g_object_ref (context
);
1597 self
->priv
->pending_cdo
= g_object_ref (dispatch_operation
);
1599 g_signal_connect (self
->priv
->pending_cdo
, "invalidated",
1600 G_CALLBACK (empathy_call_window_cdo_invalidated_cb
), self
);
1602 empathy_call_window_set_state_ringing (self
);
1603 tp_add_dispatch_operation_context_accept (context
);
1607 mic_button_clicked (GtkWidget
*button
,
1608 EmpathyCallWindow
*self
)
1610 /* Toggle the muted state. We rely on audio_input_mute_notify_cb to update
1612 empathy_audio_src_set_mute (EMPATHY_GST_AUDIO_SRC (self
->priv
->audio_input
),
1613 !self
->priv
->muted
);
1617 empathy_call_window_init (EmpathyCallWindow
*self
)
1619 EmpathyCallWindowPriv
*priv
;
1621 GtkWidget
*top_vbox
;
1623 ClutterConstraint
*constraint
;
1624 ClutterActor
*remote_avatar
;
1625 ClutterColor black
= { 0, 0, 0, 0 };
1626 ClutterMargin overlay_margin
= { OVERLAY_MARGIN
, OVERLAY_MARGIN
,
1627 OVERLAY_MARGIN
, OVERLAY_MARGIN
};
1629 priv
= self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
1630 EMPATHY_TYPE_CALL_WINDOW
, EmpathyCallWindowPriv
);
1632 priv
->settings
= g_settings_new (EMPATHY_PREFS_CALL_SCHEMA
);
1633 priv
->timer
= g_timer_new ();
1635 filename
= empathy_file_lookup ("empathy-call-window.ui", "src");
1636 gui
= tpaw_builder_get_file (filename
,
1637 "call_window_vbox", &top_vbox
,
1638 "errors_vbox", &priv
->errors_vbox
,
1639 "pane", &priv
->pane
,
1640 "remote_user_name_toolbar", &priv
->remote_user_name_toolbar
,
1641 "remote_user_status_toolbar", &priv
->remote_user_status_toolbar
,
1642 "remote_user_avatar_toolbar", &priv
->remote_user_avatar_toolbar
,
1643 "status_label", &priv
->status_label
,
1644 "audiocall", &priv
->audio_call_button
,
1645 "videocall", &priv
->video_call_button
,
1646 "microphone", &priv
->mic_button
,
1647 "microphone_icon", &priv
->microphone_icon
,
1648 "volume", &priv
->volume_button
,
1649 "camera", &priv
->camera_button
,
1650 "hangup", &priv
->hangup_button
,
1651 "dialpad", &priv
->dialpad_button
,
1652 "toolbar", &priv
->toolbar
,
1653 "bottom_toolbar", &priv
->bottom_toolbar
,
1654 "ui_manager", &priv
->ui_manager
,
1655 "menufullscreen", &priv
->menu_fullscreen
,
1656 "menupreviewswap", &priv
->menu_swap_camera
,
1657 "details_vbox", &priv
->details_vbox
,
1658 "vcodec_encoding_label", &priv
->vcodec_encoding_label
,
1659 "acodec_encoding_label", &priv
->acodec_encoding_label
,
1660 "acodec_decoding_label", &priv
->acodec_decoding_label
,
1661 "vcodec_decoding_label", &priv
->vcodec_decoding_label
,
1662 "audio_remote_candidate_label", &priv
->audio_remote_candidate_label
,
1663 "audio_local_candidate_label", &priv
->audio_local_candidate_label
,
1664 "video_remote_candidate_label", &priv
->video_remote_candidate_label
,
1665 "video_local_candidate_label", &priv
->video_local_candidate_label
,
1666 "video_remote_candidate_info_img", &priv
->video_remote_candidate_info_img
,
1667 "video_local_candidate_info_img", &priv
->video_local_candidate_info_img
,
1668 "audio_remote_candidate_info_img", &priv
->audio_remote_candidate_info_img
,
1669 "audio_local_candidate_info_img", &priv
->audio_local_candidate_info_img
,
1673 tpaw_builder_connect (gui
, self
,
1674 "hangup", "clicked", empathy_call_window_hangup_cb
,
1675 "audiocall", "clicked", empathy_call_window_audio_call_cb
,
1676 "videocall", "clicked", empathy_call_window_video_call_cb
,
1677 "camera", "toggled", empathy_call_window_camera_toggled_cb
,
1678 "dialpad", "toggled", empathy_call_window_dialpad_cb
,
1679 "menufullscreen", "activate", empathy_call_window_fullscreen_cb
,
1680 "menusettings", "activate", empathy_call_window_settings_cb
,
1681 "menucontents", "activate", empathy_call_window_contents_cb
,
1682 "menudebuggst", "activate", empathy_call_window_debug_gst_cb
,
1683 "menudebugtp", "activate", empathy_call_window_debug_tp_cb
,
1684 "menuabout", "activate", empathy_call_window_about_cb
,
1685 "menupreviewdisable", "activate", empathy_call_window_disable_camera_cb
,
1686 "menupreviewminimise", "activate", empathy_call_window_minimise_camera_cb
,
1687 "menupreviewmaximise", "activate", empathy_call_window_maximise_camera_cb
,
1688 "menupreviewswap", "activate", empathy_call_window_swap_camera_cb
,
1691 empathy_set_css_provider (GTK_WIDGET (self
));
1692 gtk_action_set_sensitive (priv
->menu_fullscreen
, FALSE
);
1694 priv
->camera_monitor
= tpaw_camera_monitor_dup_singleton ();
1695 empathy_call_window_update_swap_camera (self
);
1697 g_object_bind_property (priv
->camera_monitor
, "available",
1698 priv
->camera_button
, "sensitive",
1699 G_BINDING_SYNC_CREATE
);
1701 g_signal_connect (priv
->camera_monitor
, "added",
1702 G_CALLBACK (empathy_call_window_camera_added_cb
), self
);
1703 g_signal_connect (priv
->camera_monitor
, "removed",
1704 G_CALLBACK (empathy_call_window_camera_removed_cb
), self
);
1706 g_signal_connect (priv
->mic_button
, "clicked",
1707 G_CALLBACK (mic_button_clicked
), self
);
1709 g_mutex_init (&priv
->lock
);
1711 gtk_container_add (GTK_CONTAINER (self
), top_vbox
);
1713 priv
->content_hbox
= gtk_box_new (GTK_ORIENTATION_HORIZONTAL
,
1714 CONTENT_HBOX_SPACING
);
1715 gtk_box_pack_start (GTK_BOX (priv
->pane
), priv
->content_hbox
,
1718 /* main contents remote avatar/video box */
1719 priv
->video_layout
= clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL
,
1720 CLUTTER_BIN_ALIGNMENT_FILL
);
1722 priv
->video_box
= clutter_box_new (priv
->video_layout
);
1724 priv
->video_container
= gtk_clutter_embed_new ();
1726 gtk_widget_set_size_request (priv
->video_container
,
1727 REMOTE_VIDEO_DEFAULT_WIDTH
, REMOTE_VIDEO_DEFAULT_HEIGHT
);
1729 /* Set the background black */
1730 clutter_stage_set_color (
1731 CLUTTER_STAGE (gtk_clutter_embed_get_stage (
1732 GTK_CLUTTER_EMBED (priv
->video_container
))),
1735 clutter_container_add (
1736 CLUTTER_CONTAINER (gtk_clutter_embed_get_stage (
1737 GTK_CLUTTER_EMBED (priv
->video_container
))),
1741 constraint
= clutter_bind_constraint_new (
1742 gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv
->video_container
)),
1743 CLUTTER_BIND_SIZE
, 0);
1744 clutter_actor_add_constraint (priv
->video_box
, constraint
);
1746 priv
->remote_user_avatar_widget
= gtk_image_new ();
1747 remote_avatar
= gtk_clutter_actor_new_with_contents (
1748 priv
->remote_user_avatar_widget
);
1749 make_background_transparent (GTK_CLUTTER_ACTOR (remote_avatar
));
1751 clutter_container_add_actor (CLUTTER_CONTAINER (priv
->video_box
),
1754 /* create the overlay bin */
1755 priv
->overlay_layout
= clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER
,
1756 CLUTTER_BIN_ALIGNMENT_CENTER
);
1757 priv
->overlay_bin
= clutter_actor_new ();
1758 clutter_actor_set_layout_manager (priv
->overlay_bin
, priv
->overlay_layout
);
1760 clutter_actor_set_margin (priv
->overlay_bin
, &overlay_margin
);
1762 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv
->video_layout
),
1764 CLUTTER_BIN_ALIGNMENT_FILL
, CLUTTER_BIN_ALIGNMENT_FILL
);
1766 empathy_call_window_create_preview_rectangles (self
);
1768 gtk_box_pack_start (GTK_BOX (priv
->content_hbox
),
1769 priv
->video_container
, TRUE
, TRUE
,
1770 CONTENT_HBOX_CHILDREN_PACKING_PADDING
);
1772 create_pipeline (self
);
1773 create_video_output_widget (self
);
1774 create_audio_input (self
);
1775 create_video_input (self
);
1777 priv
->floating_toolbar
= gtk_clutter_actor_new ();
1778 clutter_actor_set_reactive (priv
->floating_toolbar
, TRUE
);
1779 make_background_transparent (GTK_CLUTTER_ACTOR (priv
->floating_toolbar
));
1781 gtk_style_context_add_class (
1782 gtk_widget_get_style_context (GTK_WIDGET (priv
->bottom_toolbar
)),
1783 GTK_STYLE_CLASS_OSD
);
1784 gtk_widget_reparent (priv
->bottom_toolbar
,
1785 gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv
->floating_toolbar
)));
1787 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv
->overlay_layout
),
1788 priv
->floating_toolbar
,
1789 CLUTTER_BIN_ALIGNMENT_CENTER
, CLUTTER_BIN_ALIGNMENT_END
);
1791 clutter_actor_raise_top (priv
->floating_toolbar
);
1793 /* Transitions for the floating toolbar */
1794 priv
->transitions
= clutter_state_new ();
1796 /* all transitions last for 2s */
1797 clutter_state_set_duration (priv
->transitions
, NULL
, NULL
, 2000);
1799 /* transition from any state to "fade-out" state */
1800 clutter_state_set (priv
->transitions
, NULL
, "fade-out",
1801 priv
->floating_toolbar
,
1802 "opacity", CLUTTER_EASE_OUT_QUAD
, 0,
1805 /* transition from any state to "fade-in" state */
1806 clutter_state_set (priv
->transitions
, NULL
, "fade-in",
1807 priv
->floating_toolbar
,
1808 "opacity", CLUTTER_EASE_OUT_QUAD
, 255,
1811 /* put the actor into the "fade-in" state with no animation */
1812 clutter_state_warp_to_state (priv
->transitions
, "fade-in");
1814 /* The call will be started as soon the pipeline is playing */
1815 priv
->start_call_when_playing
= TRUE
;
1817 priv
->dtmf_panel
= empathy_dialpad_widget_new ();
1818 g_signal_connect (priv
->dtmf_panel
, "start-tone",
1819 G_CALLBACK (dtmf_start_tone_cb
), self
);
1821 gtk_box_pack_start (GTK_BOX (priv
->pane
), priv
->dtmf_panel
,
1824 gtk_box_pack_start (GTK_BOX (priv
->pane
), priv
->details_vbox
,
1827 gtk_widget_set_sensitive (priv
->dtmf_panel
, FALSE
);
1829 gtk_widget_show_all (top_vbox
);
1831 gtk_widget_hide (priv
->dtmf_panel
);
1832 gtk_widget_hide (priv
->details_vbox
);
1834 priv
->fullscreen
= empathy_call_window_fullscreen_new (self
);
1836 empathy_call_window_fullscreen_set_video_widget (priv
->fullscreen
,
1837 priv
->video_container
);
1839 /* We hide the bottom toolbar after 3s of inactivity and show it
1840 * again on mouse movement */
1841 priv
->inactivity_src
= g_timeout_add_seconds (3,
1842 empathy_call_window_toolbar_timeout
, self
);
1844 g_signal_connect (G_OBJECT (priv
->fullscreen
->leave_fullscreen_button
),
1845 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb
), self
);
1847 g_signal_connect (G_OBJECT (self
), "realize",
1848 G_CALLBACK (empathy_call_window_realized_cb
), self
);
1850 g_signal_connect (G_OBJECT (self
), "delete-event",
1851 G_CALLBACK (empathy_call_window_delete_cb
), self
);
1853 g_signal_connect (G_OBJECT (self
), "window-state-event",
1854 G_CALLBACK (empathy_call_window_state_event_cb
), self
);
1856 g_signal_connect (G_OBJECT (self
), "key-press-event",
1857 G_CALLBACK (empathy_call_window_key_press_cb
), self
);
1859 g_signal_connect (self
, "motion-notify-event",
1860 G_CALLBACK (empathy_call_window_motion_notify_cb
), self
);
1862 g_object_ref (priv
->ui_manager
);
1863 g_object_unref (gui
);
1865 priv
->sound_mgr
= empathy_sound_manager_dup_singleton ();
1866 priv
->mic_menu
= empathy_mic_menu_new (self
);
1867 priv
->camera_menu
= empathy_camera_menu_new (self
);
1869 empathy_call_window_show_hangup_button (self
, TRUE
);
1871 gtk_window_set_default_size (GTK_WINDOW (self
), 580, 480);
1873 empathy_geometry_bind (GTK_WINDOW (self
), "call-window");
1874 /* These signals are used to track the window position and save it
1875 * when the window is destroyed. We need to do this as we don't want
1876 * the window geometry to be saved with the dialpad taken into account. */
1877 g_signal_connect (self
, "destroy",
1878 G_CALLBACK (empathy_call_window_destroyed_cb
), self
);
1879 g_signal_connect (self
, "configure-event",
1880 G_CALLBACK (empathy_call_window_configure_event_cb
), self
);
1881 g_signal_connect (self
, "window-state-event",
1882 G_CALLBACK (empathy_call_window_configure_event_cb
), self
);
1884 /* Don't display labels in both toolbars */
1885 gtk_toolbar_set_style (GTK_TOOLBAR (priv
->toolbar
), GTK_TOOLBAR_ICONS
);
1888 /* Instead of specifying a width and a height, we specify only one size. That's
1889 because we want a square avatar icon. */
1891 init_contact_avatar_with_size (EmpathyContact
*contact
,
1892 GtkWidget
*image_widget
,
1895 GdkPixbuf
*pixbuf_avatar
= NULL
;
1897 if (contact
!= NULL
)
1899 pixbuf_avatar
= empathy_pixbuf_avatar_from_contact_scaled (contact
,
1903 if (pixbuf_avatar
== NULL
)
1905 pixbuf_avatar
= tpaw_pixbuf_from_icon_name_sized (
1906 TPAW_IMAGE_AVATAR_DEFAULT
, size
);
1909 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget
), pixbuf_avatar
);
1911 if (pixbuf_avatar
!= NULL
)
1912 g_object_unref (pixbuf_avatar
);
1916 set_window_title (EmpathyCallWindow
*self
)
1918 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
1921 if (priv
->contact
!= NULL
)
1923 /* translators: Call is a noun and %s is the contact name. This string
1924 * is used in the window title */
1925 tmp
= g_strdup_printf (_("Call with %s"),
1926 empathy_contact_get_alias (priv
->contact
));
1927 gtk_window_set_title (GTK_WINDOW (self
), tmp
);
1932 g_warning ("Unknown remote contact!");
1937 set_remote_user_name (EmpathyCallWindow
*self
,
1938 EmpathyContact
*contact
)
1940 const gchar
*alias
= empathy_contact_get_alias (contact
);
1941 const gchar
*status
= empathy_contact_get_status (contact
);
1943 gtk_label_set_text (GTK_LABEL (self
->priv
->remote_user_name_toolbar
), alias
);
1945 if (status
!= NULL
) {
1948 markup
= g_markup_printf_escaped ("<small>%s</small>", status
);
1949 gtk_label_set_markup (GTK_LABEL (self
->priv
->remote_user_status_toolbar
),
1953 gtk_label_set_markup (GTK_LABEL (self
->priv
->remote_user_status_toolbar
),
1959 contact_name_changed_cb (EmpathyContact
*contact
,
1961 EmpathyCallWindow
*self
)
1963 set_window_title (self
);
1964 set_remote_user_name (self
, contact
);
1968 contact_presence_changed_cb (EmpathyContact
*contact
,
1970 EmpathyCallWindow
*self
)
1972 set_remote_user_name (self
, contact
);
1976 contact_avatar_changed_cb (EmpathyContact
*contact
,
1978 EmpathyCallWindow
*self
)
1981 GtkAllocation allocation
;
1982 GtkWidget
*avatar_widget
;
1984 avatar_widget
= self
->priv
->remote_user_avatar_widget
;
1986 gtk_widget_get_allocation (avatar_widget
, &allocation
);
1987 size
= allocation
.height
;
1991 /* the widget is not allocated yet, set a default size */
1992 size
= MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT
,
1993 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH
);
1996 init_contact_avatar_with_size (contact
, avatar_widget
, size
);
1998 avatar_widget
= self
->priv
->remote_user_avatar_toolbar
;
2000 gtk_widget_get_allocation (avatar_widget
, &allocation
);
2001 size
= allocation
.height
;
2005 /* the widget is not allocated yet, set a default size */
2006 size
= SMALL_TOOLBAR_SIZE
;
2009 init_contact_avatar_with_size (contact
, avatar_widget
, size
);
2013 empathy_call_window_setup_avatars (EmpathyCallWindow
*self
,
2014 EmpathyCallHandler
*handler
)
2016 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2018 tp_g_signal_connect_object (priv
->contact
, "notify::name",
2019 G_CALLBACK (contact_name_changed_cb
), self
, 0);
2020 tp_g_signal_connect_object (priv
->contact
, "notify::avatar",
2021 G_CALLBACK (contact_avatar_changed_cb
), self
, 0);
2022 tp_g_signal_connect_object (priv
->contact
, "notify::presence",
2023 G_CALLBACK (contact_presence_changed_cb
), self
, 0);
2025 set_window_title (self
);
2026 set_remote_user_name (self
, priv
->contact
);
2028 init_contact_avatar_with_size (priv
->contact
,
2029 priv
->remote_user_avatar_widget
,
2030 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH
,
2031 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT
));
2033 init_contact_avatar_with_size (priv
->contact
,
2034 priv
->remote_user_avatar_toolbar
,
2035 SMALL_TOOLBAR_SIZE
);
2037 /* The remote avatar is shown by default and will be hidden when we receive
2038 video from the remote side. */
2039 clutter_actor_hide (priv
->video_output
);
2040 gtk_widget_show (priv
->remote_user_avatar_widget
);
2044 update_send_codec (EmpathyCallWindow
*self
,
2047 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2054 codec
= empathy_call_handler_get_send_audio_codec (priv
->handler
);
2055 widget
= priv
->acodec_encoding_label
;
2059 codec
= empathy_call_handler_get_send_video_codec (priv
->handler
);
2060 widget
= priv
->vcodec_encoding_label
;
2066 tmp
= g_strdup_printf ("%s/%u", codec
->encoding_name
, codec
->clock_rate
);
2067 gtk_label_set_text (GTK_LABEL (widget
), tmp
);
2072 send_audio_codec_notify_cb (GObject
*object
,
2076 EmpathyCallWindow
*self
= user_data
;
2078 update_send_codec (self
, TRUE
);
2082 send_video_codec_notify_cb (GObject
*object
,
2086 EmpathyCallWindow
*self
= user_data
;
2088 update_send_codec (self
, FALSE
);
2092 update_recv_codec (EmpathyCallWindow
*self
,
2095 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2098 GString
*str
= NULL
;
2102 codecs
= empathy_call_handler_get_recv_audio_codecs (priv
->handler
);
2103 widget
= priv
->acodec_decoding_label
;
2107 codecs
= empathy_call_handler_get_recv_video_codecs (priv
->handler
);
2108 widget
= priv
->vcodec_decoding_label
;
2114 for (l
= codecs
; l
!= NULL
; l
= g_list_next (l
))
2116 FsCodec
*codec
= l
->data
;
2119 str
= g_string_new (NULL
);
2121 g_string_append (str
, ", ");
2123 g_string_append_printf (str
, "%s/%u", codec
->encoding_name
,
2127 gtk_label_set_text (GTK_LABEL (widget
), str
->str
);
2128 g_string_free (str
, TRUE
);
2132 recv_audio_codecs_notify_cb (GObject
*object
,
2136 EmpathyCallWindow
*self
= user_data
;
2138 update_recv_codec (self
, TRUE
);
2142 recv_video_codecs_notify_cb (GObject
*object
,
2146 EmpathyCallWindow
*self
= user_data
;
2148 update_recv_codec (self
, FALSE
);
2151 static const gchar
*
2152 candidate_type_to_str (FsCandidate
*candidate
)
2154 switch (candidate
->type
)
2156 case FS_CANDIDATE_TYPE_HOST
:
2158 case FS_CANDIDATE_TYPE_SRFLX
:
2159 return "server reflexive";
2160 case FS_CANDIDATE_TYPE_PRFLX
:
2161 return "peer reflexive";
2162 case FS_CANDIDATE_TYPE_RELAY
:
2164 case FS_CANDIDATE_TYPE_MULTICAST
:
2171 static const gchar
*
2172 candidate_type_to_desc (FsCandidate
*candidate
)
2174 switch (candidate
->type
)
2176 case FS_CANDIDATE_TYPE_HOST
:
2177 return _("The IP address as seen by the machine");
2178 case FS_CANDIDATE_TYPE_SRFLX
:
2179 return _("The IP address as seen by a server on the Internet");
2180 case FS_CANDIDATE_TYPE_PRFLX
:
2181 return _("The IP address of the peer as seen by the other side");
2182 case FS_CANDIDATE_TYPE_RELAY
:
2183 return _("The IP address of a relay server");
2184 case FS_CANDIDATE_TYPE_MULTICAST
:
2185 return _("The IP address of the multicast group");
2192 update_candidat_widget (EmpathyCallWindow
*self
,
2195 FsCandidate
*candidate
)
2199 g_assert (candidate
!= NULL
);
2200 str
= g_strdup_printf ("%s %u (%s)", candidate
->ip
,
2201 candidate
->port
, candidate_type_to_str (candidate
));
2203 gtk_label_set_text (GTK_LABEL (label
), str
);
2204 gtk_widget_set_tooltip_text (img
, candidate_type_to_desc (candidate
));
2210 candidates_changed_cb (GObject
*object
,
2212 EmpathyCallWindow
*self
)
2214 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2215 FsCandidate
*candidate
= NULL
;
2217 if (type
== FS_MEDIA_TYPE_VIDEO
)
2219 /* Update remote candidate */
2220 candidate
= empathy_call_handler_get_video_remote_candidate (
2223 update_candidat_widget (self
, priv
->video_remote_candidate_label
,
2224 priv
->video_remote_candidate_info_img
, candidate
);
2226 /* Update local candidate */
2227 candidate
= empathy_call_handler_get_video_local_candidate (
2230 update_candidat_widget (self
, priv
->video_local_candidate_label
,
2231 priv
->video_local_candidate_info_img
, candidate
);
2235 /* Update remote candidate */
2236 candidate
= empathy_call_handler_get_audio_remote_candidate (
2239 update_candidat_widget (self
, priv
->audio_remote_candidate_label
,
2240 priv
->audio_remote_candidate_info_img
, candidate
);
2242 /* Update local candidate */
2243 candidate
= empathy_call_handler_get_audio_local_candidate (
2246 update_candidat_widget (self
, priv
->audio_local_candidate_label
,
2247 priv
->audio_local_candidate_info_img
, candidate
);
2252 empathy_call_window_constructed (GObject
*object
)
2254 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (object
);
2255 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2256 TpCallChannel
*call
;
2259 g_assert (priv
->handler
!= NULL
);
2261 g_object_get (priv
->handler
, "call-channel", &call
, NULL
);
2262 state
= tp_call_channel_get_state (call
, NULL
, NULL
, NULL
);
2263 priv
->outgoing
= (state
== TP_CALL_STATE_PENDING_INITIATOR
);
2264 tp_clear_object (&call
);
2266 priv
->contact
= empathy_call_handler_get_contact (priv
->handler
);
2267 g_assert (priv
->contact
!= NULL
);
2268 g_object_ref (priv
->contact
);
2270 if (!empathy_contact_can_voip_video (priv
->contact
))
2272 gtk_widget_set_sensitive (priv
->video_call_button
, FALSE
);
2273 gtk_widget_set_sensitive (priv
->camera_button
, FALSE
);
2276 empathy_call_window_setup_avatars (self
, priv
->handler
);
2277 empathy_call_window_set_state_connecting (self
);
2279 if (!empathy_call_handler_has_initial_video (priv
->handler
))
2281 gtk_toggle_button_set_active (
2282 GTK_TOGGLE_BUTTON (priv
->camera_button
), FALSE
);
2284 /* If call has InitialVideo, the preview will be started once the call has
2285 * been started (start_call()). */
2287 update_send_codec (self
, TRUE
);
2288 update_send_codec (self
, FALSE
);
2289 update_recv_codec (self
, TRUE
);
2290 update_recv_codec (self
, FALSE
);
2292 tp_g_signal_connect_object (priv
->handler
, "notify::send-audio-codec",
2293 G_CALLBACK (send_audio_codec_notify_cb
), self
, 0);
2294 tp_g_signal_connect_object (priv
->handler
, "notify::send-video-codec",
2295 G_CALLBACK (send_video_codec_notify_cb
), self
, 0);
2296 tp_g_signal_connect_object (priv
->handler
, "notify::recv-audio-codecs",
2297 G_CALLBACK (recv_audio_codecs_notify_cb
), self
, 0);
2298 tp_g_signal_connect_object (priv
->handler
, "notify::recv-video-codecs",
2299 G_CALLBACK (recv_video_codecs_notify_cb
), self
, 0);
2301 tp_g_signal_connect_object (priv
->handler
, "candidates-changed",
2302 G_CALLBACK (candidates_changed_cb
), self
, 0);
2305 static void empathy_call_window_dispose (GObject
*object
);
2306 static void empathy_call_window_finalize (GObject
*object
);
2309 empathy_call_window_set_property (GObject
*object
,
2310 guint property_id
, const GValue
*value
, GParamSpec
*pspec
)
2312 EmpathyCallWindowPriv
*priv
= GET_PRIV (object
);
2314 switch (property_id
)
2316 case PROP_CALL_HANDLER
:
2317 priv
->handler
= g_value_dup_object (value
);
2320 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
2325 empathy_call_window_get_property (GObject
*object
,
2326 guint property_id
, GValue
*value
, GParamSpec
*pspec
)
2328 EmpathyCallWindowPriv
*priv
= GET_PRIV (object
);
2330 switch (property_id
)
2332 case PROP_CALL_HANDLER
:
2333 g_value_set_object (value
, priv
->handler
);
2336 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
2341 empathy_call_window_class_init (
2342 EmpathyCallWindowClass
*empathy_call_window_class
)
2344 GObjectClass
*object_class
= G_OBJECT_CLASS (empathy_call_window_class
);
2345 GParamSpec
*param_spec
;
2347 g_type_class_add_private (empathy_call_window_class
,
2348 sizeof (EmpathyCallWindowPriv
));
2350 object_class
->constructed
= empathy_call_window_constructed
;
2351 object_class
->set_property
= empathy_call_window_set_property
;
2352 object_class
->get_property
= empathy_call_window_get_property
;
2354 object_class
->dispose
= empathy_call_window_dispose
;
2355 object_class
->finalize
= empathy_call_window_finalize
;
2357 param_spec
= g_param_spec_object ("handler",
2358 "handler", "The call handler",
2359 EMPATHY_TYPE_CALL_HANDLER
,
2360 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
| G_PARAM_STATIC_STRINGS
);
2361 g_object_class_install_property (object_class
,
2362 PROP_CALL_HANDLER
, param_spec
);
2364 signals
[SIG_INHIBIT
] = g_signal_new ("inhibit",
2365 G_OBJECT_CLASS_TYPE (empathy_call_window_class
),
2367 0, NULL
, NULL
, NULL
,
2373 empathy_call_window_dispose (GObject
*object
)
2375 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (object
);
2376 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2378 if (priv
->dispose_has_run
)
2381 priv
->dispose_has_run
= TRUE
;
2383 if (priv
->handler
!= NULL
)
2385 empathy_call_handler_stop_call (priv
->handler
);
2386 tp_clear_object (&priv
->handler
);
2389 if (priv
->bus_message_source_id
!= 0)
2391 g_source_remove (priv
->bus_message_source_id
);
2392 priv
->bus_message_source_id
= 0;
2395 if (priv
->got_video_src
> 0)
2397 g_source_remove (priv
->got_video_src
);
2398 priv
->got_video_src
= 0;
2401 if (priv
->inactivity_src
> 0)
2403 g_source_remove (priv
->inactivity_src
);
2404 priv
->inactivity_src
= 0;
2407 tp_clear_object (&priv
->pipeline
);
2408 tp_clear_object (&priv
->video_input
);
2409 tp_clear_object (&priv
->audio_input
);
2410 tp_clear_object (&priv
->video_tee
);
2411 tp_clear_object (&priv
->ui_manager
);
2412 tp_clear_object (&priv
->fullscreen
);
2413 tp_clear_object (&priv
->camera_monitor
);
2414 tp_clear_object (&priv
->settings
);
2415 tp_clear_object (&priv
->sound_mgr
);
2416 tp_clear_object (&priv
->mic_menu
);
2417 tp_clear_object (&priv
->camera_menu
);
2418 tp_clear_object (&priv
->transitions
);
2420 g_list_free_full (priv
->notifiers
, g_object_unref
);
2422 if (priv
->timer_id
!= 0)
2423 g_source_remove (priv
->timer_id
);
2426 tp_clear_object (&priv
->contact
);
2428 G_OBJECT_CLASS (empathy_call_window_parent_class
)->dispose (object
);
2432 disconnect_video_output_motion_handler (EmpathyCallWindow
*self
)
2434 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2436 if (priv
->video_output_motion_handler_id
!= 0)
2438 g_signal_handler_disconnect (G_OBJECT (priv
->video_container
),
2439 priv
->video_output_motion_handler_id
);
2440 priv
->video_output_motion_handler_id
= 0;
2445 empathy_call_window_finalize (GObject
*object
)
2447 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (object
);
2448 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2450 disconnect_video_output_motion_handler (self
);
2452 /* free any data held directly by the object here */
2453 g_mutex_clear (&priv
->lock
);
2455 g_timer_destroy (priv
->timer
);
2457 G_OBJECT_CLASS (empathy_call_window_parent_class
)->finalize (object
);
2462 empathy_call_window_new (EmpathyCallHandler
*handler
)
2464 return EMPATHY_CALL_WINDOW (
2465 g_object_new (EMPATHY_TYPE_CALL_WINDOW
, "handler", handler
, NULL
));
2469 empathy_call_window_new_handler (EmpathyCallWindow
*self
,
2470 EmpathyCallHandler
*handler
,
2474 g_return_if_fail (EMPATHY_IS_CALL_HANDLER (handler
));
2477 tpaw_window_present_with_time (GTK_WINDOW (self
), x11_time
);
2479 if (self
->priv
->call_state
== DISCONNECTED
)
2481 /* start a new call if one is not already in progress */
2482 tp_clear_object (&self
->priv
->handler
);
2483 self
->priv
->handler
= g_object_ref (handler
);
2484 empathy_call_window_connect_handler (self
);
2486 empathy_call_window_restart_call (self
);
2491 empathy_call_window_conference_added_cb (EmpathyCallHandler
*handler
,
2492 GstElement
*conference
, gpointer user_data
)
2494 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (user_data
);
2495 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2496 FsElementAddedNotifier
*notifier
;
2499 DEBUG ("Conference added");
2501 /* Add notifier to set the various element properties as needed */
2502 notifier
= fs_element_added_notifier_new ();
2503 keyfile
= fs_utils_get_default_element_properties (conference
);
2505 if (keyfile
!= NULL
)
2506 fs_element_added_notifier_set_properties_from_keyfile (notifier
, keyfile
);
2508 fs_element_added_notifier_add (notifier
, GST_BIN (priv
->pipeline
));
2510 priv
->notifiers
= g_list_prepend (priv
->notifiers
, notifier
);
2512 gst_bin_add (GST_BIN (priv
->pipeline
), conference
);
2513 gst_element_set_state (conference
, GST_STATE_PLAYING
);
2517 empathy_call_window_add_notifier_remove (gpointer data
, gpointer user_data
)
2519 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (user_data
);
2520 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2521 FsElementAddedNotifier
*notifier
= data
;
2523 fs_element_added_notifier_remove (notifier
, GST_BIN (priv
->pipeline
));
2527 empathy_call_window_conference_removed_cb (EmpathyCallHandler
*handler
,
2528 GstElement
*conference
, gpointer user_data
)
2530 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (user_data
);
2531 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2533 gst_bin_remove (GST_BIN (priv
->pipeline
), conference
);
2534 gst_element_set_state (conference
, GST_STATE_NULL
);
2536 g_list_foreach (priv
->notifiers
,
2537 empathy_call_window_add_notifier_remove
, user_data
);
2539 g_list_free_full (priv
->notifiers
, g_object_unref
);
2540 priv
->notifiers
= NULL
;
2544 empathy_call_window_reset_pipeline (EmpathyCallWindow
*self
)
2546 GstStateChangeReturn state_change_return
;
2547 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2549 if (priv
->pipeline
== NULL
)
2552 if (priv
->bus_message_source_id
!= 0)
2554 g_source_remove (priv
->bus_message_source_id
);
2555 priv
->bus_message_source_id
= 0;
2558 state_change_return
= gst_element_set_state (priv
->pipeline
, GST_STATE_NULL
);
2560 if (state_change_return
== GST_STATE_CHANGE_SUCCESS
||
2561 state_change_return
== GST_STATE_CHANGE_NO_PREROLL
)
2563 if (priv
->pipeline
!= NULL
)
2564 g_object_unref (priv
->pipeline
);
2565 priv
->pipeline
= NULL
;
2567 if (priv
->audio_output
!= NULL
)
2568 g_object_unref (priv
->audio_output
);
2569 priv
->audio_output
= NULL
;
2570 priv
->audio_output_added
= FALSE
;
2572 if (priv
->video_tee
!= NULL
)
2573 g_object_unref (priv
->video_tee
);
2574 priv
->video_tee
= NULL
;
2576 if (priv
->video_preview
!= NULL
)
2577 clutter_actor_destroy (priv
->video_preview
);
2578 priv
->video_preview
= NULL
;
2580 if (priv
->preview_content
!= NULL
)
2581 g_object_unref (priv
->preview_content
);
2582 priv
->preview_content
= NULL
;
2584 /* If we destroy the preview while it's being dragged, we won't
2585 * get the ::drag-end signal, so manually destroy the clone */
2586 if (priv
->drag_preview
!= NULL
)
2588 clutter_actor_destroy (priv
->drag_preview
);
2589 empathy_call_window_show_preview_rectangles (self
, FALSE
);
2590 priv
->drag_preview
= NULL
;
2593 priv
->funnel
= NULL
;
2595 create_pipeline (self
);
2596 /* Call will be started when user will hit the 'redial' button */
2597 priv
->start_call_when_playing
= FALSE
;
2598 gst_element_set_state (priv
->pipeline
, GST_STATE_PAUSED
);
2604 g_message ("Error: could not destroy pipeline. Closing call window");
2605 gtk_widget_destroy (GTK_WIDGET (self
));
2612 reset_details_pane (EmpathyCallWindow
*self
)
2614 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2616 gtk_label_set_text (GTK_LABEL (priv
->vcodec_encoding_label
), _("Unknown"));
2617 gtk_label_set_text (GTK_LABEL (priv
->acodec_encoding_label
), _("Unknown"));
2618 gtk_label_set_text (GTK_LABEL (priv
->vcodec_decoding_label
), _("Unknown"));
2619 gtk_label_set_text (GTK_LABEL (priv
->acodec_decoding_label
), _("Unknown"));
2623 empathy_call_window_disconnected (EmpathyCallWindow
*self
,
2626 gboolean could_disconnect
= FALSE
;
2627 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2628 gboolean could_reset_pipeline
;
2630 /* Leave full screen mode if needed */
2631 gtk_window_unfullscreen (GTK_WINDOW (self
));
2633 g_signal_emit (self
, signals
[SIG_INHIBIT
], 0, FALSE
);
2635 gtk_action_set_sensitive (priv
->menu_fullscreen
, FALSE
);
2636 gtk_widget_set_sensitive (priv
->dtmf_panel
, FALSE
);
2638 /* Create the video input and then turn on the camera
2639 * menu, so that the active camera gets marked as such.
2641 if (priv
->video_input
== NULL
)
2642 create_video_input (self
);
2643 empathy_camera_menu_set_sensitive (priv
->camera_menu
, TRUE
);
2645 could_reset_pipeline
= empathy_call_window_reset_pipeline (self
);
2647 if (priv
->call_state
== CONNECTING
)
2648 empathy_sound_manager_stop (priv
->sound_mgr
, EMPATHY_SOUND_PHONE_OUTGOING
);
2650 if (priv
->call_state
!= REDIALING
)
2651 priv
->call_state
= DISCONNECTED
;
2653 /* Show the toolbar */
2654 clutter_state_set_state (priv
->transitions
, "fade-in");
2656 if (could_reset_pipeline
)
2658 g_mutex_lock (&priv
->lock
);
2660 g_timer_stop (priv
->timer
);
2662 if (priv
->timer_id
!= 0)
2663 g_source_remove (priv
->timer_id
);
2666 g_mutex_unlock (&priv
->lock
);
2669 /* We are about to destroy the window, no need to update it or create
2670 * a video preview */
2673 empathy_call_window_status_message (self
, _("Disconnected"));
2675 empathy_call_window_show_hangup_button (self
, FALSE
);
2677 /* Unsensitive the camera and mic button */
2678 gtk_widget_set_sensitive (priv
->camera_button
, FALSE
);
2679 gtk_widget_set_sensitive (priv
->mic_button
, FALSE
);
2681 /* Be sure that the mic button is enabled */
2682 empathy_audio_src_set_mute (
2683 EMPATHY_GST_AUDIO_SRC (self
->priv
->audio_input
), FALSE
);
2685 if (priv
->camera_state
== CAMERA_STATE_ON
)
2687 /* Restart the preview with the new pipeline. */
2688 display_video_preview (self
, TRUE
);
2691 /* destroy the video output; it will be recreated when we'll redial */
2692 disconnect_video_output_motion_handler (self
);
2693 if (priv
->video_output
!= NULL
)
2694 clutter_actor_destroy (priv
->video_output
);
2695 priv
->video_output
= NULL
;
2696 if (priv
->got_video_src
> 0)
2698 g_source_remove (priv
->got_video_src
);
2699 priv
->got_video_src
= 0;
2702 gtk_widget_show (priv
->remote_user_avatar_widget
);
2704 reset_details_pane (self
);
2706 priv
->sending_video
= FALSE
;
2707 priv
->call_started
= FALSE
;
2709 could_disconnect
= TRUE
;
2711 /* TODO: display the self avatar of the preview (depends if the "Always
2712 * Show Video Preview" is enabled or not) */
2715 return could_disconnect
;
2720 empathy_call_window_channel_closed_cb (EmpathyCallHandler
*handler
,
2723 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (user_data
);
2724 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2726 if (empathy_call_window_disconnected (self
, TRUE
) &&
2727 priv
->call_state
== REDIALING
)
2728 empathy_call_window_restart_call (self
);
2732 empathy_call_window_content_is_raw (TfContent
*content
)
2734 FsConference
*conference
;
2737 g_object_get (content
, "fs-conference", &conference
, NULL
);
2738 g_assert (conference
!= NULL
);
2740 /* FIXME: Ugly hack, update when moving a packetization property into
2742 israw
= g_str_has_prefix (GST_OBJECT_NAME (conference
), "fsrawconf");
2743 gst_object_unref (conference
);
2749 empathy_call_window_content_removed_cb (EmpathyCallHandler
*handler
,
2751 EmpathyCallWindow
*self
)
2753 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2754 FsMediaType media_type
;
2756 DEBUG ("removing content");
2758 g_object_get (content
, "media-type", &media_type
, NULL
);
2761 * This assumes that there is only one video stream per channel...
2764 if ((guint
) media_type
== FS_MEDIA_TYPE_VIDEO
)
2766 if (priv
->funnel
!= NULL
)
2770 output
= priv
->video_output_sink
;
2772 gst_element_set_state (output
, GST_STATE_NULL
);
2773 gst_element_set_state (priv
->funnel
, GST_STATE_NULL
);
2775 gst_bin_remove (GST_BIN (priv
->pipeline
), output
);
2776 gst_bin_remove (GST_BIN (priv
->pipeline
), priv
->funnel
);
2777 priv
->funnel
= NULL
;
2780 else if (media_type
== FS_MEDIA_TYPE_AUDIO
)
2782 if (priv
->audio_output
!= NULL
)
2784 gst_element_set_state (priv
->audio_output
, GST_STATE_NULL
);
2786 if (priv
->audio_output_added
)
2787 gst_bin_remove (GST_BIN (priv
->pipeline
), priv
->audio_output
);
2788 priv
->audio_output
= NULL
;
2789 priv
->audio_output_added
= FALSE
;
2794 g_assert_not_reached ();
2801 empathy_call_window_framerate_changed_cb (EmpathyCallHandler
*handler
,
2803 EmpathyCallWindow
*self
)
2805 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2807 DEBUG ("Framerate changed to %u", framerate
);
2809 if (priv
->video_input
!= NULL
)
2810 empathy_video_src_set_framerate (priv
->video_input
, framerate
);
2814 empathy_call_window_resolution_changed_cb (EmpathyCallHandler
*handler
,
2817 EmpathyCallWindow
*self
)
2819 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2821 DEBUG ("Resolution changed to %ux%u", width
, height
);
2823 if (priv
->video_input
!= NULL
)
2825 empathy_video_src_set_resolution (priv
->video_input
, width
, height
);
2829 /* Called with global lock held */
2831 empathy_call_window_get_video_sink_pad (EmpathyCallWindow
*self
)
2833 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2837 if (priv
->funnel
== NULL
)
2839 output
= priv
->video_output_sink
;
2840 priv
->funnel
= gst_element_factory_make ("funnel", NULL
);
2844 g_warning ("Could not create video funnel");
2848 if (!gst_bin_add (GST_BIN (priv
->pipeline
), priv
->funnel
))
2850 gst_object_unref (priv
->funnel
);
2851 priv
->funnel
= NULL
;
2852 g_warning ("Could not add funnel to pipeline");
2856 if (!gst_bin_add (GST_BIN (priv
->pipeline
), output
))
2858 g_warning ("Could not add the video output widget to the pipeline");
2862 if (!gst_element_link (priv
->funnel
, output
))
2864 g_warning ("Could not link output sink to funnel");
2865 goto error_output_added
;
2868 if (gst_element_set_state (output
, GST_STATE_PLAYING
) == GST_STATE_CHANGE_FAILURE
)
2870 g_warning ("Could not start video sink");
2871 goto error_output_added
;
2874 if (gst_element_set_state (priv
->funnel
, GST_STATE_PLAYING
) == GST_STATE_CHANGE_FAILURE
)
2876 g_warning ("Could not start funnel");
2877 goto error_output_added
;
2880 pad
= gst_element_get_request_pad (priv
->funnel
, "sink_%u");
2883 g_warning ("Could not get request pad from funnel");
2890 gst_element_set_locked_state (priv
->funnel
, TRUE
);
2891 gst_element_set_locked_state (output
, TRUE
);
2893 gst_element_set_state (priv
->funnel
, GST_STATE_NULL
);
2894 gst_element_set_state (output
, GST_STATE_NULL
);
2896 gst_bin_remove (GST_BIN (priv
->pipeline
), output
);
2897 gst_element_set_locked_state (output
, FALSE
);
2901 gst_bin_remove (GST_BIN (priv
->pipeline
), priv
->funnel
);
2902 priv
->funnel
= NULL
;
2907 /* Called with global lock held */
2909 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow
*self
,
2912 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2914 GstPadTemplate
*template;
2916 if (!priv
->audio_output_added
)
2918 if (!gst_bin_add (GST_BIN (priv
->pipeline
), priv
->audio_output
))
2920 g_warning ("Could not add audio sink to pipeline");
2921 g_object_unref (priv
->audio_output
);
2922 goto error_add_output
;
2925 if (gst_element_set_state (priv
->audio_output
, GST_STATE_PLAYING
) == GST_STATE_CHANGE_FAILURE
)
2927 g_warning ("Could not start audio sink");
2930 priv
->audio_output_added
= TRUE
;
2933 template = gst_element_class_get_pad_template (
2934 GST_ELEMENT_GET_CLASS (priv
->audio_output
), "sink%d");
2936 pad
= gst_element_request_pad (priv
->audio_output
,
2937 template, NULL
, NULL
);
2941 g_warning ("Could not get sink pad from sink");
2948 gst_element_set_locked_state (priv
->audio_output
, TRUE
);
2949 gst_element_set_state (priv
->audio_output
, GST_STATE_NULL
);
2950 gst_bin_remove (GST_BIN (priv
->pipeline
), priv
->audio_output
);
2951 priv
->audio_output
= NULL
;
2959 empathy_call_window_update_timer (gpointer user_data
)
2961 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (user_data
);
2962 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
2963 const gchar
*status
;
2967 time_
= g_timer_elapsed (priv
->timer
, NULL
);
2969 if (priv
->call_state
== HELD
)
2970 status
= _("On hold");
2971 else if (priv
->call_state
== DISCONNECTED
)
2972 status
= _("Disconnected");
2973 else if (priv
->muted
)
2976 status
= _("Duration");
2978 /* Translators: 'status — minutes∶seconds' the caller has been connected */
2979 str
= g_strdup_printf (_("%s — %d∶%02dm"),
2981 (int) time_
/ 60, (int) time_
% 60);
2982 empathy_call_window_status_message (self
, str
);
2990 EMP_RESPONSE_BALANCE
2994 on_error_infobar_response_cb (GtkInfoBar
*info_bar
,
2998 switch (response_id
)
3000 case GTK_RESPONSE_CLOSE
:
3001 gtk_widget_destroy (GTK_WIDGET (info_bar
));
3003 case EMP_RESPONSE_BALANCE
:
3004 empathy_url_show (GTK_WIDGET (info_bar
),
3005 g_object_get_data (G_OBJECT (info_bar
), "uri"));
3011 display_error (EmpathyCallWindow
*self
,
3015 const gchar
*details
,
3016 const gchar
*button_text
,
3018 gint button_response
)
3020 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3021 GtkWidget
*info_bar
;
3022 GtkWidget
*content_area
;
3029 /* Create info bar */
3030 info_bar
= gtk_info_bar_new ();
3032 if (button_text
!= NULL
)
3034 gtk_info_bar_add_button (GTK_INFO_BAR (info_bar
),
3035 button_text
, button_response
);
3036 g_object_set_data_full (G_OBJECT (info_bar
),
3037 "uri", g_strdup (uri
), g_free
);
3040 gtk_info_bar_add_button (GTK_INFO_BAR (info_bar
),
3041 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
);
3043 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar
), GTK_MESSAGE_WARNING
);
3045 content_area
= gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar
));
3047 /* hbox containing the image and the messages vbox */
3048 hbox
= gtk_box_new (GTK_ORIENTATION_HORIZONTAL
, 3);
3049 gtk_container_add (GTK_CONTAINER (content_area
), hbox
);
3052 image
= gtk_image_new_from_icon_name (img
, GTK_ICON_SIZE_DIALOG
);
3053 gtk_box_pack_start (GTK_BOX (hbox
), image
, FALSE
, FALSE
, 0);
3055 /* vbox containing the main message and the details expander */
3056 vbox
= gtk_box_new (GTK_ORIENTATION_VERTICAL
, 3);
3057 gtk_box_pack_start (GTK_BOX (hbox
), vbox
, TRUE
, TRUE
, 0);
3060 txt
= g_strdup_printf ("<b>%s</b>\n%s", title
, desc
);
3062 label
= gtk_label_new (NULL
);
3063 gtk_label_set_markup (GTK_LABEL (label
), txt
);
3064 gtk_label_set_line_wrap (GTK_LABEL (label
), TRUE
);
3065 gtk_misc_set_alignment (GTK_MISC (label
), 0, 0);
3068 gtk_box_pack_start (GTK_BOX (vbox
), label
, TRUE
, TRUE
, 0);
3071 if (details
!= NULL
)
3073 GtkWidget
*expander
;
3075 expander
= gtk_expander_new (_("Technical Details"));
3077 txt
= g_strdup_printf ("<i>%s</i>", details
);
3079 label
= gtk_label_new (NULL
);
3080 gtk_label_set_markup (GTK_LABEL (label
), txt
);
3081 gtk_label_set_line_wrap (GTK_LABEL (label
), TRUE
);
3082 gtk_misc_set_alignment (GTK_MISC (label
), 0, 0);
3085 gtk_container_add (GTK_CONTAINER (expander
), label
);
3086 gtk_box_pack_start (GTK_BOX (vbox
), expander
, TRUE
, TRUE
, 0);
3089 g_signal_connect (info_bar
, "response",
3090 G_CALLBACK (on_error_infobar_response_cb
), NULL
);
3092 gtk_box_pack_start (GTK_BOX (priv
->errors_vbox
), info_bar
,
3093 FALSE
, FALSE
, CONTENT_HBOX_CHILDREN_PACKING_PADDING
);
3094 gtk_widget_show_all (info_bar
);
3099 media_stream_error_to_txt (EmpathyCallWindow
*self
,
3100 TpCallChannel
*call
,
3102 TpMediaStreamError error
)
3104 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3105 const gchar
*cm
= NULL
;
3111 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED
:
3113 return g_strdup_printf (
3114 _("%s’s software does not understand any of the audio formats "
3115 "supported by your computer"),
3116 empathy_contact_get_alias (priv
->contact
));
3118 return g_strdup_printf (
3119 _("%s’s software does not understand any of the video formats "
3120 "supported by your computer"),
3121 empathy_contact_get_alias (priv
->contact
));
3123 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED
:
3124 return g_strdup_printf (
3125 _("Can’t establish a connection to %s. "
3126 "One of you might be on a network that does not allow "
3127 "direct connections."),
3128 empathy_contact_get_alias (priv
->contact
));
3130 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR
:
3131 return g_strdup (_("There was a failure on the network"));
3133 case TP_MEDIA_STREAM_ERROR_NO_CODECS
:
3135 return g_strdup (_("The audio formats necessary for this call "
3136 "are not installed on your computer"));
3138 return g_strdup (_("The video formats necessary for this call "
3139 "are not installed on your computer"));
3141 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR
:
3142 tp_connection_parse_object_path (
3143 tp_channel_get_connection (TP_CHANNEL (call
)),
3146 url
= g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
3147 "product=Telepathy&component=%s", cm
);
3149 result
= g_strdup_printf (
3150 _("Something unexpected happened in a Telepathy component. "
3151 "Please <a href=\"%s\">report this bug</a> and attach "
3152 "logs gathered from the “Debug” window in the Help menu."), url
);
3158 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR
:
3159 return g_strdup (_("There was a failure in the call engine"));
3161 case TP_MEDIA_STREAM_ERROR_EOS
:
3162 return g_strdup (_("The end of the stream was reached"));
3164 case TP_MEDIA_STREAM_ERROR_UNKNOWN
:
3171 empathy_call_window_stream_error (EmpathyCallWindow
*self
,
3172 TpCallChannel
*call
,
3181 desc
= media_stream_error_to_txt (self
, call
, audio
, code
);
3184 /* No description, use the error message. That's not great as it's not
3185 * localized but it's better than nothing. */
3186 display_error (self
, call
, icon
, title
, msg
, NULL
);
3190 display_error (self
, call
, icon
, title
, desc
, msg
);
3196 empathy_call_window_audio_stream_error (TpCallChannel
*call
,
3199 EmpathyCallWindow
*self
)
3201 empathy_call_window_stream_error (self
, call
, TRUE
, code
, msg
,
3202 "gnome-stock-mic", _("Can’t establish audio stream"));
3206 empathy_call_window_video_stream_error (TpCallChannel
*call
,
3209 EmpathyCallWindow
*self
)
3211 empathy_call_window_stream_error (self
, call
, FALSE
, code
, msg
,
3212 "camera-web", _("Can’t establish video stream"));
3217 show_balance_error (EmpathyCallWindow
*self
)
3221 gchar
*balance
, *tmp
;
3222 const gchar
*uri
, *currency
;
3226 g_object_get (self
->priv
->handler
,
3227 "call-channel", &call
,
3230 conn
= tp_channel_get_connection (call
);
3231 g_object_unref (call
);
3233 uri
= tp_connection_get_balance_uri (conn
);
3235 if (!tp_connection_get_balance (conn
, &amount
, &scale
, ¤cy
))
3237 /* unknown balance */
3238 balance
= g_strdup ("(--)");
3242 char *money
= empathy_format_currency (amount
, scale
, currency
);
3244 balance
= g_strdup_printf ("%s %s",
3249 tmp
= g_strdup_printf (_("Your current balance is %s."), balance
),
3251 display_error (self
,
3253 _("Sorry, you don’t have enough credit for that call."),
3257 EMP_RESPONSE_BALANCE
);
3264 empathy_call_window_state_changed_cb (EmpathyCallHandler
*handler
,
3267 EmpathyCallWindow
*self
)
3269 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3270 TpCallChannel
*call
;
3271 gboolean can_send_video
;
3273 if (state
== TP_CALL_STATE_ENDED
)
3275 DEBUG ("Call ended: %s", (reason
!= NULL
&& reason
[0] != '\0') ? reason
: "unspecified reason");
3276 empathy_call_window_disconnected (self
, TRUE
);
3277 if (!tp_strdiff (reason
, TP_ERROR_STR_INSUFFICIENT_BALANCE
))
3278 show_balance_error (self
);
3282 if (state
!= TP_CALL_STATE_ACCEPTED
)
3285 if (priv
->call_state
== CONNECTED
)
3288 g_timer_start (priv
->timer
);
3289 priv
->call_state
= CONNECTED
;
3291 empathy_sound_manager_stop (priv
->sound_mgr
, EMPATHY_SOUND_PHONE_OUTGOING
);
3293 can_send_video
= priv
->video_input
!= NULL
&&
3294 empathy_contact_can_voip_video (priv
->contact
) &&
3295 tpaw_camera_monitor_get_available (priv
->camera_monitor
);
3297 g_object_get (priv
->handler
, "call-channel", &call
, NULL
);
3299 if (tp_call_channel_has_dtmf (call
))
3300 gtk_widget_set_sensitive (priv
->dtmf_panel
, TRUE
);
3302 if (priv
->video_input
== NULL
)
3303 empathy_call_window_set_send_video (self
, CAMERA_STATE_OFF
);
3305 gtk_widget_set_sensitive (priv
->camera_button
, can_send_video
);
3307 empathy_call_window_show_hangup_button (self
, TRUE
);
3309 gtk_widget_set_sensitive (priv
->mic_button
, TRUE
);
3311 clutter_actor_hide (priv
->video_output
);
3312 gtk_widget_show (priv
->remote_user_avatar_widget
);
3314 g_object_unref (call
);
3316 g_mutex_lock (&priv
->lock
);
3318 priv
->timer_id
= g_timeout_add_seconds (1,
3319 empathy_call_window_update_timer
, self
);
3321 g_mutex_unlock (&priv
->lock
);
3323 empathy_call_window_update_timer (self
);
3325 gtk_action_set_sensitive (priv
->menu_fullscreen
, TRUE
);
3329 empathy_call_window_show_video_output_cb (gpointer user_data
)
3331 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (user_data
);
3333 if (self
->priv
->video_output
!= NULL
)
3335 gtk_widget_hide (self
->priv
->remote_user_avatar_widget
);
3336 clutter_actor_show (self
->priv
->video_output
);
3337 clutter_actor_raise_top (self
->priv
->overlay_bin
);
3344 empathy_call_window_check_video_cb (gpointer data
)
3346 EmpathyCallWindow
*self
= data
;
3348 if (self
->priv
->got_video
)
3350 self
->priv
->got_video
= FALSE
;
3354 /* No video in the last N seconds, display the remote avatar */
3355 empathy_call_window_show_video_output (self
, FALSE
);
3360 /* Called from the streaming thread */
3361 static GstPadProbeReturn
3362 empathy_call_window_video_probe_cb (GstPad
*pad
,
3363 GstPadProbeInfo
*info
,
3366 EmpathyCallWindow
*self
= user_data
;
3368 if (G_UNLIKELY (!self
->priv
->got_video
))
3370 /* show the remote video */
3371 g_idle_add_full (G_PRIORITY_DEFAULT_IDLE
,
3372 empathy_call_window_show_video_output_cb
,
3373 g_object_ref (self
), g_object_unref
);
3375 self
->priv
->got_video
= TRUE
;
3378 return GST_PAD_PROBE_OK
;
3381 /* Called from the streaming thread */
3383 empathy_call_window_src_added_cb (EmpathyCallHandler
*handler
,
3384 TfContent
*content
, GstPad
*src
, gpointer user_data
)
3386 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (user_data
);
3387 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3388 gboolean retval
= FALSE
;
3393 g_mutex_lock (&priv
->lock
);
3395 g_object_get (content
, "media-type", &media_type
, NULL
);
3399 case TP_MEDIA_STREAM_TYPE_AUDIO
:
3400 pad
= empathy_call_window_get_audio_sink_pad (self
, content
);
3402 case TP_MEDIA_STREAM_TYPE_VIDEO
:
3403 g_idle_add (empathy_call_window_show_video_output_cb
, self
);
3404 pad
= empathy_call_window_get_video_sink_pad (self
);
3406 gst_pad_add_probe (src
,
3407 GST_PAD_PROBE_TYPE_BUFFER
| GST_PAD_PROBE_TYPE_BUFFER_LIST
,
3408 empathy_call_window_video_probe_cb
, self
, NULL
);
3410 if (priv
->got_video_src
> 0)
3411 g_source_remove (priv
->got_video_src
);
3412 priv
->got_video_src
= g_timeout_add_seconds (1,
3413 empathy_call_window_check_video_cb
, self
);
3416 g_assert_not_reached ();
3422 if (GST_PAD_LINK_FAILED (gst_pad_link (src
, pad
)))
3423 g_warning ("Could not link %s sink pad",
3424 media_type
== TP_MEDIA_STREAM_TYPE_AUDIO
? "audio" : "video");
3428 gst_object_unref (pad
);
3432 /* If no sink could be linked, try to add fakesink to prevent the whole call
3437 GstElement
*fakesink
= gst_element_factory_make ("fakesink", NULL
);
3439 if (gst_bin_add (GST_BIN (priv
->pipeline
), fakesink
))
3441 GstPad
*sinkpad
= gst_element_get_static_pad (fakesink
, "sink");
3442 if (gst_element_set_state (fakesink
, GST_STATE_PLAYING
) == GST_STATE_CHANGE_FAILURE
||
3443 GST_PAD_LINK_FAILED (gst_pad_link (src
, sinkpad
)))
3445 gst_element_set_locked_state (fakesink
, TRUE
);
3446 gst_element_set_state (fakesink
, GST_STATE_NULL
);
3447 gst_bin_remove (GST_BIN (priv
->pipeline
), fakesink
);
3451 DEBUG ("Could not link real sink, linked fakesink instead");
3453 gst_object_unref (sinkpad
);
3457 gst_object_unref (fakesink
);
3462 g_mutex_unlock (&priv
->lock
);
3468 empathy_call_window_prepare_audio_output (EmpathyCallWindow
*self
,
3471 EmpathyCallWindowPriv
*priv
= self
->priv
;
3473 g_assert (priv
->audio_output_added
== FALSE
);
3474 g_assert (priv
->audio_output
== FALSE
);
3476 priv
->audio_output
= empathy_audio_sink_new ();
3477 g_object_ref_sink (priv
->audio_output
);
3479 /* volume button to output volume linking */
3480 g_object_bind_property (priv
->audio_output
, "volume",
3481 priv
->volume_button
, "value",
3482 G_BINDING_BIDIRECTIONAL
| G_BINDING_SYNC_CREATE
);
3484 g_object_bind_property_full (content
, "requested-output-volume",
3485 priv
->audio_output
, "volume",
3487 audio_control_volume_to_element
,
3488 element_volume_to_audio_control
,
3491 /* Link volumes together, sync the current audio input volume property
3492 * back to farstream first */
3493 g_object_bind_property_full (priv
->audio_output
, "volume",
3494 content
, "reported-output-volume",
3495 G_BINDING_SYNC_CREATE
,
3496 element_volume_to_audio_control
,
3497 audio_control_volume_to_element
,
3500 /* For raw audio conferences assume that the producer of the raw data
3501 * has already processed it, so turn off any echo cancellation and any
3502 * other audio improvements that come with it */
3503 empathy_audio_sink_set_echo_cancel (
3504 EMPATHY_GST_AUDIO_SINK (priv
->audio_output
),
3505 !empathy_call_window_content_is_raw (content
));
3510 empathy_call_window_content_added_cb (EmpathyCallHandler
*handler
,
3511 TfContent
*content
, gpointer user_data
)
3513 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (user_data
);
3514 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3516 FsMediaType media_type
;
3517 gboolean retval
= FALSE
;
3519 g_object_get (content
, "media-type", &media_type
, "sink-pad", &sink
, NULL
);
3520 g_assert (sink
!= NULL
);
3524 case FS_MEDIA_TYPE_AUDIO
:
3526 /* For raw audio conferences assume that the receiver of the raw data
3527 * wants it unprocessed, so turn off any echo cancellation and any
3528 * other audio improvements that come with it */
3529 empathy_audio_src_set_echo_cancel (
3530 EMPATHY_GST_AUDIO_SRC (priv
->audio_input
),
3531 !empathy_call_window_content_is_raw (content
));
3533 /* Link volumes together, sync the current audio input volume property
3534 * back to farstream first */
3535 g_object_bind_property_full (content
, "requested-input-volume",
3536 priv
->audio_input
, "volume",
3538 audio_control_volume_to_element
,
3539 element_volume_to_audio_control
,
3542 g_object_bind_property_full (priv
->audio_input
, "volume",
3543 content
, "reported-input-volume",
3544 G_BINDING_SYNC_CREATE
,
3545 element_volume_to_audio_control
,
3546 audio_control_volume_to_element
,
3549 if (!gst_bin_add (GST_BIN (priv
->pipeline
), priv
->audio_input
))
3551 g_warning ("Could not add audio source to pipeline");
3555 pad
= gst_element_get_static_pad (priv
->audio_input
, "src");
3558 gst_bin_remove (GST_BIN (priv
->pipeline
), priv
->audio_input
);
3559 g_warning ("Could not get source pad from audio source");
3563 if (GST_PAD_LINK_FAILED (gst_pad_link (pad
, sink
)))
3565 gst_bin_remove (GST_BIN (priv
->pipeline
), priv
->audio_input
);
3566 gst_object_unref (pad
);
3567 g_warning ("Could not link audio source to farsight");
3570 gst_object_unref (pad
);
3572 if (gst_element_set_state (priv
->audio_input
, GST_STATE_PLAYING
) == GST_STATE_CHANGE_FAILURE
)
3574 g_warning ("Could not start audio source");
3575 gst_element_set_state (priv
->audio_input
, GST_STATE_NULL
);
3576 gst_bin_remove (GST_BIN (priv
->pipeline
), priv
->audio_input
);
3580 /* Prepare our audio output, not added yet though */
3581 empathy_call_window_prepare_audio_output (self
, content
);
3585 case FS_MEDIA_TYPE_VIDEO
:
3586 if (priv
->video_tee
!= NULL
)
3588 pad
= gst_element_get_request_pad (priv
->video_tee
, "src_%u");
3589 if (GST_PAD_LINK_FAILED (gst_pad_link (pad
, sink
)))
3591 g_warning ("Could not link video source input pipeline");
3594 gst_object_unref (pad
);
3600 g_assert_not_reached ();
3603 gst_object_unref (sink
);
3608 empathy_call_window_remove_video_input (EmpathyCallWindow
*self
)
3610 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3611 GstElement
*preview
;
3613 disable_camera (self
);
3615 DEBUG ("remove video input");
3616 preview
= priv
->video_preview_sink
;
3618 gst_element_set_state (priv
->video_input
, GST_STATE_NULL
);
3619 gst_element_set_state (priv
->video_tee
, GST_STATE_NULL
);
3620 gst_element_set_state (preview
, GST_STATE_NULL
);
3622 gst_bin_remove_many (GST_BIN (priv
->pipeline
), priv
->video_input
,
3625 g_object_unref (priv
->video_input
);
3626 priv
->video_input
= NULL
;
3627 g_object_unref (priv
->video_tee
);
3628 priv
->video_tee
= NULL
;
3629 clutter_actor_destroy (priv
->video_preview
);
3630 priv
->video_preview
= NULL
;
3632 gtk_widget_set_sensitive (priv
->camera_button
, FALSE
);
3633 empathy_camera_menu_set_sensitive (priv
->camera_menu
, FALSE
);
3637 start_call (EmpathyCallWindow
*self
)
3639 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3641 g_signal_emit (self
, signals
[SIG_INHIBIT
], 0, TRUE
);
3643 priv
->call_started
= TRUE
;
3644 empathy_call_handler_start_call (priv
->handler
,
3645 gtk_get_current_event_time ());
3647 if (empathy_call_handler_has_initial_video (priv
->handler
))
3649 TpCallChannel
*call
;
3652 g_object_get (priv
->handler
, "call-channel", &call
, NULL
);
3653 /* If the call channel isn't set yet we're requesting it, if we're
3654 * requesting it with initial video it should be PENDING_SEND when we get
3657 s
= TP_SENDING_STATE_PENDING_SEND
;
3659 s
= empathy_call_channel_get_video_state (call
);
3661 if (s
== TP_SENDING_STATE_PENDING_SEND
||
3662 s
== TP_SENDING_STATE_SENDING
)
3664 /* Enable 'send video' buttons and display the preview */
3665 gtk_toggle_button_set_active (
3666 GTK_TOGGLE_BUTTON (priv
->camera_button
), TRUE
);
3670 gtk_toggle_button_set_active (
3671 GTK_TOGGLE_BUTTON (priv
->camera_button
), FALSE
);
3673 if (priv
->video_preview
== NULL
)
3675 create_video_preview (self
);
3676 add_video_preview_to_pipeline (self
);
3681 g_object_unref (call
);
3686 empathy_call_window_bus_message (GstBus
*bus
, GstMessage
*message
,
3689 EmpathyCallWindow
*self
= EMPATHY_CALL_WINDOW (user_data
);
3690 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3691 GstState newstate
, pending
;
3693 empathy_call_handler_bus_message (priv
->handler
, bus
, message
);
3695 switch (GST_MESSAGE_TYPE (message
))
3697 case GST_MESSAGE_STATE_CHANGED
:
3698 if (GST_MESSAGE_SRC (message
) == GST_OBJECT (priv
->video_input
))
3700 gst_message_parse_state_changed (message
, NULL
, &newstate
, NULL
);
3702 if (GST_MESSAGE_SRC (message
) == GST_OBJECT (priv
->pipeline
) &&
3703 !priv
->call_started
)
3705 gst_message_parse_state_changed (message
, NULL
, &newstate
, NULL
);
3706 if (newstate
== GST_STATE_PAUSED
)
3708 gst_element_set_state (priv
->pipeline
, GST_STATE_PLAYING
);
3709 priv
->pipeline_playing
= TRUE
;
3711 if (priv
->start_call_when_playing
)
3715 if (priv
->video_preview_sink
!= NULL
&&
3716 GST_MESSAGE_SRC (message
) == GST_OBJECT (priv
->video_preview_sink
))
3718 gst_message_parse_state_changed (message
, NULL
, &newstate
,
3721 if (newstate
== GST_STATE_PLAYING
&&
3722 pending
== GST_STATE_VOID_PENDING
)
3723 empathy_call_window_stop_camera_spinning (self
);
3726 case GST_MESSAGE_ERROR
:
3728 GError
*error
= NULL
;
3729 GstElement
*gst_error
;
3733 gst_message_parse_error (message
, &error
, &debug
);
3734 gst_error
= GST_ELEMENT (GST_MESSAGE_SRC (message
));
3736 g_message ("Element error: %s -- %s\n", error
->message
, debug
);
3738 name
= gst_element_get_name (gst_error
);
3739 if (g_str_has_prefix (name
, VIDEO_INPUT_ERROR_PREFIX
))
3741 /* Remove the video input and continue */
3742 if (priv
->video_input
!= NULL
)
3743 empathy_call_window_remove_video_input (self
);
3744 gst_element_set_state (priv
->pipeline
, GST_STATE_PLAYING
);
3748 empathy_call_window_disconnected (self
, TRUE
);
3751 g_error_free (error
);
3754 case GST_MESSAGE_UNKNOWN
:
3755 case GST_MESSAGE_EOS
:
3756 case GST_MESSAGE_WARNING
:
3757 case GST_MESSAGE_INFO
:
3758 case GST_MESSAGE_TAG
:
3759 case GST_MESSAGE_BUFFERING
:
3760 case GST_MESSAGE_STATE_DIRTY
:
3761 case GST_MESSAGE_STEP_DONE
:
3762 case GST_MESSAGE_CLOCK_PROVIDE
:
3763 case GST_MESSAGE_CLOCK_LOST
:
3764 case GST_MESSAGE_NEW_CLOCK
:
3765 case GST_MESSAGE_STRUCTURE_CHANGE
:
3766 case GST_MESSAGE_STREAM_STATUS
:
3767 case GST_MESSAGE_APPLICATION
:
3768 case GST_MESSAGE_ELEMENT
:
3769 case GST_MESSAGE_SEGMENT_START
:
3770 case GST_MESSAGE_SEGMENT_DONE
:
3771 case GST_MESSAGE_DURATION
:
3772 case GST_MESSAGE_ANY
:
3781 empathy_call_window_members_changed_cb (TpCallChannel
*call
,
3782 GHashTable
*updates
,
3784 TpCallStateReason
*reason
,
3785 EmpathyCallWindow
*self
)
3787 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3788 GHashTableIter iter
;
3789 gpointer key
, value
;
3790 gboolean held
= FALSE
;
3792 g_hash_table_iter_init (&iter
, updates
);
3793 while (g_hash_table_iter_next (&iter
, &key
, &value
))
3795 if (GPOINTER_TO_INT (value
) & TP_CALL_MEMBER_FLAG_HELD
)
3797 /* This assumes this is a 1-1 call, otherwise one participant
3798 * putting the call on hold wouldn't mean the call is on hold
3806 priv
->call_state
= HELD
;
3807 else if (priv
->call_state
== HELD
)
3808 priv
->call_state
= CONNECTED
;
3812 call_handler_notify_call_cb (EmpathyCallHandler
*handler
,
3814 EmpathyCallWindow
*self
)
3816 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3817 TpCallChannel
*call
;
3819 g_object_get (priv
->handler
, "call-channel", &call
, NULL
);
3824 tp_g_signal_connect_object (call, "audio-stream-error",
3825 G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
3826 tp_g_signal_connect_object (call, "video-stream-error",
3827 G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
3830 tp_g_signal_connect_object (call
, "members-changed",
3831 G_CALLBACK (empathy_call_window_members_changed_cb
), self
, 0);
3833 g_object_unref (call
);
3837 empathy_call_window_connect_handler (EmpathyCallWindow
*self
)
3839 EmpathyCallWindowPriv
*priv
= GET_PRIV (self
);
3840 TpCallChannel
*call
;
3842 g_signal_connect (priv
->handler
, "state-changed",
3843 G_CALLBACK (empathy_call_window_state_changed_cb
), self
);
3844 g_signal_connect (priv
->handler
, "conference-added",
3845 G_CALLBACK (empathy_call_window_conference_added_cb
), self
);
3846 g_signal_connect (priv
->handler
, "conference-removed",
3847 G_CALLBACK (empathy_call_window_conference_removed_cb
), self
);
3848 g_signal_connect (priv
->handler
, "closed",
3849 G_CALLBACK (empathy_call_window_channel_closed_cb
), self
);
3850 g_signal_connect (priv
->handler
, "src-pad-added",
3851 G_CALLBACK (empathy_call_window_src_added_cb
), self
);
3852 g_signal_connect (priv
->handler
, "content-added",
3853 G_CALLBACK (empathy_call_window_content_added_cb
), self
);
3854 g_signal_connect (priv
->handler
, "content-removed",
3855 G_CALLBACK (empathy_call_window_content_removed_cb
), self
);
3857 /* We connect to ::call-channel unconditionally since we'll
3858 * get new channels if we hangup and redial or if we reuse the
3860 g_signal_connect (priv
->handler
, "notify::call-channel",
3861 G_CALLBACK (call_handler_notify_call_cb
), self
);
3863 g_signal_connect (priv
->handler
, "framerate-changed",
3864 G_CALLBACK (empathy_call_window_framerate_changed_cb
), self
);
3865 g_signal_connect (priv
->handler
, "resolution-changed",
3866 G_CALLBACK (empathy_call_window_resolution_changed_cb
), self
);
3868 g_object_get (priv
->handler
, "call-channel", &call
, NULL
);
3871 /* We won't get notify::call-channel for this channel, so
3872 * directly call the callback. */
3873 call_handler_notify_call_cb (priv
->handler
, NULL
, self
);
3874 g_object_unref (call
);
3879 empathy_call_window_realized_cb (GtkWidget
*widget
,
3880 EmpathyCallWindow
*self
)
3884 /* Make the hangup button twice as wide */
3885 width
= gtk_widget_get_allocated_width (self
->priv
->hangup_button
);
3886 gtk_widget_set_size_request (self
->priv
->hangup_button
, width
* 2, -1);
3888 empathy_call_window_connect_handler (self
);
3890 gst_element_set_state (self
->priv
->pipeline
, GST_STATE_PAUSED
);
3894 empathy_call_window_delete_cb (GtkWidget
*widget
, GdkEvent
*event
,
3895 EmpathyCallWindow
*window
)
3897 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
3899 if (priv
->pipeline
!= NULL
)
3901 if (priv
->bus_message_source_id
!= 0)
3903 g_source_remove (priv
->bus_message_source_id
);
3904 priv
->bus_message_source_id
= 0;
3907 gst_element_set_state (priv
->pipeline
, GST_STATE_NULL
);
3910 if (priv
->call_state
== CONNECTING
)
3911 empathy_sound_manager_stop (priv
->sound_mgr
, EMPATHY_SOUND_PHONE_OUTGOING
);
3917 show_controls (EmpathyCallWindow
*window
, gboolean set_fullscreen
)
3920 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
3922 menu
= gtk_ui_manager_get_widget (priv
->ui_manager
,
3927 gtk_widget_hide (priv
->dtmf_panel
);
3928 gtk_widget_hide (menu
);
3929 gtk_widget_hide (priv
->toolbar
);
3933 if (priv
->dialpad_was_visible_before_fs
)
3934 gtk_widget_show (priv
->dtmf_panel
);
3936 gtk_widget_show (menu
);
3937 gtk_widget_show (priv
->toolbar
);
3939 gtk_window_resize (GTK_WINDOW (window
), priv
->original_width_before_fs
,
3940 priv
->original_height_before_fs
);
3945 show_borders (EmpathyCallWindow
*window
, gboolean set_fullscreen
)
3947 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
3949 gtk_box_set_spacing (GTK_BOX (priv
->content_hbox
),
3950 set_fullscreen
? 0 : CONTENT_HBOX_SPACING
);
3952 if (priv
->video_output
!= NULL
)
3955 gtk_box_set_child_packing (GTK_BOX (priv
->content_hbox
),
3956 priv
->video_output
, TRUE
, TRUE
,
3957 set_fullscreen
? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING
,
3964 empathy_call_window_state_event_cb (GtkWidget
*widget
,
3965 GdkEventWindowState
*event
, EmpathyCallWindow
*window
)
3967 if (event
->changed_mask
& GDK_WINDOW_STATE_FULLSCREEN
)
3969 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
3970 gboolean set_fullscreen
= event
->new_window_state
&
3971 GDK_WINDOW_STATE_FULLSCREEN
;
3975 gboolean dialpad_was_visible
;
3976 GtkAllocation allocation
;
3977 gint original_width
, original_height
;
3979 gtk_widget_get_allocation (GTK_WIDGET (window
), &allocation
);
3980 original_width
= allocation
.width
;
3981 original_height
= allocation
.height
;
3983 g_object_get (priv
->dtmf_panel
,
3984 "visible", &dialpad_was_visible
,
3987 priv
->dialpad_was_visible_before_fs
= dialpad_was_visible
;
3988 priv
->original_width_before_fs
= original_width
;
3989 priv
->original_height_before_fs
= original_height
;
3991 if (priv
->video_output_motion_handler_id
== 0 &&
3992 priv
->video_output
!= NULL
)
3994 priv
->video_output_motion_handler_id
= g_signal_connect (
3995 G_OBJECT (priv
->video_container
), "motion-notify-event",
3996 G_CALLBACK (empathy_call_window_video_output_motion_notify
),
4002 disconnect_video_output_motion_handler (window
);
4005 empathy_call_window_fullscreen_set_fullscreen (priv
->fullscreen
,
4007 show_controls (window
, set_fullscreen
);
4008 show_borders (window
, set_fullscreen
);
4009 gtk_action_set_stock_id (priv
->menu_fullscreen
,
4010 (set_fullscreen
? "view-restore" : "view-fullscreen"));
4011 priv
->is_fullscreen
= set_fullscreen
;
4018 empathy_call_window_show_dialpad (EmpathyCallWindow
*window
,
4021 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
4022 int w
, h
, dialpad_width
;
4023 GtkAllocation allocation
;
4025 gtk_widget_get_allocation (GTK_WIDGET (window
), &allocation
);
4026 w
= allocation
.width
;
4027 h
= allocation
.height
;
4029 gtk_widget_get_preferred_width (priv
->dtmf_panel
, &dialpad_width
, NULL
);
4033 gtk_widget_show (priv
->dtmf_panel
);
4039 gtk_widget_hide (priv
->dtmf_panel
);
4043 gtk_window_resize (GTK_WINDOW (window
), w
, h
);
4047 empathy_call_window_set_send_video (EmpathyCallWindow
*window
,
4050 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
4051 TpCallChannel
*call
;
4053 priv
->sending_video
= (state
== CAMERA_STATE_ON
);
4055 if (state
== CAMERA_STATE_ON
)
4057 /* When we start sending video, we want to show the video preview by
4059 display_video_preview (window
, TRUE
);
4063 display_video_preview (window
, FALSE
);
4066 if (priv
->call_state
!= CONNECTED
)
4069 g_object_get (priv
->handler
, "call-channel", &call
, NULL
);
4070 DEBUG ("%s sending video", priv
->sending_video
? "start": "stop");
4071 empathy_call_channel_send_video (call
, priv
->sending_video
);
4072 g_object_unref (call
);
4076 empathy_call_window_hangup_cb (gpointer object
,
4077 EmpathyCallWindow
*self
)
4079 /* stopping the call will put it the ENDED state and
4080 * from state_changed_cb we'll reconfigure the window
4082 empathy_call_handler_stop_call (self
->priv
->handler
);
4086 empathy_call_window_restart_call (EmpathyCallWindow
*window
)
4088 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
4090 /* Remove error info bars */
4091 gtk_container_forall (GTK_CONTAINER (priv
->errors_vbox
),
4092 (GtkCallback
) gtk_widget_destroy
, NULL
);
4094 create_video_output_widget (window
);
4095 priv
->outgoing
= TRUE
;
4096 empathy_call_window_set_state_connecting (window
);
4098 if (priv
->pipeline_playing
)
4099 start_call (window
);
4101 /* call will be started when the pipeline is ready */
4102 priv
->start_call_when_playing
= TRUE
;
4104 empathy_call_window_setup_avatars (window
, priv
->handler
);
4106 empathy_call_window_show_hangup_button (window
, TRUE
);
4110 empathy_call_window_dialpad_cb (GtkToggleToolButton
*button
,
4111 EmpathyCallWindow
*window
)
4115 active
= gtk_toggle_tool_button_get_active (button
);
4117 empathy_call_window_show_dialpad (window
, active
);
4121 empathy_call_window_fullscreen_cb (gpointer object
,
4122 EmpathyCallWindow
*window
)
4124 empathy_call_window_fullscreen_toggle (window
);
4128 empathy_call_window_fullscreen_toggle (EmpathyCallWindow
*window
)
4130 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
4132 if (priv
->is_fullscreen
)
4133 gtk_window_unfullscreen (GTK_WINDOW (window
));
4135 gtk_window_fullscreen (GTK_WINDOW (window
));
4139 empathy_call_window_video_button_press_cb (GtkWidget
*video_preview
,
4140 GdkEventButton
*event
, EmpathyCallWindow
*window
)
4142 if (event
->button
== 3 && event
->type
== GDK_BUTTON_PRESS
)
4144 empathy_call_window_video_menu_popup (window
, event
->button
);
4152 empathy_call_window_key_press_cb (GtkWidget
*video_output
,
4153 GdkEventKey
*event
, EmpathyCallWindow
*window
)
4155 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
4158 if (priv
->is_fullscreen
&& event
->keyval
== GDK_KEY_Escape
)
4160 /* Since we are in fullscreen mode, toggling will bring us back to
4162 empathy_call_window_fullscreen_toggle (window
);
4166 key
= gdk_keyval_to_unicode (event
->keyval
);
4187 gtk_toggle_tool_button_set_active (
4188 GTK_TOGGLE_TOOL_BUTTON (priv
->dialpad_button
), TRUE
);
4190 empathy_dialpad_widget_press_key (
4191 EMPATHY_DIALPAD_WIDGET (priv
->dtmf_panel
), key
);
4197 empathy_call_window_video_output_motion_notify (GtkWidget
*widget
,
4198 GdkEventMotion
*event
, EmpathyCallWindow
*window
)
4200 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
4202 if (priv
->is_fullscreen
)
4204 empathy_call_window_fullscreen_show_popup (priv
->fullscreen
);
4206 /* Show the bottom toolbar */
4207 empathy_call_window_motion_notify_cb (NULL
, NULL
, window
);
4214 empathy_call_window_video_menu_popup (EmpathyCallWindow
*window
,
4218 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
4220 menu
= gtk_ui_manager_get_widget (priv
->ui_manager
,
4222 gtk_menu_popup (GTK_MENU (menu
), NULL
, NULL
, NULL
, NULL
,
4223 button
, gtk_get_current_event_time ());
4224 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu
), FALSE
);
4228 empathy_call_window_status_message (EmpathyCallWindow
*self
,
4231 gtk_label_set_label (GTK_LABEL (self
->priv
->status_label
), message
);
4235 empathy_call_window_get_ui_manager (EmpathyCallWindow
*window
)
4237 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
4239 return priv
->ui_manager
;
4242 EmpathyGstAudioSrc
*
4243 empathy_call_window_get_audio_src (EmpathyCallWindow
*window
)
4245 EmpathyCallWindowPriv
*priv
= GET_PRIV (window
);
4247 return (EmpathyGstAudioSrc
*) priv
->audio_input
;
4250 EmpathyGstVideoSrc
*
4251 empathy_call_window_get_video_src (EmpathyCallWindow
*self
)
4253 return EMPATHY_GST_VIDEO_SRC (self
->priv
->video_input
);
4257 empathy_call_window_change_webcam (EmpathyCallWindow
*self
,
4258 const gchar
*device
)
4260 EmpathyGstVideoSrc
*video
;
4263 /* Restart the camera only if it's already running */
4264 running
= (self
->priv
->video_preview
!= NULL
);
4265 video
= empathy_call_window_get_video_src (self
);
4268 empathy_call_window_play_camera (self
, FALSE
);
4270 empathy_video_src_change_device (video
, device
);
4273 empathy_call_window_play_camera (self
, TRUE
);