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 tp_g_signal_connect_object (priv->contact, "notify::name",
1295 G_CALLBACK (contact_name_changed_cb), self, 0);
1296 tp_g_signal_connect_object (priv->contact, "notify::avatar",
1297 G_CALLBACK (contact_avatar_changed_cb), self, 0);
1298 tp_g_signal_connect_object (priv->contact, "notify::presence",
1299 G_CALLBACK (contact_presence_changed_cb), self, 0);
1301 set_window_title (self);
1302 set_remote_user_name (self, priv->contact);
1304 init_contact_avatar_with_size (priv->contact,
1305 priv->remote_user_avatar_widget,
1306 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1307 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1309 init_contact_avatar_with_size (priv->contact,
1310 priv->remote_user_avatar_toolbar,
1311 SMALL_TOOLBAR_SIZE);
1313 /* The remote avatar is shown by default and will be hidden when we receive
1314 video from the remote side. */
1315 clutter_actor_hide (priv->video_output);
1316 gtk_widget_show (priv->remote_user_avatar_widget);
1320 update_send_codec (EmpathyCallWindow *self,
1323 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1330 codec = empathy_call_handler_get_send_audio_codec (priv->handler);
1331 widget = priv->acodec_encoding_label;
1335 codec = empathy_call_handler_get_send_video_codec (priv->handler);
1336 widget = priv->vcodec_encoding_label;
1342 tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
1343 gtk_label_set_text (GTK_LABEL (widget), tmp);
1348 send_audio_codec_notify_cb (GObject *object,
1352 EmpathyCallWindow *self = user_data;
1354 update_send_codec (self, TRUE);
1358 send_video_codec_notify_cb (GObject *object,
1362 EmpathyCallWindow *self = user_data;
1364 update_send_codec (self, FALSE);
1368 update_recv_codec (EmpathyCallWindow *self,
1371 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1374 GString *str = NULL;
1378 codecs = empathy_call_handler_get_recv_audio_codecs (priv->handler);
1379 widget = priv->acodec_decoding_label;
1383 codecs = empathy_call_handler_get_recv_video_codecs (priv->handler);
1384 widget = priv->vcodec_decoding_label;
1390 for (l = codecs; l != NULL; l = g_list_next (l))
1392 FsCodec *codec = l->data;
1395 str = g_string_new (NULL);
1397 g_string_append (str, ", ");
1399 g_string_append_printf (str, "%s/%u", codec->encoding_name,
1403 gtk_label_set_text (GTK_LABEL (widget), str->str);
1404 g_string_free (str, TRUE);
1408 recv_audio_codecs_notify_cb (GObject *object,
1412 EmpathyCallWindow *self = user_data;
1414 update_recv_codec (self, TRUE);
1418 recv_video_codecs_notify_cb (GObject *object,
1422 EmpathyCallWindow *self = user_data;
1424 update_recv_codec (self, FALSE);
1427 static const gchar *
1428 candidate_type_to_str (FsCandidate *candidate)
1430 switch (candidate->type)
1432 case FS_CANDIDATE_TYPE_HOST:
1434 case FS_CANDIDATE_TYPE_SRFLX:
1435 return "server reflexive";
1436 case FS_CANDIDATE_TYPE_PRFLX:
1437 return "peer reflexive";
1438 case FS_CANDIDATE_TYPE_RELAY:
1440 case FS_CANDIDATE_TYPE_MULTICAST:
1447 static const gchar *
1448 candidate_type_to_desc (FsCandidate *candidate)
1450 switch (candidate->type)
1452 case FS_CANDIDATE_TYPE_HOST:
1453 return _("The IP address as seen by the machine");
1454 case FS_CANDIDATE_TYPE_SRFLX:
1455 return _("The IP address as seen by a server on the Internet");
1456 case FS_CANDIDATE_TYPE_PRFLX:
1457 return _("The IP address of the peer as seen by the other side");
1458 case FS_CANDIDATE_TYPE_RELAY:
1459 return _("The IP address of a relay server");
1460 case FS_CANDIDATE_TYPE_MULTICAST:
1461 return _("The IP address of the multicast group");
1468 update_candidat_widget (EmpathyCallWindow *self,
1471 FsCandidate *candidate)
1475 g_assert (candidate != NULL);
1476 str = g_strdup_printf ("%s %u (%s)", candidate->ip,
1477 candidate->port, candidate_type_to_str (candidate));
1479 gtk_label_set_text (GTK_LABEL (label), str);
1480 gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
1486 candidates_changed_cb (GObject *object,
1488 EmpathyCallWindow *self)
1490 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1491 FsCandidate *candidate = NULL;
1493 if (type == FS_MEDIA_TYPE_VIDEO)
1495 /* Update remote candidate */
1496 candidate = empathy_call_handler_get_video_remote_candidate (
1499 update_candidat_widget (self, priv->video_remote_candidate_label,
1500 priv->video_remote_candidate_info_img, candidate);
1502 /* Update local candidate */
1503 candidate = empathy_call_handler_get_video_local_candidate (
1506 update_candidat_widget (self, priv->video_local_candidate_label,
1507 priv->video_local_candidate_info_img, candidate);
1511 /* Update remote candidate */
1512 candidate = empathy_call_handler_get_audio_remote_candidate (
1515 update_candidat_widget (self, priv->audio_remote_candidate_label,
1516 priv->audio_remote_candidate_info_img, candidate);
1518 /* Update local candidate */
1519 candidate = empathy_call_handler_get_audio_local_candidate (
1522 update_candidat_widget (self, priv->audio_local_candidate_label,
1523 priv->audio_local_candidate_info_img, candidate);
1528 empathy_call_window_constructed (GObject *object)
1530 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1531 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1532 TpyCallChannel *call;
1534 g_assert (priv->handler != NULL);
1536 g_object_get (priv->handler, "call-channel", &call, NULL);
1537 priv->outgoing = (call == NULL);
1539 g_object_unref (call);
1541 g_object_get (priv->handler, "target-contact", &priv->contact, NULL);
1542 g_assert (priv->contact != NULL);
1544 empathy_call_window_setup_avatars (self, priv->handler);
1545 empathy_call_window_set_state_connecting (self);
1547 if (!empathy_call_handler_has_initial_video (priv->handler))
1549 gtk_toggle_tool_button_set_active (
1550 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1552 /* If call has InitialVideo, the preview will be started once the call has
1553 * been started (start_call()). */
1555 update_send_codec (self, TRUE);
1556 update_send_codec (self, FALSE);
1557 update_recv_codec (self, TRUE);
1558 update_recv_codec (self, FALSE);
1560 tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
1561 G_CALLBACK (send_audio_codec_notify_cb), self, 0);
1562 tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
1563 G_CALLBACK (send_video_codec_notify_cb), self, 0);
1564 tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
1565 G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
1566 tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
1567 G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
1569 tp_g_signal_connect_object (priv->handler, "candidates-changed",
1570 G_CALLBACK (candidates_changed_cb), self, 0);
1573 static void empathy_call_window_dispose (GObject *object);
1574 static void empathy_call_window_finalize (GObject *object);
1577 empathy_call_window_set_property (GObject *object,
1578 guint property_id, const GValue *value, GParamSpec *pspec)
1580 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1582 switch (property_id)
1584 case PROP_CALL_HANDLER:
1585 priv->handler = g_value_dup_object (value);
1588 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1593 empathy_call_window_get_property (GObject *object,
1594 guint property_id, GValue *value, GParamSpec *pspec)
1596 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1598 switch (property_id)
1600 case PROP_CALL_HANDLER:
1601 g_value_set_object (value, priv->handler);
1604 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1609 empathy_call_window_class_init (
1610 EmpathyCallWindowClass *empathy_call_window_class)
1612 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1613 GParamSpec *param_spec;
1615 g_type_class_add_private (empathy_call_window_class,
1616 sizeof (EmpathyCallWindowPriv));
1618 object_class->constructed = empathy_call_window_constructed;
1619 object_class->set_property = empathy_call_window_set_property;
1620 object_class->get_property = empathy_call_window_get_property;
1622 object_class->dispose = empathy_call_window_dispose;
1623 object_class->finalize = empathy_call_window_finalize;
1625 param_spec = g_param_spec_object ("handler",
1626 "handler", "The call handler",
1627 EMPATHY_TYPE_CALL_HANDLER,
1628 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1629 g_object_class_install_property (object_class,
1630 PROP_CALL_HANDLER, param_spec);
1634 empathy_call_window_dispose (GObject *object)
1636 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1637 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1639 if (priv->dispose_has_run)
1642 priv->dispose_has_run = TRUE;
1644 if (priv->handler != NULL)
1646 empathy_call_handler_stop_call (priv->handler);
1647 tp_clear_object (&priv->handler);
1650 if (priv->bus_message_source_id != 0)
1652 g_source_remove (priv->bus_message_source_id);
1653 priv->bus_message_source_id = 0;
1656 if (priv->got_video_src > 0)
1658 g_source_remove (priv->got_video_src);
1659 priv->got_video_src = 0;
1662 tp_clear_object (&priv->pipeline);
1663 tp_clear_object (&priv->video_input);
1664 tp_clear_object (&priv->audio_input);
1665 tp_clear_object (&priv->video_tee);
1666 tp_clear_object (&priv->ui_manager);
1667 tp_clear_object (&priv->fullscreen);
1668 tp_clear_object (&priv->camera_monitor);
1670 g_list_free_full (priv->notifiers, g_object_unref);
1672 if (priv->timer_id != 0)
1673 g_source_remove (priv->timer_id);
1676 if (priv->contact != NULL)
1678 g_signal_handlers_disconnect_by_func (priv->contact,
1679 contact_name_changed_cb, self);
1680 priv->contact = NULL;
1684 tp_clear_object (&priv->sound_mgr);
1686 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1690 disconnect_video_output_motion_handler (EmpathyCallWindow *self)
1692 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1694 if (priv->video_output_motion_handler_id != 0)
1696 g_signal_handler_disconnect (G_OBJECT (priv->video_container),
1697 priv->video_output_motion_handler_id);
1698 priv->video_output_motion_handler_id = 0;
1703 empathy_call_window_finalize (GObject *object)
1705 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1706 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1708 disconnect_video_output_motion_handler (self);
1710 /* free any data held directly by the object here */
1711 g_mutex_free (priv->lock);
1713 g_timer_destroy (priv->timer);
1715 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1720 empathy_call_window_new (EmpathyCallHandler *handler)
1722 return EMPATHY_CALL_WINDOW (
1723 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1727 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1728 GstElement *conference, gpointer user_data)
1730 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1731 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1732 FsElementAddedNotifier *notifier;
1735 DEBUG ("Conference added");
1737 /* Add notifier to set the various element properties as needed */
1738 notifier = fs_element_added_notifier_new ();
1739 keyfile = fs_utils_get_default_element_properties (conference);
1741 if (keyfile != NULL)
1742 fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
1744 fs_element_added_notifier_add (notifier, GST_BIN (priv->pipeline));
1746 priv->notifiers = g_list_prepend (priv->notifiers, notifier);
1748 gst_bin_add (GST_BIN (priv->pipeline), conference);
1749 gst_element_set_state (conference, GST_STATE_PLAYING);
1753 empathy_call_window_conference_removed_cb (EmpathyCallHandler *handler,
1754 GstElement *conference, gpointer user_data)
1756 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1757 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1759 gst_bin_remove (GST_BIN (priv->pipeline), conference);
1760 gst_element_set_state (conference, GST_STATE_NULL);
1764 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1766 GstStateChangeReturn state_change_return;
1767 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1769 if (priv->pipeline == NULL)
1772 if (priv->bus_message_source_id != 0)
1774 g_source_remove (priv->bus_message_source_id);
1775 priv->bus_message_source_id = 0;
1778 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1780 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1781 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1783 if (priv->pipeline != NULL)
1784 g_object_unref (priv->pipeline);
1785 priv->pipeline = NULL;
1787 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1788 empathy_call_window_mic_volume_changed_cb, self);
1790 if (priv->audio_output != NULL)
1791 g_object_unref (priv->audio_output);
1792 priv->audio_output = NULL;
1794 if (priv->video_tee != NULL)
1795 g_object_unref (priv->video_tee);
1796 priv->video_tee = NULL;
1798 if (priv->video_preview != NULL)
1799 clutter_actor_destroy (priv->video_preview);
1800 priv->video_preview = NULL;
1802 priv->funnel = NULL;
1804 create_pipeline (self);
1805 /* Call will be started when user will hit the 'redial' button */
1806 priv->start_call_when_playing = FALSE;
1807 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1813 g_message ("Error: could not destroy pipeline. Closing call window");
1814 gtk_widget_destroy (GTK_WIDGET (self));
1821 reset_details_pane (EmpathyCallWindow *self)
1823 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1825 gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
1826 gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
1827 gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
1828 gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
1832 empathy_call_window_disconnected (EmpathyCallWindow *self,
1835 gboolean could_disconnect = FALSE;
1836 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1837 gboolean could_reset_pipeline;
1839 /* Leave full screen mode if needed */
1840 gtk_window_unfullscreen (GTK_WINDOW (self));
1842 gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1843 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1845 could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1847 if (priv->call_state == CONNECTING)
1848 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
1850 if (priv->call_state != REDIALING)
1851 priv->call_state = DISCONNECTED;
1853 if (could_reset_pipeline)
1855 g_mutex_lock (priv->lock);
1857 g_timer_stop (priv->timer);
1859 if (priv->timer_id != 0)
1860 g_source_remove (priv->timer_id);
1863 g_mutex_unlock (priv->lock);
1866 /* We are about to destroy the window, no need to update it or create
1867 * a video preview */
1870 empathy_call_window_status_message (self, _("Disconnected"));
1872 empathy_call_window_show_hangup_button (self, FALSE);
1874 /* Unsensitive the camera and mic button */
1875 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1876 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1878 /* Be sure that the mic button is enabled */
1879 gtk_toggle_tool_button_set_active (
1880 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1882 if (priv->camera_state == CAMERA_STATE_ON)
1884 /* Restart the preview with the new pipeline. */
1885 display_video_preview (self, TRUE);
1888 gtk_progress_bar_set_fraction (
1889 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1891 /* destroy the video output; it will be recreated when we'll redial */
1892 disconnect_video_output_motion_handler (self);
1893 if (priv->video_output != NULL)
1894 clutter_actor_destroy (priv->video_output);
1895 priv->video_output = NULL;
1896 if (priv->got_video_src > 0)
1898 g_source_remove (priv->got_video_src);
1899 priv->got_video_src = 0;
1902 gtk_widget_show (priv->remote_user_avatar_widget);
1904 reset_details_pane (self);
1906 priv->sending_video = FALSE;
1907 priv->call_started = FALSE;
1909 could_disconnect = TRUE;
1911 /* TODO: display the self avatar of the preview (depends if the "Always
1912 * Show Video Preview" is enabled or not) */
1915 return could_disconnect;
1920 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1923 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1924 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1926 if (empathy_call_window_disconnected (self, TRUE) &&
1927 priv->call_state == REDIALING)
1928 empathy_call_window_restart_call (self);
1932 empathy_call_window_sink_removed_cb (EmpathyCallHandler *handler,
1934 FsMediaType media_type,
1935 EmpathyCallWindow *self)
1937 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1939 DEBUG ("removing content");
1942 * This assumes that there is only one video stream per channel...
1945 if ((guint) media_type == FS_MEDIA_TYPE_VIDEO)
1947 if (priv->funnel != NULL)
1951 output = priv->video_output_sink;
1953 gst_element_set_state (output, GST_STATE_NULL);
1954 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1956 gst_bin_remove (GST_BIN (priv->pipeline), output);
1957 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1958 priv->funnel = NULL;
1962 else if (media_type == FS_MEDIA_TYPE_AUDIO)
1964 if (priv->audio_output != NULL)
1966 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1968 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1969 priv->audio_output = NULL;
1977 /* Called with global lock held */
1979 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1981 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1985 if (priv->funnel == NULL)
1987 output = priv->video_output_sink;
1989 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1993 g_warning ("Could not create fsfunnel");
1997 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
1999 gst_object_unref (priv->funnel);
2000 priv->funnel = NULL;
2001 g_warning ("Could not add funnel to pipeline");
2005 if (!gst_bin_add (GST_BIN (priv->pipeline), output))
2007 g_warning ("Could not add the video output widget to the pipeline");
2011 if (!gst_element_link (priv->funnel, output))
2013 g_warning ("Could not link output sink to funnel");
2014 goto error_output_added;
2017 if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2019 g_warning ("Could not start video sink");
2020 goto error_output_added;
2023 if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2025 g_warning ("Could not start funnel");
2026 goto error_output_added;
2030 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
2033 g_warning ("Could not get request pad from funnel");
2040 gst_element_set_locked_state (priv->funnel, TRUE);
2041 gst_element_set_locked_state (output, TRUE);
2043 gst_element_set_state (priv->funnel, GST_STATE_NULL);
2044 gst_element_set_state (output, GST_STATE_NULL);
2046 gst_bin_remove (GST_BIN (priv->pipeline), output);
2047 gst_element_set_locked_state (output, FALSE);
2051 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
2052 priv->funnel = NULL;
2057 /* Called with global lock held */
2059 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
2061 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2063 GstPadTemplate *template;
2065 if (priv->audio_output == NULL)
2067 priv->audio_output = empathy_audio_sink_new ();
2068 g_object_ref_sink (priv->audio_output);
2070 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
2072 g_warning ("Could not add audio sink to pipeline");
2073 g_object_unref (priv->audio_output);
2074 goto error_add_output;
2077 if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2079 g_warning ("Could not start audio sink");
2084 template = gst_element_class_get_pad_template (
2085 GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
2087 pad = gst_element_request_pad (priv->audio_output,
2088 template, NULL, NULL);
2092 g_warning ("Could not get sink pad from sink");
2099 gst_element_set_locked_state (priv->audio_output, TRUE);
2100 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
2101 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
2102 priv->audio_output = NULL;
2110 empathy_call_window_update_timer (gpointer user_data)
2112 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2113 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2117 time_ = g_timer_elapsed (priv->timer, NULL);
2119 /* Translators: 'status - minutes:seconds' the caller has been connected */
2120 str = g_strdup_printf (_("%s — %d:%02dm"),
2121 priv->call_state == HELD ? _("On hold") : _("Connected"),
2122 (int) time_ / 60, (int) time_ % 60);
2123 empathy_call_window_status_message (self, str);
2131 display_error (EmpathyCallWindow *self,
2132 TpyCallChannel *call,
2136 const gchar *details)
2138 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2139 GtkWidget *info_bar;
2140 GtkWidget *content_area;
2147 /* Create info bar */
2148 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2151 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
2153 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2155 /* hbox containing the image and the messages vbox */
2156 hbox = gtk_hbox_new (FALSE, 3);
2157 gtk_container_add (GTK_CONTAINER (content_area), hbox);
2160 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
2161 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2163 /* vbox containing the main message and the details expander */
2164 vbox = gtk_vbox_new (FALSE, 3);
2165 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2168 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2170 label = gtk_label_new (NULL);
2171 gtk_label_set_markup (GTK_LABEL (label), txt);
2172 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2173 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2176 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2179 if (details != NULL)
2181 GtkWidget *expander;
2183 expander = gtk_expander_new (_("Technical Details"));
2185 txt = g_strdup_printf ("<i>%s</i>", details);
2187 label = gtk_label_new (NULL);
2188 gtk_label_set_markup (GTK_LABEL (label), txt);
2189 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2190 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2193 gtk_container_add (GTK_CONTAINER (expander), label);
2194 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2197 g_signal_connect (info_bar, "response",
2198 G_CALLBACK (gtk_widget_destroy), NULL);
2200 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2201 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2202 gtk_widget_show_all (info_bar);
2206 media_stream_error_to_txt (EmpathyCallWindow *self,
2207 TpyCallChannel *call,
2209 TpMediaStreamError error)
2211 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2212 const gchar *cm = NULL;
2218 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2220 return g_strdup_printf (
2221 _("%s's software does not understand any of the audio formats "
2222 "supported by your computer"),
2223 empathy_contact_get_alias (priv->contact));
2225 return g_strdup_printf (
2226 _("%s's software does not understand any of the video formats "
2227 "supported by your computer"),
2228 empathy_contact_get_alias (priv->contact));
2230 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2231 return g_strdup_printf (
2232 _("Can't establish a connection to %s. "
2233 "One of you might be on a network that does not allow "
2234 "direct connections."),
2235 empathy_contact_get_alias (priv->contact));
2237 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2238 return g_strdup (_("There was a failure on the network"));
2240 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2242 return g_strdup (_("The audio formats necessary for this call "
2243 "are not installed on your computer"));
2245 return g_strdup (_("The video formats necessary for this call "
2246 "are not installed on your computer"));
2248 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2249 tp_connection_parse_object_path (
2250 tp_channel_borrow_connection (TP_CHANNEL (call)),
2253 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2254 "product=Telepathy&component=%s", cm);
2256 result = g_strdup_printf (
2257 _("Something unexpected happened in a Telepathy component. "
2258 "Please <a href=\"%s\">report this bug</a> and attach "
2259 "logs gathered from the 'Debug' window in the Help menu."), url);
2265 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2266 return g_strdup (_("There was a failure in the call engine"));
2268 case TP_MEDIA_STREAM_ERROR_EOS:
2269 return g_strdup (_("The end of the stream was reached"));
2271 case TP_MEDIA_STREAM_ERROR_UNKNOWN:
2278 empathy_call_window_stream_error (EmpathyCallWindow *self,
2279 TpyCallChannel *call,
2288 desc = media_stream_error_to_txt (self, call, audio, code);
2291 /* No description, use the error message. That's not great as it's not
2292 * localized but it's better than nothing. */
2293 display_error (self, call, icon, title, msg, NULL);
2297 display_error (self, call, icon, title, desc, msg);
2303 empathy_call_window_audio_stream_error (TpyCallChannel *call,
2306 EmpathyCallWindow *self)
2308 empathy_call_window_stream_error (self, call, TRUE, code, msg,
2309 "gnome-stock-mic", _("Can't establish audio stream"));
2313 empathy_call_window_video_stream_error (TpyCallChannel *call,
2316 EmpathyCallWindow *self)
2318 empathy_call_window_stream_error (self, call, FALSE, code, msg,
2319 "camera-web", _("Can't establish video stream"));
2324 empathy_call_window_state_changed_cb (EmpathyCallHandler *handler,
2326 EmpathyCallWindow *self)
2328 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2329 TpyCallChannel *call;
2330 gboolean can_send_video;
2332 if (state != TPY_CALL_STATE_ACCEPTED)
2335 if (priv->call_state == CONNECTED)
2338 g_timer_start (priv->timer);
2339 priv->call_state = CONNECTED;
2341 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2343 can_send_video = priv->video_input != NULL &&
2344 empathy_contact_can_voip_video (priv->contact) &&
2345 empathy_camera_monitor_get_available (priv->camera_monitor);
2347 g_object_get (priv->handler, "call-channel", &call, NULL);
2349 if (tpy_call_channel_has_dtmf (call))
2350 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2352 if (priv->video_input == NULL)
2353 empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
2355 gtk_widget_set_sensitive (priv->camera_button, can_send_video);
2357 empathy_call_window_show_hangup_button (self, TRUE);
2359 gtk_widget_set_sensitive (priv->mic_button, TRUE);
2361 clutter_actor_hide (priv->video_output);
2362 gtk_widget_show (priv->remote_user_avatar_widget);
2364 g_object_unref (call);
2366 g_mutex_lock (priv->lock);
2368 priv->timer_id = g_timeout_add_seconds (1,
2369 empathy_call_window_update_timer, self);
2371 g_mutex_unlock (priv->lock);
2373 empathy_call_window_update_timer (self);
2375 gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
2379 empathy_call_window_show_video_output_cb (gpointer user_data)
2381 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2383 if (self->priv->video_output != NULL)
2385 gtk_widget_hide (self->priv->remote_user_avatar_widget);
2386 clutter_actor_show (self->priv->video_output);
2393 empathy_call_window_check_video_cb (gpointer data)
2395 EmpathyCallWindow *self = data;
2397 if (self->priv->got_video)
2399 self->priv->got_video = FALSE;
2403 /* No video in the last N seconds, display the remote avatar */
2404 empathy_call_window_show_video_output (self, FALSE);
2409 /* Called from the streaming thread */
2411 empathy_call_window_video_probe_cb (GstPad *pad,
2412 GstMiniObject *mini_obj,
2413 EmpathyCallWindow *self)
2416 if (GST_IS_EVENT (mini_obj))
2419 if (G_UNLIKELY (!self->priv->got_video))
2421 /* show the remote video */
2422 g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
2423 empathy_call_window_show_video_output_cb,
2424 g_object_ref (self), g_object_unref);
2426 self->priv->got_video = TRUE;
2432 /* Called from the streaming thread */
2434 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2435 GstPad *src, guint media_type, gpointer user_data)
2437 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2438 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2439 gboolean retval = FALSE;
2443 g_mutex_lock (priv->lock);
2447 case TP_MEDIA_STREAM_TYPE_AUDIO:
2448 pad = empathy_call_window_get_audio_sink_pad (self);
2450 case TP_MEDIA_STREAM_TYPE_VIDEO:
2451 g_idle_add (empathy_call_window_show_video_output_cb, self);
2452 pad = empathy_call_window_get_video_sink_pad (self);
2454 gst_pad_add_data_probe (src,
2455 G_CALLBACK (empathy_call_window_video_probe_cb), self);
2456 if (priv->got_video_src > 0)
2457 g_source_remove (priv->got_video_src);
2458 priv->got_video_src = g_timeout_add_seconds (5,
2459 empathy_call_window_check_video_cb, self);
2462 g_assert_not_reached ();
2468 if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2469 g_warning ("Could not link %s sink pad",
2470 media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2474 gst_object_unref (pad);
2478 /* If no sink could be linked, try to add fakesink to prevent the whole call
2483 GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2485 if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2487 GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2488 if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2489 GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2491 gst_element_set_locked_state (fakesink, TRUE);
2492 gst_element_set_state (fakesink, GST_STATE_NULL);
2493 gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2497 DEBUG ("Could not link real sink, linked fakesink instead");
2499 gst_object_unref (sinkpad);
2503 gst_object_unref (fakesink);
2508 g_mutex_unlock (priv->lock);
2514 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2515 GstPad *sink, FsMediaType media_type, gpointer user_data)
2517 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2518 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2520 gboolean retval = FALSE;
2524 case FS_MEDIA_TYPE_AUDIO:
2525 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2527 g_warning ("Could not add audio source to pipeline");
2531 pad = gst_element_get_static_pad (priv->audio_input, "src");
2534 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2535 g_warning ("Could not get source pad from audio source");
2539 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2541 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2542 g_warning ("Could not link audio source to farsight");
2546 if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2548 g_warning ("Could not start audio source");
2549 gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2550 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2556 case FS_MEDIA_TYPE_VIDEO:
2557 if (priv->video_tee != NULL)
2559 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2560 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2562 g_warning ("Could not link video source input pipeline");
2565 gst_object_unref (pad);
2571 g_assert_not_reached ();
2578 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2580 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2581 GstElement *preview;
2583 disable_camera (self);
2585 DEBUG ("remove video input");
2586 preview = priv->video_preview_sink;
2588 gst_element_set_state (priv->video_input, GST_STATE_NULL);
2589 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2590 gst_element_set_state (preview, GST_STATE_NULL);
2592 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2595 g_object_unref (priv->video_input);
2596 priv->video_input = NULL;
2597 g_object_unref (priv->video_tee);
2598 priv->video_tee = NULL;
2599 clutter_actor_destroy (priv->video_preview);
2600 priv->video_preview = NULL;
2602 gtk_widget_set_sensitive (priv->camera_button, FALSE);
2606 start_call (EmpathyCallWindow *self)
2608 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2610 priv->call_started = TRUE;
2611 empathy_call_handler_start_call (priv->handler,
2612 gtk_get_current_event_time ());
2614 if (empathy_call_handler_has_initial_video (priv->handler))
2616 TpyCallChannel *call;
2619 g_object_get (priv->handler, "call-channel", &call, NULL);
2620 s = tpy_call_channel_get_video_state (call);
2622 if (s == TPY_SENDING_STATE_PENDING_SEND ||
2623 s == TPY_SENDING_STATE_SENDING)
2625 /* Enable 'send video' buttons and display the preview */
2626 gtk_toggle_tool_button_set_active (
2627 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), TRUE);
2631 gtk_toggle_tool_button_set_active (
2632 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
2634 if (priv->video_preview == NULL)
2636 create_video_preview (self);
2637 add_video_preview_to_pipeline (self);
2641 g_object_unref (call);
2646 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2649 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2650 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2653 empathy_call_handler_bus_message (priv->handler, bus, message);
2655 switch (GST_MESSAGE_TYPE (message))
2657 case GST_MESSAGE_STATE_CHANGED:
2658 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2660 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2661 if (newstate == GST_STATE_PAUSED)
2662 empathy_call_window_setup_video_input (self);
2664 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2665 !priv->call_started)
2667 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2668 if (newstate == GST_STATE_PAUSED)
2670 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2671 priv->pipeline_playing = TRUE;
2673 if (priv->start_call_when_playing)
2678 case GST_MESSAGE_ERROR:
2680 GError *error = NULL;
2681 GstElement *gst_error;
2684 gst_message_parse_error (message, &error, &debug);
2685 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2687 g_message ("Element error: %s -- %s\n", error->message, debug);
2689 if (g_str_has_prefix (gst_element_get_name (gst_error),
2690 VIDEO_INPUT_ERROR_PREFIX))
2692 /* Remove the video input and continue */
2693 if (priv->video_input != NULL)
2694 empathy_call_window_remove_video_input (self);
2695 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2699 empathy_call_window_disconnected (self, TRUE);
2701 g_error_free (error);
2704 case GST_MESSAGE_UNKNOWN:
2705 case GST_MESSAGE_EOS:
2706 case GST_MESSAGE_WARNING:
2707 case GST_MESSAGE_INFO:
2708 case GST_MESSAGE_TAG:
2709 case GST_MESSAGE_BUFFERING:
2710 case GST_MESSAGE_STATE_DIRTY:
2711 case GST_MESSAGE_STEP_DONE:
2712 case GST_MESSAGE_CLOCK_PROVIDE:
2713 case GST_MESSAGE_CLOCK_LOST:
2714 case GST_MESSAGE_NEW_CLOCK:
2715 case GST_MESSAGE_STRUCTURE_CHANGE:
2716 case GST_MESSAGE_STREAM_STATUS:
2717 case GST_MESSAGE_APPLICATION:
2718 case GST_MESSAGE_ELEMENT:
2719 case GST_MESSAGE_SEGMENT_START:
2720 case GST_MESSAGE_SEGMENT_DONE:
2721 case GST_MESSAGE_DURATION:
2722 case GST_MESSAGE_ANY:
2731 empathy_call_window_members_changed_cb (TpyCallChannel *call,
2732 GHashTable *members,
2733 EmpathyCallWindow *self)
2735 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2736 GHashTableIter iter;
2737 gpointer key, value;
2738 gboolean held = FALSE;
2740 g_hash_table_iter_init (&iter, members);
2741 while (g_hash_table_iter_next (&iter, &key, &value))
2743 if (GPOINTER_TO_INT (value) & TPY_CALL_MEMBER_FLAG_HELD)
2745 /* This assumes this is a 1-1 call, otherwise one participant
2746 * putting the call on hold wouldn't mean the call is on hold
2754 priv->call_state = HELD;
2755 else if (priv->call_state == HELD)
2756 priv->call_state = CONNECTED;
2760 call_handler_notify_call_cb (EmpathyCallHandler *handler,
2762 EmpathyCallWindow *self)
2764 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2765 TpyCallChannel *call;
2767 g_object_get (priv->handler, "call-channel", &call, NULL);
2772 tp_g_signal_connect_object (call, "audio-stream-error",
2773 G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
2774 tp_g_signal_connect_object (call, "video-stream-error",
2775 G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
2778 tp_g_signal_connect_object (call, "members-changed",
2779 G_CALLBACK (empathy_call_window_members_changed_cb), self, 0);
2781 g_object_unref (call);
2785 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2787 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2788 TpyCallChannel *call;
2791 /* Make the hangup button twice as wide */
2792 width = gtk_widget_get_allocated_width (priv->hangup_button);
2793 gtk_widget_set_size_request (priv->hangup_button, width * 2, -1);
2795 g_signal_connect (priv->handler, "state-changed",
2796 G_CALLBACK (empathy_call_window_state_changed_cb), window);
2797 g_signal_connect (priv->handler, "conference-added",
2798 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2799 g_signal_connect (priv->handler, "conference-removed",
2800 G_CALLBACK (empathy_call_window_conference_removed_cb), window);
2801 g_signal_connect (priv->handler, "closed",
2802 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2803 g_signal_connect (priv->handler, "src-pad-added",
2804 G_CALLBACK (empathy_call_window_src_added_cb), window);
2805 g_signal_connect (priv->handler, "sink-pad-added",
2806 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2807 g_signal_connect (priv->handler, "sink-pad-removed",
2808 G_CALLBACK (empathy_call_window_sink_removed_cb), window);
2810 g_object_get (priv->handler, "call-channel", &call, NULL);
2813 call_handler_notify_call_cb (priv->handler, NULL, window);
2814 g_object_unref (call);
2818 /* call-channel doesn't exist yet, we'll connect signals once it has been
2820 g_signal_connect (priv->handler, "notify::call-channel",
2821 G_CALLBACK (call_handler_notify_call_cb), window);
2824 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2828 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2829 EmpathyCallWindow *window)
2831 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2833 if (priv->pipeline != NULL)
2835 if (priv->bus_message_source_id != 0)
2837 g_source_remove (priv->bus_message_source_id);
2838 priv->bus_message_source_id = 0;
2841 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2844 if (priv->call_state == CONNECTING)
2845 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2851 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2854 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2856 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2861 gtk_widget_hide (priv->sidebar);
2862 gtk_widget_hide (menu);
2863 gtk_widget_hide (priv->toolbar);
2867 if (priv->sidebar_was_visible_before_fs)
2868 gtk_widget_show (priv->sidebar);
2870 gtk_widget_show (menu);
2871 gtk_widget_show (priv->toolbar);
2873 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2874 priv->original_height_before_fs);
2879 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2881 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2883 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2884 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2885 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2886 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2888 if (priv->video_output != NULL)
2891 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2892 priv->video_output, TRUE, TRUE,
2893 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2900 empathy_call_window_state_event_cb (GtkWidget *widget,
2901 GdkEventWindowState *event, EmpathyCallWindow *window)
2903 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2905 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2906 gboolean set_fullscreen = event->new_window_state &
2907 GDK_WINDOW_STATE_FULLSCREEN;
2911 gboolean sidebar_was_visible;
2912 GtkAllocation allocation;
2913 gint original_width, original_height;
2915 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2916 original_width = allocation.width;
2917 original_height = allocation.height;
2919 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2921 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2922 priv->original_width_before_fs = original_width;
2923 priv->original_height_before_fs = original_height;
2925 if (priv->video_output_motion_handler_id == 0 &&
2926 priv->video_output != NULL)
2928 priv->video_output_motion_handler_id = g_signal_connect (
2929 G_OBJECT (priv->video_container), "motion-notify-event",
2930 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2936 disconnect_video_output_motion_handler (window);
2939 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2941 show_controls (window, set_fullscreen);
2942 show_borders (window, set_fullscreen);
2943 gtk_action_set_stock_id (priv->menu_fullscreen,
2944 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2945 priv->is_fullscreen = set_fullscreen;
2952 empathy_call_window_update_sidebar_buttons (EmpathyCallWindow *window,
2955 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2957 /* Update dialpad button */
2958 g_signal_handlers_block_by_func (priv->dialpad_button,
2959 empathy_call_window_dialpad_cb, window);
2960 gtk_toggle_tool_button_set_active (
2961 GTK_TOGGLE_TOOL_BUTTON (priv->dialpad_button),
2963 g_signal_handlers_unblock_by_func (priv->dialpad_button,
2964 empathy_call_window_dialpad_cb, window);
2966 /* Update sidebar menu */
2967 g_signal_handlers_block_by_func (priv->menu_sidebar,
2968 empathy_call_window_sidebar_cb, window);
2969 gtk_toggle_action_set_active (
2970 GTK_TOGGLE_ACTION (priv->menu_sidebar),
2971 gtk_widget_get_visible (priv->sidebar));
2972 g_signal_handlers_unblock_by_func (priv->menu_sidebar,
2973 empathy_call_window_sidebar_cb, window);
2977 empathy_call_window_show_sidebar (EmpathyCallWindow *window,
2980 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2981 int w, h, sidebar_width, handle_size;
2982 GtkAllocation allocation;
2984 gboolean dialpad_shown;
2986 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2987 w = allocation.width;
2988 h = allocation.height;
2990 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2992 gtk_widget_get_preferred_width (priv->sidebar, &sidebar_width, NULL);
2996 gtk_widget_show (priv->sidebar);
2997 w += sidebar_width + handle_size;
3001 w -= sidebar_width + handle_size;
3002 gtk_widget_hide (priv->sidebar);
3006 gtk_window_resize (GTK_WINDOW (window), w, h);
3008 /* Update the 'Dialpad' menu */
3009 page = ev_sidebar_get_current_page (EV_SIDEBAR (priv->sidebar));
3010 dialpad_shown = active && !tp_strdiff (page, "dialpad");
3013 empathy_call_window_update_sidebar_buttons (window, dialpad_shown);
3017 empathy_call_window_set_send_video (EmpathyCallWindow *window,
3020 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3021 TpyCallChannel *call;
3023 priv->sending_video = (state == CAMERA_STATE_ON);
3025 if (state == CAMERA_STATE_ON)
3027 /* When we start sending video, we want to show the video preview by
3029 display_video_preview (window, TRUE);
3033 display_video_preview (window, FALSE);
3036 if (priv->call_state != CONNECTED)
3039 g_object_get (priv->handler, "call-channel", &call, NULL);
3040 DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
3041 tpy_call_channel_send_video (call, priv->sending_video);
3042 g_object_unref (call);
3046 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
3047 EmpathyCallWindow *window)
3049 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3052 active = (gtk_toggle_tool_button_get_active (toggle));
3056 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
3058 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
3062 /* TODO, Instead of setting the input volume to 0 we should probably
3063 * stop sending but this would cause the audio call to drop if both
3064 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
3065 * in the future. GNOME #574574
3067 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
3069 gtk_adjustment_set_value (priv->audio_input_adj, 0);
3074 empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
3075 EmpathyCallWindow *window)
3077 empathy_call_window_show_sidebar (window, FALSE);
3081 empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
3082 EmpathyCallWindow *window)
3084 empathy_call_window_show_sidebar (window, TRUE);
3088 empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
3090 EmpathyCallWindow *window)
3092 empathy_call_window_update_sidebar_buttons (window,
3093 !tp_strdiff (page, "dialpad"));
3097 empathy_call_window_hangup_cb (gpointer object,
3098 EmpathyCallWindow *window)
3100 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3102 empathy_call_handler_stop_call (priv->handler);
3104 if (empathy_call_window_disconnected (window, FALSE))
3105 gtk_widget_destroy (GTK_WIDGET (window));
3109 empathy_call_window_restart_call (EmpathyCallWindow *window)
3111 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3113 /* Remove error info bars */
3114 gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
3115 (GtkCallback) gtk_widget_destroy, NULL);
3117 create_video_output_widget (window);
3119 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
3120 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
3122 /* While the call was disconnected, the input volume might have changed.
3123 * However, since the audio_input source was destroyed, its volume has not
3124 * been updated during that time. That's why we manually update it here */
3125 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
3127 priv->outgoing = TRUE;
3128 empathy_call_window_set_state_connecting (window);
3130 if (priv->pipeline_playing)
3131 start_call (window);
3133 /* call will be started when the pipeline is ready */
3134 priv->start_call_when_playing = TRUE;
3136 empathy_call_window_setup_avatars (window, priv->handler);
3138 empathy_call_window_show_hangup_button (window, TRUE);
3142 empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
3143 EmpathyCallWindow *window)
3145 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3148 active = gtk_toggle_tool_button_get_active (button);
3151 ev_sidebar_set_current_page (EV_SIDEBAR (priv->sidebar), "dialpad");
3153 empathy_call_window_show_sidebar (window, active);
3157 empathy_call_window_sidebar_cb (GtkToggleAction *menu,
3158 EmpathyCallWindow *self)
3160 empathy_call_window_show_sidebar (self,
3161 gtk_toggle_action_get_active (menu));
3165 empathy_call_window_fullscreen_cb (gpointer object,
3166 EmpathyCallWindow *window)
3168 empathy_call_window_fullscreen_toggle (window);
3172 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
3174 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3176 if (priv->is_fullscreen)
3177 gtk_window_unfullscreen (GTK_WINDOW (window));
3179 gtk_window_fullscreen (GTK_WINDOW (window));
3183 empathy_call_window_video_button_press_cb (GtkWidget *video_preview,
3184 GdkEventButton *event, EmpathyCallWindow *window)
3186 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
3188 empathy_call_window_video_menu_popup (window, event->button);
3196 empathy_call_window_key_press_cb (GtkWidget *video_output,
3197 GdkEventKey *event, EmpathyCallWindow *window)
3199 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3201 if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
3203 /* Since we are in fullscreen mode, toggling will bring us back to
3205 empathy_call_window_fullscreen_toggle (window);
3213 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
3214 GdkEventMotion *event, EmpathyCallWindow *window)
3216 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3218 if (priv->is_fullscreen)
3220 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
3227 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
3231 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3233 menu = gtk_ui_manager_get_widget (priv->ui_manager,
3235 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
3236 button, gtk_get_current_event_time ());
3237 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
3241 empathy_call_window_status_message (EmpathyCallWindow *self,
3244 gtk_label_set_label (GTK_LABEL (self->priv->status_label), message);
3248 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
3249 gdouble value, EmpathyCallWindow *window)
3251 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3253 if (priv->audio_output == NULL)
3256 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),