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
27 #include <gdk/gdkkeysyms.h>
30 #include <glib/gi18n.h>
32 #include <clutter/clutter.h>
33 #include <clutter-gtk/clutter-gtk.h>
34 #include <clutter-gst/clutter-gst.h>
36 #include <telepathy-glib/util.h>
37 #include <telepathy-farstream/telepathy-farstream.h>
38 #include <telepathy-glib/util.h>
40 #include <gst/farsight/fs-element-added-notifier.h>
41 #include <gst/farsight/fs-utils.h>
43 #include <libempathy/empathy-camera-monitor.h>
44 #include <libempathy/empathy-tp-contact-factory.h>
45 #include <libempathy/empathy-utils.h>
46 #include <libempathy-gtk/empathy-avatar-image.h>
47 #include <libempathy-gtk/empathy-ui-utils.h>
48 #include <libempathy-gtk/empathy-sound-manager.h>
49 #include <libempathy-gtk/empathy-geometry.h>
50 #include <libempathy-gtk/empathy-images.h>
52 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
53 #include <libempathy/empathy-debug.h>
55 #include "empathy-call-window.h"
56 #include "empathy-call-window-fullscreen.h"
57 #include "empathy-call-factory.h"
58 #include "empathy-video-widget.h"
59 #include "empathy-audio-src.h"
60 #include "empathy-audio-sink.h"
61 #include "empathy-video-src.h"
62 #include "ev-sidebar.h"
64 #define CONTENT_HBOX_BORDER_WIDTH 6
65 #define CONTENT_HBOX_SPACING 3
66 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
68 #define SELF_VIDEO_SECTION_WIDTH 120
69 #define SELF_VIDEO_SECTION_HEIGTH 90
71 /* The avatar's default width and height are set to the same value because we
72 want a square icon. */
73 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
74 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
75 EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
77 #define SMALL_TOOLBAR_SIZE 36
79 /* If an video input error occurs, the error message will start with "v4l" */
80 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
82 /* The time interval in milliseconds between 2 outgoing rings */
83 #define MS_BETWEEN_RING 500
85 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
88 PROP_CALL_HANDLER = 1,
100 CAMERA_STATE_OFF = 0,
104 struct _EmpathyCallWindowPriv
106 gboolean dispose_has_run;
107 EmpathyCallHandler *handler;
109 EmpathyContact *contact;
111 EmpathyCameraMonitor *camera_monitor;
116 GtkUIManager *ui_manager;
117 GtkWidget *errors_vbox;
118 /* widget displays the video received from the remote user. This widget is
119 * alive only during call. */
120 ClutterActor *video_output;
121 ClutterActor *video_preview;
122 ClutterActor *preview_hidden_button;
123 GtkWidget *video_container;
124 GtkWidget *remote_user_avatar_widget;
125 GtkWidget *remote_user_avatar_toolbar;
126 GtkWidget *remote_user_name_toolbar;
128 GtkWidget *status_label;
129 GtkWidget *hangup_button;
130 GtkWidget *audio_call_button;
131 GtkWidget *video_call_button;
132 GtkWidget *mic_button;
133 GtkWidget *camera_button;
134 GtkWidget *dialpad_button;
136 GtkWidget *bottom_toolbar;
138 GtkAction *menu_sidebar;
139 GtkAction *menu_fullscreen;
141 /* The box that contains self and remote avatar and video
142 input/output. When we redial, we destroy and re-create the box */
143 ClutterActor *video_box;
144 ClutterLayoutManager *video_layout;
146 /* We keep a reference on the hbox which contains the main content so we can
147 easilly repack everything when toggling fullscreen */
148 GtkWidget *content_hbox;
150 gulong video_output_motion_handler_id;
151 guint bus_message_source_id;
154 GtkWidget *volume_scale;
155 GtkWidget *volume_progress_bar;
156 GtkAdjustment *audio_input_adj;
158 GtkWidget *dtmf_panel;
161 GtkWidget *details_vbox;
162 GtkWidget *vcodec_encoding_label;
163 GtkWidget *acodec_encoding_label;
164 GtkWidget *vcodec_decoding_label;
165 GtkWidget *acodec_decoding_label;
167 GtkWidget *audio_remote_candidate_label;
168 GtkWidget *audio_local_candidate_label;
169 GtkWidget *video_remote_candidate_label;
170 GtkWidget *video_local_candidate_label;
171 GtkWidget *video_remote_candidate_info_img;
172 GtkWidget *video_local_candidate_info_img;
173 GtkWidget *audio_remote_candidate_info_img;
174 GtkWidget *audio_local_candidate_info_img;
176 GstElement *video_input;
177 GstElement *video_preview_sink;
178 GstElement *video_output_sink;
179 GstElement *audio_input;
180 GstElement *audio_output;
181 GstElement *pipeline;
182 GstElement *video_tee;
191 GtkWidget *video_contrast;
192 GtkWidget *video_brightness;
193 GtkWidget *video_gamma;
196 gboolean call_started;
197 gboolean sending_video;
198 CameraState camera_state;
200 EmpathyCallWindowFullscreen *fullscreen;
201 gboolean is_fullscreen;
206 /* Those fields represent the state of the window before it actually was in
208 gboolean sidebar_was_visible_before_fs;
209 gint original_width_before_fs;
210 gint original_height_before_fs;
212 gint x, y, w, h, sidebar_width;
215 /* TRUE if the call should be started when the pipeline is playing */
216 gboolean start_call_when_playing;
217 /* TRUE if we requested to set the pipeline in the playing state */
218 gboolean pipeline_playing;
220 EmpathySoundManager *sound_mgr;
223 #define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv)
225 static void empathy_call_window_realized_cb (GtkWidget *widget,
226 EmpathyCallWindow *window);
228 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
229 GdkEvent *event, EmpathyCallWindow *window);
231 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
232 GdkEventWindowState *event, EmpathyCallWindow *window);
234 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
237 static void empathy_call_window_mic_toggled_cb (
238 GtkToggleToolButton *toggle, EmpathyCallWindow *window);
240 static void empathy_call_window_sidebar_cb (GtkToggleAction *menu,
241 EmpathyCallWindow *self);
243 static void empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
244 EmpathyCallWindow *window);
246 static void empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
247 EmpathyCallWindow *window);
249 static void empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
251 EmpathyCallWindow *window);
253 static void empathy_call_window_hangup_cb (gpointer object,
254 EmpathyCallWindow *window);
256 static void empathy_call_window_fullscreen_cb (gpointer object,
257 EmpathyCallWindow *window);
259 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
261 static gboolean empathy_call_window_video_button_press_cb (
262 GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
264 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
265 GdkEventKey *event, EmpathyCallWindow *window);
267 static gboolean empathy_call_window_video_output_motion_notify (
268 GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
270 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
273 static void empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
274 EmpathyCallWindow *window);
276 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
278 static void empathy_call_window_status_message (EmpathyCallWindow *window,
281 static gboolean empathy_call_window_bus_message (GstBus *bus,
282 GstMessage *message, gpointer user_data);
285 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
286 gdouble value, EmpathyCallWindow *window);
289 empathy_call_window_show_hangup_button (EmpathyCallWindow *self,
292 gtk_widget_set_visible (self->priv->hangup_button, show);
293 gtk_widget_set_visible (self->priv->audio_call_button, !show);
294 gtk_widget_set_visible (self->priv->video_call_button, !show);
298 empathy_call_window_audio_call_cb (GtkToggleToolButton *button,
299 EmpathyCallWindow *self)
301 g_object_set (self->priv->handler, "initial-video", FALSE, NULL);
302 empathy_call_window_restart_call (self);
306 empathy_call_window_video_call_cb (GtkToggleToolButton *button,
307 EmpathyCallWindow *self)
309 empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
310 g_object_set (self->priv->handler, "initial-video", TRUE, NULL);
311 empathy_call_window_restart_call (self);
315 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
317 EmpathyCallWindowPriv *priv = GET_PRIV (window);
318 TpyCallChannel *call;
322 g_object_get (priv->handler, "call-channel", &call, NULL);
324 button_quark = g_quark_from_static_string (EMPATHY_DTMF_BUTTON_ID);
325 event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
328 tpy_call_channel_dtmf_start_tone (call, event);
330 g_object_unref (call);
334 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
336 EmpathyCallWindowPriv *priv = GET_PRIV (window);
337 TpyCallChannel *call;
339 g_object_get (priv->handler, "call-channel", &call, NULL);
341 tpy_call_channel_dtmf_stop_tone (call);
343 g_object_unref (call);
347 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
348 gchar *label_text, GtkWidget *bin)
350 GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
351 GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
352 GtkWidget *label = gtk_label_new (label_text);
354 gtk_widget_set_sensitive (scale, FALSE);
356 gtk_container_add (GTK_CONTAINER (bin), vbox);
358 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
359 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
360 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
366 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
367 EmpathyCallWindow *self)
370 EmpathyCallWindowPriv *priv = GET_PRIV (self);
372 empathy_video_src_set_channel (priv->video_input,
373 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
377 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
378 EmpathyCallWindow *self)
381 EmpathyCallWindowPriv *priv = GET_PRIV (self);
383 empathy_video_src_set_channel (priv->video_input,
384 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
388 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
389 EmpathyCallWindow *self)
392 EmpathyCallWindowPriv *priv = GET_PRIV (self);
394 empathy_video_src_set_channel (priv->video_input,
395 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
400 empathy_call_window_create_video_input (EmpathyCallWindow *self)
402 EmpathyCallWindowPriv *priv = GET_PRIV (self);
405 hbox = gtk_hbox_new (TRUE, 3);
407 priv->video_contrast = empathy_call_window_create_video_input_add_slider (
408 self, _("Contrast"), hbox);
410 priv->video_brightness = empathy_call_window_create_video_input_add_slider (
411 self, _("Brightness"), hbox);
413 priv->video_gamma = empathy_call_window_create_video_input_add_slider (
414 self, _("Gamma"), hbox);
420 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
422 EmpathyCallWindowPriv *priv = GET_PRIV (self);
426 supported = empathy_video_src_get_supported_channels (priv->video_input);
428 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
430 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
432 gtk_adjustment_set_value (adj,
433 empathy_video_src_get_channel (priv->video_input,
434 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
436 g_signal_connect (G_OBJECT (adj), "value-changed",
437 G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
439 gtk_widget_set_sensitive (priv->video_contrast, TRUE);
442 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
444 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
446 gtk_adjustment_set_value (adj,
447 empathy_video_src_get_channel (priv->video_input,
448 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
450 g_signal_connect (G_OBJECT (adj), "value-changed",
451 G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
452 gtk_widget_set_sensitive (priv->video_brightness, TRUE);
455 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
457 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
459 gtk_adjustment_set_value (adj,
460 empathy_video_src_get_channel (priv->video_input,
461 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
463 g_signal_connect (G_OBJECT (adj), "value-changed",
464 G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
465 gtk_widget_set_sensitive (priv->video_gamma, TRUE);
470 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
471 EmpathyCallWindow *self)
473 EmpathyCallWindowPriv *priv = GET_PRIV (self);
476 volume = gtk_adjustment_get_value (adj)/100.0;
478 /* Don't store the volume because of muting */
479 if (volume > 0 || gtk_toggle_tool_button_get_active (
480 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
481 priv->volume = volume;
483 /* Ensure that the toggle button is active if the volume is > 0 and inactive
484 * if it's smaller than 0 */
485 if ((volume > 0) != gtk_toggle_tool_button_get_active (
486 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
487 gtk_toggle_tool_button_set_active (
488 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
490 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
495 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
496 gdouble level, EmpathyCallWindow *window)
499 EmpathyCallWindowPriv *priv = GET_PRIV (window);
501 value = CLAMP (pow (10, level / 20), 0.0, 1.0);
502 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
507 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
509 EmpathyCallWindowPriv *priv = GET_PRIV (self);
510 GtkWidget *hbox, *vbox, *label;
512 hbox = gtk_hbox_new (TRUE, 3);
514 vbox = gtk_vbox_new (FALSE, 3);
515 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
517 priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
518 gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
519 label = gtk_label_new (_("Volume"));
521 priv->audio_input_adj = gtk_range_get_adjustment (
522 GTK_RANGE (priv->volume_scale));
523 priv->volume = empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
524 (priv->audio_input));
525 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
527 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
528 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
530 gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
531 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
533 priv->volume_progress_bar = gtk_progress_bar_new ();
535 gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar),
536 GTK_ORIENTATION_VERTICAL);
538 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
541 gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
548 empathy_call_window_show_video_output (EmpathyCallWindow *self,
551 if (self->priv->video_output != NULL)
552 g_object_set (self->priv->video_output, "visible", show, NULL);
554 gtk_widget_set_visible (self->priv->remote_user_avatar_widget, !show);
558 create_video_output_widget (EmpathyCallWindow *self)
560 EmpathyCallWindowPriv *priv = GET_PRIV (self);
562 g_assert (priv->video_output == NULL);
563 g_assert (priv->pipeline != NULL);
565 priv->video_output = clutter_texture_new ();
567 clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (priv->video_output),
570 priv->video_output_sink = clutter_gst_video_sink_new (
571 CLUTTER_TEXTURE (priv->video_output));
573 clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
576 gtk_widget_add_events (priv->video_container,
577 GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
578 g_signal_connect (G_OBJECT (priv->video_container), "button-press-event",
579 G_CALLBACK (empathy_call_window_video_button_press_cb), self);
583 create_video_input (EmpathyCallWindow *self)
585 EmpathyCallWindowPriv *priv = GET_PRIV (self);
587 g_assert (priv->video_input == NULL);
588 priv->video_input = empathy_video_src_new ();
589 gst_object_ref (priv->video_input);
590 gst_object_sink (priv->video_input);
594 create_audio_input (EmpathyCallWindow *self)
596 EmpathyCallWindowPriv *priv = GET_PRIV (self);
598 g_assert (priv->audio_input == NULL);
599 priv->audio_input = empathy_audio_src_new ();
600 gst_object_ref (priv->audio_input);
601 gst_object_sink (priv->audio_input);
603 tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
604 G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
609 add_video_preview_to_pipeline (EmpathyCallWindow *self)
611 EmpathyCallWindowPriv *priv = GET_PRIV (self);
614 g_assert (priv->video_preview != NULL);
615 g_assert (priv->pipeline != NULL);
616 g_assert (priv->video_input != NULL);
617 g_assert (priv->video_tee != NULL);
619 preview = priv->video_preview_sink;
621 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
623 g_warning ("Could not add video input to pipeline");
627 if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
629 g_warning ("Could not add video preview to pipeline");
633 if (!gst_element_link (priv->video_input, priv->video_tee))
635 g_warning ("Could not link video input to video tee");
639 if (!gst_element_link (priv->video_tee, preview))
641 g_warning ("Could not link video tee to video preview");
647 empathy_call_window_disable_camera_cb (GtkAction *action,
648 EmpathyCallWindow *self)
650 clutter_actor_destroy (self->priv->preview_hidden_button);
652 gtk_toggle_tool_button_set_active (
653 GTK_TOGGLE_TOOL_BUTTON (self->priv->camera_button), FALSE);
657 empathy_call_window_minimise_camera_cb (GtkAction *action,
658 EmpathyCallWindow *self)
660 clutter_actor_hide (self->priv->video_preview);
661 clutter_actor_show (self->priv->preview_hidden_button);
665 empathy_call_window_maximise_camera_cb (GtkAction *action,
666 EmpathyCallWindow *self)
668 clutter_actor_show (self->priv->video_preview);
669 clutter_actor_hide (self->priv->preview_hidden_button);
673 empathy_call_window_preview_button_clicked_cb (GtkButton *button,
674 EmpathyCallWindow *self)
678 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
680 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
681 0, gtk_get_current_event_time ());
682 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
686 empathy_call_window_preview_hidden_button_clicked_cb (GtkButton *button,
687 EmpathyCallWindow *self)
691 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
692 "/preview-hidden-menu");
693 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
694 0, gtk_get_current_event_time ());
695 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
699 create_video_preview (EmpathyCallWindow *self)
701 EmpathyCallWindowPriv *priv = GET_PRIV (self);
702 ClutterLayoutManager *layout, *layout_center;
703 ClutterActor *preview;
708 g_assert (priv->video_preview == NULL);
710 preview = clutter_texture_new ();
711 clutter_actor_set_size (preview,
712 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
713 priv->video_preview_sink = clutter_gst_video_sink_new (
714 CLUTTER_TEXTURE (preview));
716 /* Flip the video preview */
717 clutter_actor_set_rotation (preview,
720 SELF_VIDEO_SECTION_WIDTH * 0.5,
724 /* Add a little offset to the video preview */
725 layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_END,
726 CLUTTER_BIN_ALIGNMENT_START);
727 priv->video_preview = clutter_box_new (layout);
728 clutter_actor_set_size (priv->video_preview,
729 SELF_VIDEO_SECTION_WIDTH + 10, SELF_VIDEO_SECTION_HEIGTH + 10);
731 layout_center = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
732 CLUTTER_BIN_ALIGNMENT_CENTER);
733 box = clutter_box_new (layout_center);
734 clutter_actor_set_size (box,
735 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
737 clutter_container_add_actor (CLUTTER_CONTAINER (box), preview);
738 clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview), box);
740 g_object_set (priv->video_preview_sink,
745 /* Translators: this is an "Info" label. It should be as short
747 button = gtk_button_new_with_label (_("i"));
748 b = gtk_clutter_actor_new_with_contents (button);
750 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout_center),
752 CLUTTER_BIN_ALIGNMENT_END,
753 CLUTTER_BIN_ALIGNMENT_END);
755 g_signal_connect (button, "clicked",
756 G_CALLBACK (empathy_call_window_preview_button_clicked_cb),
759 /* Translators: this is an "Info" label. It should be as short
761 button = gtk_button_new_with_label (_("i"));
762 priv->preview_hidden_button =
763 gtk_clutter_actor_new_with_contents (button);
765 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout),
766 priv->preview_hidden_button,
767 CLUTTER_BIN_ALIGNMENT_START,
768 CLUTTER_BIN_ALIGNMENT_END);
770 clutter_actor_hide (priv->preview_hidden_button);
772 g_signal_connect (button, "clicked",
773 G_CALLBACK (empathy_call_window_preview_hidden_button_clicked_cb),
776 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout),
778 CLUTTER_BIN_ALIGNMENT_START,
779 CLUTTER_BIN_ALIGNMENT_END);
783 play_camera (EmpathyCallWindow *window,
786 EmpathyCallWindowPriv *priv = GET_PRIV (window);
790 if (priv->video_preview == NULL)
792 create_video_preview (window);
793 add_video_preview_to_pipeline (window);
797 state = GST_STATE_PLAYING;
799 state = GST_STATE_NULL;
801 preview = priv->video_preview_sink;
803 gst_element_set_state (preview, state);
804 gst_element_set_state (priv->video_input, state);
805 gst_element_set_state (priv->video_tee, state);
809 display_video_preview (EmpathyCallWindow *self,
812 EmpathyCallWindowPriv *priv = GET_PRIV (self);
816 /* Display the video preview */
817 DEBUG ("Show video preview");
819 play_camera (self, TRUE);
820 clutter_actor_show (priv->video_preview);
824 /* Hide the video preview */
825 DEBUG ("Hide video preview");
827 if (priv->video_preview != NULL)
829 clutter_actor_hide (priv->video_preview);
830 play_camera (self, FALSE);
836 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
838 EmpathyCallWindowPriv *priv = GET_PRIV (window);
840 empathy_call_window_status_message (window, _("Connecting…"));
841 priv->call_state = CONNECTING;
844 empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
845 EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
849 disable_camera (EmpathyCallWindow *self)
851 EmpathyCallWindowPriv *priv = GET_PRIV (self);
853 if (priv->camera_state == CAMERA_STATE_OFF)
856 DEBUG ("Disable camera");
858 display_video_preview (self, FALSE);
860 if (priv->camera_state == CAMERA_STATE_ON)
861 empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
863 priv->camera_state = CAMERA_STATE_OFF;
867 enable_camera (EmpathyCallWindow *self)
869 EmpathyCallWindowPriv *priv = GET_PRIV (self);
871 if (priv->camera_state == CAMERA_STATE_ON)
874 if (priv->video_input == NULL)
876 DEBUG ("Can't enable camera, no input");
880 DEBUG ("Enable camera");
882 empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
884 priv->camera_state = CAMERA_STATE_ON;
888 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
889 EmpathyCallWindow *self)
891 if (gtk_toggle_tool_button_get_active (toggle))
892 enable_camera (self);
894 disable_camera (self);
898 create_pipeline (EmpathyCallWindow *self)
900 EmpathyCallWindowPriv *priv = GET_PRIV (self);
903 g_assert (priv->pipeline == NULL);
905 priv->pipeline = gst_pipeline_new (NULL);
906 priv->pipeline_playing = FALSE;
908 priv->video_tee = gst_element_factory_make ("tee", NULL);
909 gst_object_ref (priv->video_tee);
910 gst_object_sink (priv->video_tee);
912 gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee);
914 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
915 priv->bus_message_source_id = gst_bus_add_watch (bus,
916 empathy_call_window_bus_message, self);
918 g_object_unref (bus);
922 empathy_call_window_configure_event_cb (GtkWidget *widget,
924 EmpathyCallWindow *self)
926 GdkWindow *gdk_window;
927 GdkWindowState window_state;
929 gtk_window_get_position (GTK_WINDOW (self), &self->priv->x, &self->priv->y);
930 gtk_window_get_size (GTK_WINDOW (self), &self->priv->w, &self->priv->h);
932 gtk_widget_get_preferred_width (self->priv->sidebar,
933 &self->priv->sidebar_width, NULL);
935 gdk_window = gtk_widget_get_window (widget);
936 window_state = gdk_window_get_state (gdk_window);
937 self->priv->maximized = (window_state & GDK_WINDOW_STATE_MAXIMIZED);
943 empathy_call_window_destroyed_cb (GtkWidget *object,
944 EmpathyCallWindow *self)
946 if (gtk_widget_get_visible (self->priv->sidebar))
948 /* Save the geometry as if the sidebar was hidden. */
949 empathy_geometry_save_values (GTK_WINDOW (self),
950 self->priv->x, self->priv->y,
951 self->priv->w - self->priv->sidebar_width, self->priv->h,
952 self->priv->maximized);
957 empathy_call_window_init (EmpathyCallWindow *self)
959 EmpathyCallWindowPriv *priv;
965 ClutterConstraint *size_constraint;
966 ClutterActor *remote_avatar;
967 GtkStyleContext *context;
971 priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
972 EMPATHY_TYPE_CALL_WINDOW, EmpathyCallWindowPriv);
974 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
975 gui = empathy_builder_get_file (filename,
976 "call_window_vbox", &top_vbox,
977 "errors_vbox", &priv->errors_vbox,
979 "remote_user_name_toolbar", &priv->remote_user_name_toolbar,
980 "remote_user_avatar_toolbar", &priv->remote_user_avatar_toolbar,
981 "status_label", &priv->status_label,
982 "audiocall", &priv->audio_call_button,
983 "videocall", &priv->video_call_button,
984 "microphone", &priv->mic_button,
985 "camera", &priv->camera_button,
986 "hangup", &priv->hangup_button,
987 "dialpad", &priv->dialpad_button,
988 "toolbar", &priv->toolbar,
989 "bottom_toolbar", &priv->bottom_toolbar,
990 "menusidebar", &priv->menu_sidebar,
991 "ui_manager", &priv->ui_manager,
992 "menufullscreen", &priv->menu_fullscreen,
993 "details_vbox", &priv->details_vbox,
994 "vcodec_encoding_label", &priv->vcodec_encoding_label,
995 "acodec_encoding_label", &priv->acodec_encoding_label,
996 "acodec_decoding_label", &priv->acodec_decoding_label,
997 "vcodec_decoding_label", &priv->vcodec_decoding_label,
998 "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
999 "audio_local_candidate_label", &priv->audio_local_candidate_label,
1000 "video_remote_candidate_label", &priv->video_remote_candidate_label,
1001 "video_local_candidate_label", &priv->video_local_candidate_label,
1002 "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
1003 "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
1004 "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
1005 "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
1009 empathy_builder_connect (gui, self,
1010 "menuhangup", "activate", empathy_call_window_hangup_cb,
1011 "hangup", "clicked", empathy_call_window_hangup_cb,
1012 "audiocall", "clicked", empathy_call_window_audio_call_cb,
1013 "videocall", "clicked", empathy_call_window_video_call_cb,
1014 "menusidebar", "toggled", empathy_call_window_sidebar_cb,
1015 "volume", "value-changed", empathy_call_window_volume_changed_cb,
1016 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
1017 "camera", "toggled", empathy_call_window_camera_toggled_cb,
1018 "dialpad", "toggled", empathy_call_window_dialpad_cb,
1019 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
1020 "menupreviewdisable", "activate", empathy_call_window_disable_camera_cb,
1021 "menupreviewminimise", "activate", empathy_call_window_minimise_camera_cb,
1022 "menupreviewmaximise", "activate", empathy_call_window_maximise_camera_cb,
1025 gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1027 priv->camera_monitor = empathy_camera_monitor_dup_singleton ();
1029 g_object_bind_property (priv->camera_monitor, "available",
1030 priv->camera_button, "sensitive",
1031 G_BINDING_SYNC_CREATE);
1033 priv->lock = g_mutex_new ();
1035 gtk_container_add (GTK_CONTAINER (self), top_vbox);
1037 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
1038 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1039 CONTENT_HBOX_BORDER_WIDTH);
1040 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
1042 /* avatar/video box */
1043 priv->video_layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
1044 CLUTTER_BIN_ALIGNMENT_CENTER);
1046 priv->video_box = clutter_box_new (priv->video_layout);
1048 priv->video_container = gtk_clutter_embed_new ();
1050 /* Set the background color to that of the rest of the window */
1051 context = gtk_widget_get_style_context (priv->content_hbox);
1052 gtk_style_context_get_background_color (context,
1053 GTK_STATE_FLAG_NORMAL, &rgba);
1054 bg.red = CLAMP (rgba.red * 255.0, 0, 255);
1055 bg.green = CLAMP (rgba.green * 255.0, 0, 255);
1056 bg.blue = CLAMP (rgba.blue * 255.0, 0, 255);
1057 bg.alpha = CLAMP (rgba.alpha * 255.0, 0, 255);
1058 clutter_stage_set_color (
1059 CLUTTER_STAGE (gtk_clutter_embed_get_stage (
1060 GTK_CLUTTER_EMBED (priv->video_container))),
1063 clutter_container_add (
1064 CLUTTER_CONTAINER (gtk_clutter_embed_get_stage (
1065 GTK_CLUTTER_EMBED (priv->video_container))),
1069 size_constraint = clutter_bind_constraint_new (
1070 gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)),
1071 CLUTTER_BIND_SIZE, 0);
1072 clutter_actor_add_constraint (priv->video_box, size_constraint);
1074 priv->remote_user_avatar_widget = gtk_image_new ();
1075 remote_avatar = gtk_clutter_actor_new_with_contents (
1076 priv->remote_user_avatar_widget);
1078 clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
1081 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1082 priv->video_container, TRUE, TRUE,
1083 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1085 create_pipeline (self);
1086 create_video_output_widget (self);
1087 create_audio_input (self);
1088 create_video_input (self);
1090 /* The call will be started as soon the pipeline is playing */
1091 priv->start_call_when_playing = TRUE;
1093 priv->sidebar = ev_sidebar_new ();
1094 g_signal_connect (G_OBJECT (priv->sidebar),
1095 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
1096 g_signal_connect (G_OBJECT (priv->sidebar),
1097 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
1098 g_signal_connect (G_OBJECT (priv->sidebar), "changed",
1099 G_CALLBACK (empathy_call_window_sidebar_changed_cb), self);
1100 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1102 page = empathy_call_window_create_audio_input (self);
1103 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input",
1104 _("Audio input"), page);
1106 page = empathy_call_window_create_video_input (self);
1107 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input",
1108 _("Video input"), page);
1110 priv->dtmf_panel = empathy_create_dtmf_dialpad (G_OBJECT (self),
1111 G_CALLBACK (dtmf_button_pressed_cb),
1112 G_CALLBACK (dtmf_button_released_cb));
1113 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
1114 _("Dialpad"), priv->dtmf_panel);
1116 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1118 /* Put the details vbox in a scroll window as it can require a lot of
1119 * horizontal space. */
1120 scroll = gtk_scrolled_window_new (NULL, NULL);
1121 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll),
1122 priv->details_vbox);
1124 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details", _("Details"),
1127 gtk_widget_show_all (top_vbox);
1129 gtk_widget_hide (priv->sidebar);
1131 priv->fullscreen = empathy_call_window_fullscreen_new (self);
1133 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1134 priv->video_container);
1136 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1137 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1139 g_signal_connect (G_OBJECT (self), "realize",
1140 G_CALLBACK (empathy_call_window_realized_cb), self);
1142 g_signal_connect (G_OBJECT (self), "delete-event",
1143 G_CALLBACK (empathy_call_window_delete_cb), self);
1145 g_signal_connect (G_OBJECT (self), "window-state-event",
1146 G_CALLBACK (empathy_call_window_state_event_cb), self);
1148 g_signal_connect (G_OBJECT (self), "key-press-event",
1149 G_CALLBACK (empathy_call_window_key_press_cb), self);
1151 priv->timer = g_timer_new ();
1153 g_object_ref (priv->ui_manager);
1154 g_object_unref (gui);
1156 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
1158 empathy_call_window_show_hangup_button (self, TRUE);
1160 empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1161 /* These signals are used to track the window position and save it
1162 * when the window is destroyed. We need to do this as we don't want
1163 * the window geometry to be saved with the sidebar taken into account. */
1164 g_signal_connect (self, "destroy",
1165 G_CALLBACK (empathy_call_window_destroyed_cb), self);
1166 g_signal_connect (self, "configure-event",
1167 G_CALLBACK (empathy_call_window_configure_event_cb), self);
1168 g_signal_connect (self, "window-state-event",
1169 G_CALLBACK (empathy_call_window_configure_event_cb), self);
1172 /* Instead of specifying a width and a height, we specify only one size. That's
1173 because we want a square avatar icon. */
1175 init_contact_avatar_with_size (EmpathyContact *contact,
1176 GtkWidget *image_widget,
1179 GdkPixbuf *pixbuf_avatar = NULL;
1181 if (contact != NULL)
1183 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1187 if (pixbuf_avatar == NULL)
1189 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
1190 EMPATHY_IMAGE_AVATAR_DEFAULT, size);
1193 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1195 if (pixbuf_avatar != NULL)
1196 g_object_unref (pixbuf_avatar);
1200 set_window_title (EmpathyCallWindow *self)
1202 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1205 if (priv->contact != NULL)
1207 /* translators: Call is a noun and %s is the contact name. This string
1208 * is used in the window title */
1209 tmp = g_strdup_printf (_("Call with %s"),
1210 empathy_contact_get_alias (priv->contact));
1211 gtk_window_set_title (GTK_WINDOW (self), tmp);
1216 gtk_window_set_title (GTK_WINDOW (self), _("Call with %d participants"));
1221 set_remote_user_name (EmpathyCallWindow *self,
1222 EmpathyContact *contact)
1224 const gchar *alias = empathy_contact_get_alias (contact);
1225 const gchar *status = empathy_contact_get_status (contact);
1228 label = g_strdup_printf ("%s\n<small>%s</small>", alias, status);
1229 gtk_label_set_markup (GTK_LABEL (self->priv->remote_user_name_toolbar),
1235 contact_name_changed_cb (EmpathyContact *contact,
1237 EmpathyCallWindow *self)
1239 set_window_title (self);
1240 set_remote_user_name (self, contact);
1244 contact_presence_changed_cb (EmpathyContact *contact,
1246 EmpathyCallWindow *self)
1248 set_remote_user_name (self, contact);
1252 contact_avatar_changed_cb (EmpathyContact *contact,
1254 EmpathyCallWindow *self)
1257 GtkAllocation allocation;
1258 GtkWidget *avatar_widget;
1260 avatar_widget = self->priv->remote_user_avatar_widget;
1262 gtk_widget_get_allocation (avatar_widget, &allocation);
1263 size = allocation.height;
1267 /* the widget is not allocated yet, set a default size */
1268 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1269 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1272 init_contact_avatar_with_size (contact, avatar_widget, size);
1274 avatar_widget = self->priv->remote_user_avatar_toolbar;
1276 gtk_widget_get_allocation (avatar_widget, &allocation);
1277 size = allocation.height;
1281 /* the widget is not allocated yet, set a default size */
1282 size = SMALL_TOOLBAR_SIZE;
1285 init_contact_avatar_with_size (contact, avatar_widget, size);
1289 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1290 EmpathyCallHandler *handler)
1292 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1294 g_signal_connect (priv->contact, "notify::name",
1295 G_CALLBACK (contact_name_changed_cb), self);
1296 g_signal_connect (priv->contact, "notify::avatar",
1297 G_CALLBACK (contact_avatar_changed_cb), self);
1298 /* FIXME: There's no EmpathyContact::presence yet */
1299 g_signal_connect (priv->contact, "notify::presence",
1300 G_CALLBACK (contact_presence_changed_cb), self);
1302 set_window_title (self);
1303 set_remote_user_name (self, priv->contact);
1305 init_contact_avatar_with_size (priv->contact,
1306 priv->remote_user_avatar_widget,
1307 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1308 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1310 init_contact_avatar_with_size (priv->contact,
1311 priv->remote_user_avatar_toolbar,
1312 SMALL_TOOLBAR_SIZE);
1314 /* The remote avatar is shown by default and will be hidden when we receive
1315 video from the remote side. */
1316 clutter_actor_hide (priv->video_output);
1317 gtk_widget_show (priv->remote_user_avatar_widget);
1321 update_send_codec (EmpathyCallWindow *self,
1324 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1331 codec = empathy_call_handler_get_send_audio_codec (priv->handler);
1332 widget = priv->acodec_encoding_label;
1336 codec = empathy_call_handler_get_send_video_codec (priv->handler);
1337 widget = priv->vcodec_encoding_label;
1343 tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
1344 gtk_label_set_text (GTK_LABEL (widget), tmp);
1349 send_audio_codec_notify_cb (GObject *object,
1353 EmpathyCallWindow *self = user_data;
1355 update_send_codec (self, TRUE);
1359 send_video_codec_notify_cb (GObject *object,
1363 EmpathyCallWindow *self = user_data;
1365 update_send_codec (self, FALSE);
1369 update_recv_codec (EmpathyCallWindow *self,
1372 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1375 GString *str = NULL;
1379 codecs = empathy_call_handler_get_recv_audio_codecs (priv->handler);
1380 widget = priv->acodec_decoding_label;
1384 codecs = empathy_call_handler_get_recv_video_codecs (priv->handler);
1385 widget = priv->vcodec_decoding_label;
1391 for (l = codecs; l != NULL; l = g_list_next (l))
1393 FsCodec *codec = l->data;
1396 str = g_string_new (NULL);
1398 g_string_append (str, ", ");
1400 g_string_append_printf (str, "%s/%u", codec->encoding_name,
1404 gtk_label_set_text (GTK_LABEL (widget), str->str);
1405 g_string_free (str, TRUE);
1409 recv_audio_codecs_notify_cb (GObject *object,
1413 EmpathyCallWindow *self = user_data;
1415 update_recv_codec (self, TRUE);
1419 recv_video_codecs_notify_cb (GObject *object,
1423 EmpathyCallWindow *self = user_data;
1425 update_recv_codec (self, FALSE);
1428 static const gchar *
1429 candidate_type_to_str (FsCandidate *candidate)
1431 switch (candidate->type)
1433 case FS_CANDIDATE_TYPE_HOST:
1435 case FS_CANDIDATE_TYPE_SRFLX:
1436 return "server reflexive";
1437 case FS_CANDIDATE_TYPE_PRFLX:
1438 return "peer reflexive";
1439 case FS_CANDIDATE_TYPE_RELAY:
1441 case FS_CANDIDATE_TYPE_MULTICAST:
1448 static const gchar *
1449 candidate_type_to_desc (FsCandidate *candidate)
1451 switch (candidate->type)
1453 case FS_CANDIDATE_TYPE_HOST:
1454 return _("The IP address as seen by the machine");
1455 case FS_CANDIDATE_TYPE_SRFLX:
1456 return _("The IP address as seen by a server on the Internet");
1457 case FS_CANDIDATE_TYPE_PRFLX:
1458 return _("The IP address of the peer as seen by the other side");
1459 case FS_CANDIDATE_TYPE_RELAY:
1460 return _("The IP address of a relay server");
1461 case FS_CANDIDATE_TYPE_MULTICAST:
1462 return _("The IP address of the multicast group");
1469 update_candidat_widget (EmpathyCallWindow *self,
1472 FsCandidate *candidate)
1476 g_assert (candidate != NULL);
1477 str = g_strdup_printf ("%s %u (%s)", candidate->ip,
1478 candidate->port, candidate_type_to_str (candidate));
1480 gtk_label_set_text (GTK_LABEL (label), str);
1481 gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
1487 candidates_changed_cb (GObject *object,
1489 EmpathyCallWindow *self)
1491 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1492 FsCandidate *candidate = NULL;
1494 if (type == FS_MEDIA_TYPE_VIDEO)
1496 /* Update remote candidate */
1497 candidate = empathy_call_handler_get_video_remote_candidate (
1500 update_candidat_widget (self, priv->video_remote_candidate_label,
1501 priv->video_remote_candidate_info_img, candidate);
1503 /* Update local candidate */
1504 candidate = empathy_call_handler_get_video_local_candidate (
1507 update_candidat_widget (self, priv->video_local_candidate_label,
1508 priv->video_local_candidate_info_img, candidate);
1512 /* Update remote candidate */
1513 candidate = empathy_call_handler_get_audio_remote_candidate (
1516 update_candidat_widget (self, priv->audio_remote_candidate_label,
1517 priv->audio_remote_candidate_info_img, candidate);
1519 /* Update local candidate */
1520 candidate = empathy_call_handler_get_audio_local_candidate (
1523 update_candidat_widget (self, priv->audio_local_candidate_label,
1524 priv->audio_local_candidate_info_img, candidate);
1529 empathy_call_window_constructed (GObject *object)
1531 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1532 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1533 TpyCallChannel *call;
1535 g_assert (priv->handler != NULL);
1537 g_object_get (priv->handler, "call-channel", &call, NULL);
1538 priv->outgoing = (call == NULL);
1540 g_object_unref (call);
1542 g_object_get (priv->handler, "target-contact", &priv->contact, NULL);
1543 g_assert (priv->contact != NULL);
1545 empathy_call_window_setup_avatars (self, priv->handler);
1546 empathy_call_window_set_state_connecting (self);
1548 if (!empathy_call_handler_has_initial_video (priv->handler))
1550 gtk_toggle_tool_button_set_active (
1551 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1553 /* If call has InitialVideo, the preview will be started once the call has
1554 * been started (start_call()). */
1556 update_send_codec (self, TRUE);
1557 update_send_codec (self, FALSE);
1558 update_recv_codec (self, TRUE);
1559 update_recv_codec (self, FALSE);
1561 tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
1562 G_CALLBACK (send_audio_codec_notify_cb), self, 0);
1563 tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
1564 G_CALLBACK (send_video_codec_notify_cb), self, 0);
1565 tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
1566 G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
1567 tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
1568 G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
1570 tp_g_signal_connect_object (priv->handler, "candidates-changed",
1571 G_CALLBACK (candidates_changed_cb), self, 0);
1574 static void empathy_call_window_dispose (GObject *object);
1575 static void empathy_call_window_finalize (GObject *object);
1578 empathy_call_window_set_property (GObject *object,
1579 guint property_id, const GValue *value, GParamSpec *pspec)
1581 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1583 switch (property_id)
1585 case PROP_CALL_HANDLER:
1586 priv->handler = g_value_dup_object (value);
1589 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1594 empathy_call_window_get_property (GObject *object,
1595 guint property_id, GValue *value, GParamSpec *pspec)
1597 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1599 switch (property_id)
1601 case PROP_CALL_HANDLER:
1602 g_value_set_object (value, priv->handler);
1605 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1610 empathy_call_window_class_init (
1611 EmpathyCallWindowClass *empathy_call_window_class)
1613 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1614 GParamSpec *param_spec;
1616 g_type_class_add_private (empathy_call_window_class,
1617 sizeof (EmpathyCallWindowPriv));
1619 object_class->constructed = empathy_call_window_constructed;
1620 object_class->set_property = empathy_call_window_set_property;
1621 object_class->get_property = empathy_call_window_get_property;
1623 object_class->dispose = empathy_call_window_dispose;
1624 object_class->finalize = empathy_call_window_finalize;
1626 param_spec = g_param_spec_object ("handler",
1627 "handler", "The call handler",
1628 EMPATHY_TYPE_CALL_HANDLER,
1629 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1630 g_object_class_install_property (object_class,
1631 PROP_CALL_HANDLER, param_spec);
1635 empathy_call_window_dispose (GObject *object)
1637 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1638 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1640 if (priv->dispose_has_run)
1643 priv->dispose_has_run = TRUE;
1645 if (priv->handler != NULL)
1647 empathy_call_handler_stop_call (priv->handler);
1648 tp_clear_object (&priv->handler);
1651 if (priv->bus_message_source_id != 0)
1653 g_source_remove (priv->bus_message_source_id);
1654 priv->bus_message_source_id = 0;
1657 if (priv->got_video_src > 0)
1659 g_source_remove (priv->got_video_src);
1660 priv->got_video_src = 0;
1663 tp_clear_object (&priv->pipeline);
1664 tp_clear_object (&priv->video_input);
1665 tp_clear_object (&priv->audio_input);
1666 tp_clear_object (&priv->video_tee);
1667 tp_clear_object (&priv->ui_manager);
1668 tp_clear_object (&priv->fullscreen);
1669 tp_clear_object (&priv->camera_monitor);
1671 g_list_free_full (priv->notifiers, g_object_unref);
1673 if (priv->timer_id != 0)
1674 g_source_remove (priv->timer_id);
1677 if (priv->contact != NULL)
1679 g_signal_handlers_disconnect_by_func (priv->contact,
1680 contact_name_changed_cb, self);
1681 priv->contact = NULL;
1685 tp_clear_object (&priv->sound_mgr);
1687 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1691 disconnect_video_output_motion_handler (EmpathyCallWindow *self)
1693 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1695 if (priv->video_output_motion_handler_id != 0)
1697 g_signal_handler_disconnect (G_OBJECT (priv->video_container),
1698 priv->video_output_motion_handler_id);
1699 priv->video_output_motion_handler_id = 0;
1704 empathy_call_window_finalize (GObject *object)
1706 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1707 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1709 disconnect_video_output_motion_handler (self);
1711 /* free any data held directly by the object here */
1712 g_mutex_free (priv->lock);
1714 g_timer_destroy (priv->timer);
1716 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1721 empathy_call_window_new (EmpathyCallHandler *handler)
1723 return EMPATHY_CALL_WINDOW (
1724 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1728 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1729 GstElement *conference, gpointer user_data)
1731 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1732 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1733 FsElementAddedNotifier *notifier;
1736 DEBUG ("Conference added");
1738 /* Add notifier to set the various element properties as needed */
1739 notifier = fs_element_added_notifier_new ();
1740 keyfile = fs_utils_get_default_element_properties (conference);
1742 if (keyfile != NULL)
1743 fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
1745 fs_element_added_notifier_add (notifier, GST_BIN (priv->pipeline));
1747 priv->notifiers = g_list_prepend (priv->notifiers, notifier);
1749 gst_bin_add (GST_BIN (priv->pipeline), conference);
1750 gst_element_set_state (conference, GST_STATE_PLAYING);
1754 empathy_call_window_conference_removed_cb (EmpathyCallHandler *handler,
1755 GstElement *conference, gpointer user_data)
1757 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1758 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1760 gst_bin_remove (GST_BIN (priv->pipeline), conference);
1761 gst_element_set_state (conference, GST_STATE_NULL);
1765 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1767 GstStateChangeReturn state_change_return;
1768 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1770 if (priv->pipeline == NULL)
1773 if (priv->bus_message_source_id != 0)
1775 g_source_remove (priv->bus_message_source_id);
1776 priv->bus_message_source_id = 0;
1779 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1781 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1782 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1784 if (priv->pipeline != NULL)
1785 g_object_unref (priv->pipeline);
1786 priv->pipeline = NULL;
1788 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1789 empathy_call_window_mic_volume_changed_cb, self);
1791 if (priv->audio_output != NULL)
1792 g_object_unref (priv->audio_output);
1793 priv->audio_output = NULL;
1795 if (priv->video_tee != NULL)
1796 g_object_unref (priv->video_tee);
1797 priv->video_tee = NULL;
1799 if (priv->video_preview != NULL)
1800 clutter_actor_destroy (priv->video_preview);
1801 priv->video_preview = NULL;
1803 priv->funnel = NULL;
1805 create_pipeline (self);
1806 /* Call will be started when user will hit the 'redial' button */
1807 priv->start_call_when_playing = FALSE;
1808 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1814 g_message ("Error: could not destroy pipeline. Closing call window");
1815 gtk_widget_destroy (GTK_WIDGET (self));
1822 reset_details_pane (EmpathyCallWindow *self)
1824 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1826 gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
1827 gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
1828 gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
1829 gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
1833 empathy_call_window_disconnected (EmpathyCallWindow *self,
1836 gboolean could_disconnect = FALSE;
1837 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1838 gboolean could_reset_pipeline;
1840 /* Leave full screen mode if needed */
1841 gtk_window_unfullscreen (GTK_WINDOW (self));
1843 gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1844 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1846 could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1848 if (priv->call_state == CONNECTING)
1849 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
1851 if (priv->call_state != REDIALING)
1852 priv->call_state = DISCONNECTED;
1854 if (could_reset_pipeline)
1856 g_mutex_lock (priv->lock);
1858 g_timer_stop (priv->timer);
1860 if (priv->timer_id != 0)
1861 g_source_remove (priv->timer_id);
1864 g_mutex_unlock (priv->lock);
1867 /* We are about to destroy the window, no need to update it or create
1868 * a video preview */
1871 empathy_call_window_status_message (self, _("Disconnected"));
1873 empathy_call_window_show_hangup_button (self, FALSE);
1875 /* Unsensitive the camera and mic button */
1876 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1877 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1879 /* Be sure that the mic button is enabled */
1880 gtk_toggle_tool_button_set_active (
1881 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1883 if (priv->camera_state == CAMERA_STATE_ON)
1885 /* Restart the preview with the new pipeline. */
1886 display_video_preview (self, TRUE);
1889 gtk_progress_bar_set_fraction (
1890 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1892 /* destroy the video output; it will be recreated when we'll redial */
1893 disconnect_video_output_motion_handler (self);
1894 if (priv->video_output != NULL)
1895 clutter_actor_destroy (priv->video_output);
1896 priv->video_output = NULL;
1897 if (priv->got_video_src > 0)
1899 g_source_remove (priv->got_video_src);
1900 priv->got_video_src = 0;
1903 gtk_widget_show (priv->remote_user_avatar_widget);
1905 reset_details_pane (self);
1907 priv->sending_video = FALSE;
1908 priv->call_started = FALSE;
1910 could_disconnect = TRUE;
1912 /* TODO: display the self avatar of the preview (depends if the "Always
1913 * Show Video Preview" is enabled or not) */
1916 return could_disconnect;
1921 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1924 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1925 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1927 if (empathy_call_window_disconnected (self, TRUE) &&
1928 priv->call_state == REDIALING)
1929 empathy_call_window_restart_call (self);
1933 empathy_call_window_sink_removed_cb (EmpathyCallHandler *handler,
1935 FsMediaType media_type,
1936 EmpathyCallWindow *self)
1938 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1940 DEBUG ("removing content");
1943 * This assumes that there is only one video stream per channel...
1946 if ((guint) media_type == FS_MEDIA_TYPE_VIDEO)
1948 if (priv->funnel != NULL)
1952 output = priv->video_output_sink;
1954 gst_element_set_state (output, GST_STATE_NULL);
1955 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1957 gst_bin_remove (GST_BIN (priv->pipeline), output);
1958 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1959 priv->funnel = NULL;
1963 else if (media_type == FS_MEDIA_TYPE_AUDIO)
1965 if (priv->audio_output != NULL)
1967 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1969 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1970 priv->audio_output = NULL;
1978 /* Called with global lock held */
1980 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1982 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1986 if (priv->funnel == NULL)
1988 output = priv->video_output_sink;
1990 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1994 g_warning ("Could not create fsfunnel");
1998 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
2000 gst_object_unref (priv->funnel);
2001 priv->funnel = NULL;
2002 g_warning ("Could not add funnel to pipeline");
2006 if (!gst_bin_add (GST_BIN (priv->pipeline), output))
2008 g_warning ("Could not add the video output widget to the pipeline");
2012 if (!gst_element_link (priv->funnel, output))
2014 g_warning ("Could not link output sink to funnel");
2015 goto error_output_added;
2018 if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2020 g_warning ("Could not start video sink");
2021 goto error_output_added;
2024 if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2026 g_warning ("Could not start funnel");
2027 goto error_output_added;
2031 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
2034 g_warning ("Could not get request pad from funnel");
2041 gst_element_set_locked_state (priv->funnel, TRUE);
2042 gst_element_set_locked_state (output, TRUE);
2044 gst_element_set_state (priv->funnel, GST_STATE_NULL);
2045 gst_element_set_state (output, GST_STATE_NULL);
2047 gst_bin_remove (GST_BIN (priv->pipeline), output);
2048 gst_element_set_locked_state (output, FALSE);
2052 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
2053 priv->funnel = NULL;
2058 /* Called with global lock held */
2060 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
2062 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2064 GstPadTemplate *template;
2066 if (priv->audio_output == NULL)
2068 priv->audio_output = empathy_audio_sink_new ();
2069 g_object_ref_sink (priv->audio_output);
2071 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
2073 g_warning ("Could not add audio sink to pipeline");
2074 g_object_unref (priv->audio_output);
2075 goto error_add_output;
2078 if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2080 g_warning ("Could not start audio sink");
2085 template = gst_element_class_get_pad_template (
2086 GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
2088 pad = gst_element_request_pad (priv->audio_output,
2089 template, NULL, NULL);
2093 g_warning ("Could not get sink pad from sink");
2100 gst_element_set_locked_state (priv->audio_output, TRUE);
2101 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
2102 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
2103 priv->audio_output = NULL;
2111 empathy_call_window_update_timer (gpointer user_data)
2113 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2114 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2118 time_ = g_timer_elapsed (priv->timer, NULL);
2120 /* Translators: 'status - minutes:seconds' the caller has been connected */
2121 str = g_strdup_printf (_("%s — %d:%02dm"),
2122 priv->call_state == HELD ? _("On hold") : _("Connected"),
2123 (int) time_ / 60, (int) time_ % 60);
2124 empathy_call_window_status_message (self, str);
2132 display_error (EmpathyCallWindow *self,
2133 TpyCallChannel *call,
2137 const gchar *details)
2139 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2140 GtkWidget *info_bar;
2141 GtkWidget *content_area;
2148 /* Create info bar */
2149 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2152 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
2154 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2156 /* hbox containing the image and the messages vbox */
2157 hbox = gtk_hbox_new (FALSE, 3);
2158 gtk_container_add (GTK_CONTAINER (content_area), hbox);
2161 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
2162 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2164 /* vbox containing the main message and the details expander */
2165 vbox = gtk_vbox_new (FALSE, 3);
2166 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2169 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2171 label = gtk_label_new (NULL);
2172 gtk_label_set_markup (GTK_LABEL (label), txt);
2173 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2174 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2177 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2180 if (details != NULL)
2182 GtkWidget *expander;
2184 expander = gtk_expander_new (_("Technical Details"));
2186 txt = g_strdup_printf ("<i>%s</i>", details);
2188 label = gtk_label_new (NULL);
2189 gtk_label_set_markup (GTK_LABEL (label), txt);
2190 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2191 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2194 gtk_container_add (GTK_CONTAINER (expander), label);
2195 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2198 g_signal_connect (info_bar, "response",
2199 G_CALLBACK (gtk_widget_destroy), NULL);
2201 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2202 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2203 gtk_widget_show_all (info_bar);
2207 media_stream_error_to_txt (EmpathyCallWindow *self,
2208 TpyCallChannel *call,
2210 TpMediaStreamError error)
2212 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2213 const gchar *cm = NULL;
2219 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2221 return g_strdup_printf (
2222 _("%s's software does not understand any of the audio formats "
2223 "supported by your computer"),
2224 empathy_contact_get_alias (priv->contact));
2226 return g_strdup_printf (
2227 _("%s's software does not understand any of the video formats "
2228 "supported by your computer"),
2229 empathy_contact_get_alias (priv->contact));
2231 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2232 return g_strdup_printf (
2233 _("Can't establish a connection to %s. "
2234 "One of you might be on a network that does not allow "
2235 "direct connections."),
2236 empathy_contact_get_alias (priv->contact));
2238 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2239 return g_strdup (_("There was a failure on the network"));
2241 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2243 return g_strdup (_("The audio formats necessary for this call "
2244 "are not installed on your computer"));
2246 return g_strdup (_("The video formats necessary for this call "
2247 "are not installed on your computer"));
2249 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2250 tp_connection_parse_object_path (
2251 tp_channel_borrow_connection (TP_CHANNEL (call)),
2254 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2255 "product=Telepathy&component=%s", cm);
2257 result = g_strdup_printf (
2258 _("Something unexpected happened in a Telepathy component. "
2259 "Please <a href=\"%s\">report this bug</a> and attach "
2260 "logs gathered from the 'Debug' window in the Help menu."), url);
2266 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2267 return g_strdup (_("There was a failure in the call engine"));
2269 case TP_MEDIA_STREAM_ERROR_EOS:
2270 return g_strdup (_("The end of the stream was reached"));
2272 case TP_MEDIA_STREAM_ERROR_UNKNOWN:
2279 empathy_call_window_stream_error (EmpathyCallWindow *self,
2280 TpyCallChannel *call,
2289 desc = media_stream_error_to_txt (self, call, audio, code);
2292 /* No description, use the error message. That's not great as it's not
2293 * localized but it's better than nothing. */
2294 display_error (self, call, icon, title, msg, NULL);
2298 display_error (self, call, icon, title, desc, msg);
2304 empathy_call_window_audio_stream_error (TpyCallChannel *call,
2307 EmpathyCallWindow *self)
2309 empathy_call_window_stream_error (self, call, TRUE, code, msg,
2310 "gnome-stock-mic", _("Can't establish audio stream"));
2314 empathy_call_window_video_stream_error (TpyCallChannel *call,
2317 EmpathyCallWindow *self)
2319 empathy_call_window_stream_error (self, call, FALSE, code, msg,
2320 "camera-web", _("Can't establish video stream"));
2325 empathy_call_window_state_changed_cb (EmpathyCallHandler *handler,
2327 EmpathyCallWindow *self)
2329 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2330 TpyCallChannel *call;
2331 gboolean can_send_video;
2333 if (state != TPY_CALL_STATE_ACCEPTED)
2336 if (priv->call_state == CONNECTED)
2339 g_timer_start (priv->timer);
2340 priv->call_state = CONNECTED;
2342 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2344 can_send_video = priv->video_input != NULL &&
2345 empathy_contact_can_voip_video (priv->contact) &&
2346 empathy_camera_monitor_get_available (priv->camera_monitor);
2348 g_object_get (priv->handler, "call-channel", &call, NULL);
2350 if (tpy_call_channel_has_dtmf (call))
2351 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2353 if (priv->video_input == NULL)
2354 empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
2356 gtk_widget_set_sensitive (priv->camera_button, can_send_video);
2358 empathy_call_window_show_hangup_button (self, TRUE);
2360 gtk_widget_set_sensitive (priv->mic_button, TRUE);
2362 clutter_actor_hide (priv->video_output);
2363 gtk_widget_show (priv->remote_user_avatar_widget);
2365 g_object_unref (call);
2367 g_mutex_lock (priv->lock);
2369 priv->timer_id = g_timeout_add_seconds (1,
2370 empathy_call_window_update_timer, self);
2372 g_mutex_unlock (priv->lock);
2374 empathy_call_window_update_timer (self);
2376 gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
2380 empathy_call_window_show_video_output_cb (gpointer user_data)
2382 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2384 if (self->priv->video_output != NULL)
2386 gtk_widget_hide (self->priv->remote_user_avatar_widget);
2387 clutter_actor_show (self->priv->video_output);
2394 empathy_call_window_check_video_cb (gpointer data)
2396 EmpathyCallWindow *self = data;
2398 if (self->priv->got_video)
2400 self->priv->got_video = FALSE;
2404 /* No video in the last N seconds, display the remote avatar */
2405 empathy_call_window_show_video_output (self, FALSE);
2410 /* Called from the streaming thread */
2412 empathy_call_window_video_probe_cb (GstPad *pad,
2413 GstMiniObject *mini_obj,
2414 EmpathyCallWindow *self)
2417 if (GST_IS_EVENT (mini_obj))
2420 if (G_UNLIKELY (!self->priv->got_video))
2422 /* show the remote video */
2423 g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
2424 empathy_call_window_show_video_output_cb,
2425 g_object_ref (self), g_object_unref);
2427 self->priv->got_video = TRUE;
2433 /* Called from the streaming thread */
2435 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2436 GstPad *src, guint media_type, gpointer user_data)
2438 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2439 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2440 gboolean retval = FALSE;
2444 g_mutex_lock (priv->lock);
2448 case TP_MEDIA_STREAM_TYPE_AUDIO:
2449 pad = empathy_call_window_get_audio_sink_pad (self);
2451 case TP_MEDIA_STREAM_TYPE_VIDEO:
2452 g_idle_add (empathy_call_window_show_video_output_cb, self);
2453 pad = empathy_call_window_get_video_sink_pad (self);
2455 gst_pad_add_data_probe (src,
2456 G_CALLBACK (empathy_call_window_video_probe_cb), self);
2457 if (priv->got_video_src > 0)
2458 g_source_remove (priv->got_video_src);
2459 priv->got_video_src = g_timeout_add_seconds (5,
2460 empathy_call_window_check_video_cb, self);
2463 g_assert_not_reached ();
2469 if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2470 g_warning ("Could not link %s sink pad",
2471 media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2475 gst_object_unref (pad);
2479 /* If no sink could be linked, try to add fakesink to prevent the whole call
2484 GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2486 if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2488 GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2489 if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2490 GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2492 gst_element_set_locked_state (fakesink, TRUE);
2493 gst_element_set_state (fakesink, GST_STATE_NULL);
2494 gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2498 DEBUG ("Could not link real sink, linked fakesink instead");
2500 gst_object_unref (sinkpad);
2504 gst_object_unref (fakesink);
2509 g_mutex_unlock (priv->lock);
2515 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2516 GstPad *sink, FsMediaType media_type, gpointer user_data)
2518 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2519 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2521 gboolean retval = FALSE;
2525 case FS_MEDIA_TYPE_AUDIO:
2526 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2528 g_warning ("Could not add audio source to pipeline");
2532 pad = gst_element_get_static_pad (priv->audio_input, "src");
2535 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2536 g_warning ("Could not get source pad from audio source");
2540 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2542 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2543 g_warning ("Could not link audio source to farsight");
2547 if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2549 g_warning ("Could not start audio source");
2550 gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2551 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2557 case FS_MEDIA_TYPE_VIDEO:
2558 if (priv->video_tee != NULL)
2560 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2561 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2563 g_warning ("Could not link video source input pipeline");
2566 gst_object_unref (pad);
2572 g_assert_not_reached ();
2579 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2581 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2582 GstElement *preview;
2584 disable_camera (self);
2586 DEBUG ("remove video input");
2587 preview = priv->video_preview_sink;
2589 gst_element_set_state (priv->video_input, GST_STATE_NULL);
2590 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2591 gst_element_set_state (preview, GST_STATE_NULL);
2593 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2596 g_object_unref (priv->video_input);
2597 priv->video_input = NULL;
2598 g_object_unref (priv->video_tee);
2599 priv->video_tee = NULL;
2600 clutter_actor_destroy (priv->video_preview);
2601 priv->video_preview = NULL;
2603 gtk_widget_set_sensitive (priv->camera_button, FALSE);
2607 start_call (EmpathyCallWindow *self)
2609 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2611 priv->call_started = TRUE;
2612 empathy_call_handler_start_call (priv->handler,
2613 gtk_get_current_event_time ());
2615 if (empathy_call_handler_has_initial_video (priv->handler))
2617 TpyCallChannel *call;
2620 g_object_get (priv->handler, "call-channel", &call, NULL);
2621 s = tpy_call_channel_get_video_state (call);
2623 if (s == TPY_SENDING_STATE_PENDING_SEND ||
2624 s == TPY_SENDING_STATE_SENDING)
2626 /* Enable 'send video' buttons and display the preview */
2627 gtk_toggle_tool_button_set_active (
2628 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), TRUE);
2632 gtk_toggle_tool_button_set_active (
2633 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
2635 if (priv->video_preview == NULL)
2637 create_video_preview (self);
2638 add_video_preview_to_pipeline (self);
2642 g_object_unref (call);
2647 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2650 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2651 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2654 empathy_call_handler_bus_message (priv->handler, bus, message);
2656 switch (GST_MESSAGE_TYPE (message))
2658 case GST_MESSAGE_STATE_CHANGED:
2659 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2661 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2662 if (newstate == GST_STATE_PAUSED)
2663 empathy_call_window_setup_video_input (self);
2665 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2666 !priv->call_started)
2668 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2669 if (newstate == GST_STATE_PAUSED)
2671 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2672 priv->pipeline_playing = TRUE;
2674 if (priv->start_call_when_playing)
2679 case GST_MESSAGE_ERROR:
2681 GError *error = NULL;
2682 GstElement *gst_error;
2685 gst_message_parse_error (message, &error, &debug);
2686 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2688 g_message ("Element error: %s -- %s\n", error->message, debug);
2690 if (g_str_has_prefix (gst_element_get_name (gst_error),
2691 VIDEO_INPUT_ERROR_PREFIX))
2693 /* Remove the video input and continue */
2694 if (priv->video_input != NULL)
2695 empathy_call_window_remove_video_input (self);
2696 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2700 empathy_call_window_disconnected (self, TRUE);
2702 g_error_free (error);
2705 case GST_MESSAGE_UNKNOWN:
2706 case GST_MESSAGE_EOS:
2707 case GST_MESSAGE_WARNING:
2708 case GST_MESSAGE_INFO:
2709 case GST_MESSAGE_TAG:
2710 case GST_MESSAGE_BUFFERING:
2711 case GST_MESSAGE_STATE_DIRTY:
2712 case GST_MESSAGE_STEP_DONE:
2713 case GST_MESSAGE_CLOCK_PROVIDE:
2714 case GST_MESSAGE_CLOCK_LOST:
2715 case GST_MESSAGE_NEW_CLOCK:
2716 case GST_MESSAGE_STRUCTURE_CHANGE:
2717 case GST_MESSAGE_STREAM_STATUS:
2718 case GST_MESSAGE_APPLICATION:
2719 case GST_MESSAGE_ELEMENT:
2720 case GST_MESSAGE_SEGMENT_START:
2721 case GST_MESSAGE_SEGMENT_DONE:
2722 case GST_MESSAGE_DURATION:
2723 case GST_MESSAGE_ANY:
2732 empathy_call_window_members_changed_cb (TpyCallChannel *call,
2733 GHashTable *members,
2734 EmpathyCallWindow *self)
2736 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2737 GHashTableIter iter;
2738 gpointer key, value;
2739 gboolean held = FALSE;
2741 g_hash_table_iter_init (&iter, members);
2742 while (g_hash_table_iter_next (&iter, &key, &value))
2744 if (GPOINTER_TO_INT (value) & TPY_CALL_MEMBER_FLAG_HELD)
2746 /* This assumes this is a 1-1 call, otherwise one participant
2747 * putting the call on hold wouldn't mean the call is on hold
2755 priv->call_state = HELD;
2756 else if (priv->call_state == HELD)
2757 priv->call_state = CONNECTED;
2761 call_handler_notify_call_cb (EmpathyCallHandler *handler,
2763 EmpathyCallWindow *self)
2765 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2766 TpyCallChannel *call;
2768 g_object_get (priv->handler, "call-channel", &call, NULL);
2773 tp_g_signal_connect_object (call, "audio-stream-error",
2774 G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
2775 tp_g_signal_connect_object (call, "video-stream-error",
2776 G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
2779 tp_g_signal_connect_object (call, "members-changed",
2780 G_CALLBACK (empathy_call_window_members_changed_cb), self, 0);
2782 g_object_unref (call);
2786 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2788 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2789 TpyCallChannel *call;
2792 /* Make the hangup button twice as wide */
2793 width = gtk_widget_get_allocated_width (priv->hangup_button);
2794 gtk_widget_set_size_request (priv->hangup_button, width * 2, -1);
2796 g_signal_connect (priv->handler, "state-changed",
2797 G_CALLBACK (empathy_call_window_state_changed_cb), window);
2798 g_signal_connect (priv->handler, "conference-added",
2799 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2800 g_signal_connect (priv->handler, "conference-removed",
2801 G_CALLBACK (empathy_call_window_conference_removed_cb), window);
2802 g_signal_connect (priv->handler, "closed",
2803 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2804 g_signal_connect (priv->handler, "src-pad-added",
2805 G_CALLBACK (empathy_call_window_src_added_cb), window);
2806 g_signal_connect (priv->handler, "sink-pad-added",
2807 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2808 g_signal_connect (priv->handler, "sink-pad-removed",
2809 G_CALLBACK (empathy_call_window_sink_removed_cb), window);
2811 g_object_get (priv->handler, "call-channel", &call, NULL);
2814 call_handler_notify_call_cb (priv->handler, NULL, window);
2815 g_object_unref (call);
2819 /* call-channel doesn't exist yet, we'll connect signals once it has been
2821 g_signal_connect (priv->handler, "notify::call-channel",
2822 G_CALLBACK (call_handler_notify_call_cb), window);
2825 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2829 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2830 EmpathyCallWindow *window)
2832 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2834 if (priv->pipeline != NULL)
2836 if (priv->bus_message_source_id != 0)
2838 g_source_remove (priv->bus_message_source_id);
2839 priv->bus_message_source_id = 0;
2842 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2845 if (priv->call_state == CONNECTING)
2846 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2852 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2855 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2857 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2862 gtk_widget_hide (priv->sidebar);
2863 gtk_widget_hide (menu);
2864 gtk_widget_hide (priv->toolbar);
2868 if (priv->sidebar_was_visible_before_fs)
2869 gtk_widget_show (priv->sidebar);
2871 gtk_widget_show (menu);
2872 gtk_widget_show (priv->toolbar);
2874 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2875 priv->original_height_before_fs);
2880 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2882 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2884 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2885 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2886 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2887 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2889 if (priv->video_output != NULL)
2892 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2893 priv->video_output, TRUE, TRUE,
2894 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2901 empathy_call_window_state_event_cb (GtkWidget *widget,
2902 GdkEventWindowState *event, EmpathyCallWindow *window)
2904 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2906 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2907 gboolean set_fullscreen = event->new_window_state &
2908 GDK_WINDOW_STATE_FULLSCREEN;
2912 gboolean sidebar_was_visible;
2913 GtkAllocation allocation;
2914 gint original_width, original_height;
2916 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2917 original_width = allocation.width;
2918 original_height = allocation.height;
2920 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2922 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2923 priv->original_width_before_fs = original_width;
2924 priv->original_height_before_fs = original_height;
2926 if (priv->video_output_motion_handler_id == 0 &&
2927 priv->video_output != NULL)
2929 priv->video_output_motion_handler_id = g_signal_connect (
2930 G_OBJECT (priv->video_container), "motion-notify-event",
2931 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2937 disconnect_video_output_motion_handler (window);
2940 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2942 show_controls (window, set_fullscreen);
2943 show_borders (window, set_fullscreen);
2944 gtk_action_set_stock_id (priv->menu_fullscreen,
2945 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2946 priv->is_fullscreen = set_fullscreen;
2953 empathy_call_window_update_sidebar_buttons (EmpathyCallWindow *window,
2956 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2958 /* Update dialpad button */
2959 g_signal_handlers_block_by_func (priv->dialpad_button,
2960 empathy_call_window_dialpad_cb, window);
2961 gtk_toggle_tool_button_set_active (
2962 GTK_TOGGLE_TOOL_BUTTON (priv->dialpad_button),
2964 g_signal_handlers_unblock_by_func (priv->dialpad_button,
2965 empathy_call_window_dialpad_cb, window);
2967 /* Update sidebar menu */
2968 g_signal_handlers_block_by_func (priv->menu_sidebar,
2969 empathy_call_window_sidebar_cb, window);
2970 gtk_toggle_action_set_active (
2971 GTK_TOGGLE_ACTION (priv->menu_sidebar),
2972 gtk_widget_get_visible (priv->sidebar));
2973 g_signal_handlers_unblock_by_func (priv->menu_sidebar,
2974 empathy_call_window_sidebar_cb, window);
2978 empathy_call_window_show_sidebar (EmpathyCallWindow *window,
2981 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2982 int w, h, sidebar_width, handle_size;
2983 GtkAllocation allocation;
2985 gboolean dialpad_shown;
2987 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2988 w = allocation.width;
2989 h = allocation.height;
2991 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2993 gtk_widget_get_preferred_width (priv->sidebar, &sidebar_width, NULL);
2997 gtk_widget_show (priv->sidebar);
2998 w += sidebar_width + handle_size;
3002 w -= sidebar_width + handle_size;
3003 gtk_widget_hide (priv->sidebar);
3007 gtk_window_resize (GTK_WINDOW (window), w, h);
3009 /* Update the 'Dialpad' menu */
3010 page = ev_sidebar_get_current_page (EV_SIDEBAR (priv->sidebar));
3011 dialpad_shown = active && !tp_strdiff (page, "dialpad");
3014 empathy_call_window_update_sidebar_buttons (window, dialpad_shown);
3018 empathy_call_window_set_send_video (EmpathyCallWindow *window,
3021 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3022 TpyCallChannel *call;
3024 priv->sending_video = (state == CAMERA_STATE_ON);
3026 if (state == CAMERA_STATE_ON)
3028 /* When we start sending video, we want to show the video preview by
3030 display_video_preview (window, TRUE);
3034 display_video_preview (window, FALSE);
3037 if (priv->call_state != CONNECTED)
3040 g_object_get (priv->handler, "call-channel", &call, NULL);
3041 DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
3042 tpy_call_channel_send_video (call, priv->sending_video);
3043 g_object_unref (call);
3047 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
3048 EmpathyCallWindow *window)
3050 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3053 active = (gtk_toggle_tool_button_get_active (toggle));
3057 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
3059 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
3063 /* TODO, Instead of setting the input volume to 0 we should probably
3064 * stop sending but this would cause the audio call to drop if both
3065 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
3066 * in the future. GNOME #574574
3068 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
3070 gtk_adjustment_set_value (priv->audio_input_adj, 0);
3075 empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
3076 EmpathyCallWindow *window)
3078 empathy_call_window_show_sidebar (window, FALSE);
3082 empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
3083 EmpathyCallWindow *window)
3085 empathy_call_window_show_sidebar (window, TRUE);
3089 empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
3091 EmpathyCallWindow *window)
3093 empathy_call_window_update_sidebar_buttons (window,
3094 !tp_strdiff (page, "dialpad"));
3098 empathy_call_window_hangup_cb (gpointer object,
3099 EmpathyCallWindow *window)
3101 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3103 empathy_call_handler_stop_call (priv->handler);
3105 if (empathy_call_window_disconnected (window, FALSE))
3106 gtk_widget_destroy (GTK_WIDGET (window));
3110 empathy_call_window_restart_call (EmpathyCallWindow *window)
3112 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3114 /* Remove error info bars */
3115 gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
3116 (GtkCallback) gtk_widget_destroy, NULL);
3118 create_video_output_widget (window);
3120 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
3121 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
3123 /* While the call was disconnected, the input volume might have changed.
3124 * However, since the audio_input source was destroyed, its volume has not
3125 * been updated during that time. That's why we manually update it here */
3126 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
3128 priv->outgoing = TRUE;
3129 empathy_call_window_set_state_connecting (window);
3131 if (priv->pipeline_playing)
3132 start_call (window);
3134 /* call will be started when the pipeline is ready */
3135 priv->start_call_when_playing = TRUE;
3137 empathy_call_window_setup_avatars (window, priv->handler);
3139 empathy_call_window_show_hangup_button (window, TRUE);
3143 empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
3144 EmpathyCallWindow *window)
3146 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3149 active = gtk_toggle_tool_button_get_active (button);
3152 ev_sidebar_set_current_page (EV_SIDEBAR (priv->sidebar), "dialpad");
3154 empathy_call_window_show_sidebar (window, active);
3158 empathy_call_window_sidebar_cb (GtkToggleAction *menu,
3159 EmpathyCallWindow *self)
3161 empathy_call_window_show_sidebar (self,
3162 gtk_toggle_action_get_active (menu));
3166 empathy_call_window_fullscreen_cb (gpointer object,
3167 EmpathyCallWindow *window)
3169 empathy_call_window_fullscreen_toggle (window);
3173 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
3175 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3177 if (priv->is_fullscreen)
3178 gtk_window_unfullscreen (GTK_WINDOW (window));
3180 gtk_window_fullscreen (GTK_WINDOW (window));
3184 empathy_call_window_video_button_press_cb (GtkWidget *video_preview,
3185 GdkEventButton *event, EmpathyCallWindow *window)
3187 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
3189 empathy_call_window_video_menu_popup (window, event->button);
3197 empathy_call_window_key_press_cb (GtkWidget *video_output,
3198 GdkEventKey *event, EmpathyCallWindow *window)
3200 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3202 if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
3204 /* Since we are in fullscreen mode, toggling will bring us back to
3206 empathy_call_window_fullscreen_toggle (window);
3214 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
3215 GdkEventMotion *event, EmpathyCallWindow *window)
3217 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3219 if (priv->is_fullscreen)
3221 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
3228 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
3232 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3234 menu = gtk_ui_manager_get_widget (priv->ui_manager,
3236 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
3237 button, gtk_get_current_event_time ());
3238 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
3242 empathy_call_window_status_message (EmpathyCallWindow *self,
3245 gtk_label_set_label (GTK_LABEL (self->priv->status_label), message);
3249 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
3250 gdouble value, EmpathyCallWindow *window)
3252 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3254 if (priv->audio_output == NULL)
3257 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
3262 empathy_call_window_get_ui_manager (EmpathyCallWindow *window)
3264 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3266 return priv->ui_manager;
3269 EmpathyGstAudioSrc *
3270 empathy_call_window_get_audio_src (EmpathyCallWindow *window)
3272 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3274 return (EmpathyGstAudioSrc *) priv->audio_input;